Skip to content

Commit 0ae19d6

Browse files
committed
Show error message in console if given invalid value as prop to render
1 parent 19ddaa3 commit 0ae19d6

File tree

4 files changed

+54
-28
lines changed

4 files changed

+54
-28
lines changed

src/components/ui/Accordion/Accordion.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import AccordionItem from './fragments/AccordionItem';
44
import AccordionHeader from './fragments/AccordionHeader';
55
import AccordionTrigger from './fragments/AccordionTrigger';
66
import AccordionContent from './fragments/AccordionContent';
7-
import { isReactNode } from '~/core/types';
7+
import { validateReactNode } from '~/core/types';
88

99
export type AccordionProps = {
1010
items: {title: string, content: React.ReactNode}[];
@@ -17,11 +17,11 @@ const Accordion = ({ items } : AccordionProps) => {
1717
<AccordionItem value={index} key={index} >
1818
<AccordionHeader>
1919
<AccordionTrigger>
20-
{isReactNode(item.title) ? item.title : 'Accordion title must be a valid string'}
20+
{validateReactNode(item.title, 'title') && item.title}
2121
</AccordionTrigger>
2222
</AccordionHeader>
2323
<AccordionContent>
24-
{isReactNode(item.content) ? item.content : 'Accordion content must be a valid React element'}
24+
{validateReactNode(item.content, 'content') && item.content}
2525
</AccordionContent>
2626
</AccordionItem>
2727
))}

src/components/ui/Accordion/tests/Accordion.test.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,25 @@ describe('Accordion', () => {
5555
expect(screen.getByText('is')).not.toHaveAttribute('hidden');
5656
});
5757

58-
test('renders component when header and content are invalid', () => {
59-
// @ts-expect-error: title and content should be a string and ReactNode, respectively
60-
const { rerender } = render(<Accordion items={[{ title: 28, content: { key: 'value' } }]} />);
61-
expect(screen.getByText(28)).toBeInTheDocument();
62-
expect(screen.getByText('Accordion content must be a valid React element')).toBeInTheDocument();
58+
describe('props to render are invalid', () => {
59+
beforeEach(() => {
60+
console.error = jest.fn();
61+
});
6362

64-
// @ts-expect-error: title and content should be a string and ReactNode, respectively
65-
rerender(<Accordion items={[{ title: { hi: 'bye' }, content: () => {} }]} />);
66-
expect(screen.getByText('Accordion title must be a valid string')).toBeInTheDocument();
67-
expect(screen.getByText('Accordion content must be a valid React element')).toBeInTheDocument();
63+
test('renders accordion when title and content are invalid', () => {
64+
// @ts-expect-error: title and content should be a string and ReactNode, respectively
65+
render(<Accordion items={[{ title: { hi: 'bye' }, content: () => {} }]} />);
66+
expect(console.error).toHaveBeenCalledWith('title is not a valid React node');
67+
expect(console.error).toHaveBeenCalledWith('content is not a valid React node');
68+
});
6869

69-
// @ts-expect-error: item should contain title and content keys
70-
rerender(<Accordion items={[{ extra: '' }]} />);
71-
expect(screen.getByTestId('accordion-root')).toBeInTheDocument();
70+
test('renders accordion when title and content are missing', () => {
71+
// @ts-expect-error: item should contain title and content keys
72+
render(<Accordion items={[{ extra: '' }]} />);
73+
expect(screen.getByTestId('accordion-root')).toBeInTheDocument();
74+
// nothing is printed to the console since a value is undefined if it's not given and undefined
75+
// is a valid React node
76+
expect(console.error).not.toHaveBeenCalled();
77+
});
7278
});
7379
});

src/core/types.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/core/types.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
3+
/**
4+
* Check that the given value is a valid React Node in a component before rendering it to avoid
5+
* rendering invalid values and crashing the app
6+
*
7+
* @param value value to validate
8+
* @param name user-friendly name of the value
9+
* @returns value if it's a valid React Node and false otherwise
10+
*/
11+
export const validateReactNode = (value: any, name?: string): React.ReactNode => {
12+
if (isReactNode(value)) {
13+
return value;
14+
}
15+
16+
console.error(`${name || value} is not a valid React node`);
17+
return false;
18+
};
19+
20+
/**
21+
* Check if the given value is a valid React Node
22+
*/
23+
const isReactNode = (value: any): value is React.ReactNode => {
24+
return (
25+
typeof value === 'string' ||
26+
typeof value === 'number' ||
27+
typeof value === 'boolean' ||
28+
value === null ||
29+
value === undefined ||
30+
React.isValidElement(value) ||
31+
(Array.isArray(value) && value.every(isReactNode))
32+
);
33+
};

0 commit comments

Comments
 (0)