Skip to content

Commit

Permalink
fix: validate screen configs
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed Jan 23, 2020
1 parent 9976a88 commit 2f1f0af
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 3 deletions.
2 changes: 2 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@
"dependencies": {
"escape-string-regexp": "^2.0.0",
"query-string": "^6.9.0",
"react-is": "^16.12.0",
"shortid": "^2.2.15",
"use-subscription": "^1.3.0"
},
"devDependencies": {
"@babel/core": "^7.7.7",
"@react-native-community/bob": "^0.8.0",
"@types/react": "^16.9.17",
"@types/react-is": "^16.7.1",
"@types/shortid": "^0.0.29",
"@types/use-subscription": "^1.0.0",
"del-cli": "^3.0.0",
Expand Down
97 changes: 97 additions & 0 deletions packages/core/src/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -936,3 +936,100 @@ it('switches rendered navigators', () => {
'Another navigator is already registered for this container.'
);
});

it('throws if both children and component are passed', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};

const element = (
<NavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()}>
{jest.fn()}
</Screen>
</TestNavigator>
</NavigationContainer>
);

expect(() => render(element).update(element)).toThrowError(
"We got both 'component' and 'children' props for 'Screen'. You must pass only one of them."
);
});

it('throws descriptive error for undefined screen component', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};

const element = (
<NavigationContainer>
<TestNavigator>
<Screen name="foo" component={undefined as any} />
</TestNavigator>
</NavigationContainer>
);

expect(() => render(element).update(element)).toThrowError(
"We couldn't find a 'component' or 'children' prop for 'Screen'"
);
});

it('throws descriptive error for invalid screen component', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};

const element = (
<NavigationContainer>
<TestNavigator>
<Screen name="foo" component={{} as any} />
</TestNavigator>
</NavigationContainer>
);

expect(() => render(element).update(element)).toThrowError(
"We got an invalid value for 'component' prop for 'Screen'. It must be a a valid React Component."
);
});

it('throws descriptive error for invalid children', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};

const element = (
<NavigationContainer>
<TestNavigator>
<Screen name="foo">{[] as any}</Screen>
</TestNavigator>
</NavigationContainer>
);

expect(() => render(element).update(element)).toThrowError(
"We got an invalid value for 'children' prop for 'Screen'. It must be a function returning a React Element."
);
});

it("doesn't throw if children is null", () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
return null;
};

const element = (
<NavigationContainer>
<TestNavigator>
<Screen name="foo" component={jest.fn()}>
{null as any}
</Screen>
</TestNavigator>
</NavigationContainer>
);

expect(() => render(element).update(element)).not.toThrowError();
});
38 changes: 36 additions & 2 deletions packages/core/src/useNavigationBuilder.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { isValidElementType } from 'react-is';
import { NavigationStateContext } from './NavigationContainer';
import NavigationRouteContext from './NavigationRouteContext';
import Screen from './Screen';
Expand Down Expand Up @@ -53,8 +54,8 @@ const isArrayEqual = (a: any[], b: any[]) =>
*/
const getRouteConfigsFromChildren = <ScreenOptions extends object>(
children: React.ReactNode
) =>
React.Children.toArray(children).reduce<
) => {
const configs = React.Children.toArray(children).reduce<
RouteConfig<ParamListBase, string, ScreenOptions>[]
>((acc, child) => {
if (React.isValidElement(child)) {
Expand Down Expand Up @@ -85,6 +86,39 @@ const getRouteConfigsFromChildren = <ScreenOptions extends object>(
);
}, []);

if (process.env.NODE_ENV !== 'production') {
configs.forEach(config => {
const { children, component } = config as any;

if (children != null || component !== undefined) {
if (children != null && component !== undefined) {
throw new Error(
"We got both 'component' and 'children' props for 'Screen'. You must pass only one of them."
);
}

if (children != null && typeof children !== 'function') {
throw new Error(
`We got an invalid value for 'children' prop for 'Screen'. It must be a function returning a React Element.`
);
}

if (component !== undefined && !isValidElementType(component)) {
throw new Error(
`We got an invalid value for 'component' prop for 'Screen'. It must be a a valid React Component.`
);
}
} else {
throw new Error(
"We couldn't find a 'component' or 'children' prop for 'Screen'. This can happen if you passed 'undefined'. You likely forgot to export your component from the file it's defined in, or mixed up default import and named import when importing."
);
}
});
}

return configs;
};

/**
* Hook for building navigators.
*
Expand Down
9 changes: 8 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3081,6 +3081,13 @@
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.0.tgz#2a5fa918786d07d3725726f7f650527e1cfeaffd"
integrity sha512-c4zji5CjWv1tJxIZkz1oUtGcdOlsH3aza28Nqmm+uNDWBRHoMsjooBEN4czZp1V3iXPihE/VRUOBqg+4Xq0W4g==

"@types/react-is@^16.7.1":
version "16.7.1"
resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-16.7.1.tgz#d3f1c68c358c00ce116b55ef5410cf486dd08539"
integrity sha512-dMLFD2cCsxtDgMkTydQCM0PxDq8vwc6uN5M/jRktDfYvH3nQj6pjC9OrCXS2lKlYoYTNJorI/dI8x9dpLshexQ==
dependencies:
"@types/react" "*"

"@types/react-native-vector-icons@^6.4.5":
version "6.4.5"
resolved "https://registry.yarnpkg.com/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.5.tgz#74cbfc564bd8435e43ad6728572a0e5b49c335d1"
Expand Down Expand Up @@ -13535,7 +13542,7 @@ react-error-overlay@^6.0.1:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.4.tgz#0d165d6d27488e660bc08e57bdabaad741366f7a"
integrity sha512-ueZzLmHltszTshDMwyfELDq8zOA803wQ1ZuzCccXa1m57k1PxSHfflPD5W9YIiTXLs0JTLzoj6o1LuM5N6zzNA==

react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
Expand Down

0 comments on commit 2f1f0af

Please sign in to comment.