I have two custom React hooks that fetch different data and parse it. Both hooks return an object of the same type. I have a component that displays that data and depending on a "variant" prop, i want it to decide which of those two hooks it mounts.
Example code:
const useJohn = () => {
// fetch & parse some data
return { parsedData: { date: new Date(), name: 'John Doe' } }
}
const useJane = () => {
// fetch & parse some data
return { parsedData: { date: new Date(), name: 'Jane Smith' } }
}
const dataDisplay = ({ variant }: { variant: 'John' | 'Jane' }) => {
const hook = variant === 'John' ? useJohn : useJane
const { parsedData } = hook()
return <div>{`${parsedData.date.toString()} ${parsedData.name}`}</div>
}
I get no warnings/errors from this pattern and everything seems to be working fine. Is this a legitimate way of conditionally using custom hooks? Or are there any issues with this pattern that i am not aware of?
Ok, first, we'll suppose that both useJohn and useJane are calling some effects herein.
And if they do, doing:
const hook = variant === 'John' ? useJohn : useJane
const { parsedData } = hook()
is wrong, and will fail!
If it did not fail yet, it's because you did not update the prop of your component. But as soon as the prop changes, the hook being called will change, and you will break a react invariant which is that a component should render the exact same number of hook calls at each render.
i.e., this will fail:
const MyComponent = () => {
const [data, setData] = useState<'John' | 'Jane'>('John');
useEffect(() => {
setTimeout(() => setData('Jane'), 1000)
}, [setData])
<dataDisplay variant={{variant: data}} />
How should you fix that?
Either you create a high order component that handles the John vs Jane decision, so that you have two components: one that does useJohn and the other that always does useJane, and use the same display component:
const DecideJohnVsJane = (choice: 'John' | 'Jane') => {
switch (choice) {
case "John": return <JohnComponent />
case "Jane": return <JaneComponent />
}
}
const JohnComponent = () => {
const parsedData = useJohn();
return <DisplayComponent data={parsedData} />
}
const JaneComponent = () => {
const parsedData = useJane();
return <DisplayComponent data={parsedData} />
}
const DisplayComponent = (parsedData) => <div>{`${parsedData.date.toString()} ${parsedData.name}`}</div>
Another, maybe simpler way, would be to create a single effect:
const useJaneOrJohn = (choice: 'John' | 'Jane') => {
switch (choice) {
case 'John':
// fetch the john specific stuff
return parsedData
case 'Jane':
// fetch the jane specific stuff
return parsedData
}
}
Which is best really depends on context that's not shared in your question, and that choice is up to you.