skip to Main Content

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?

2

Answers


  1. 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.

    Login or Signup to reply.
  2. If you want conditionally use a hook, use the condition in its implementation.

    Please check the accepted post here use the condition in its implementation.

    Please see below your code refactored on the same basis. The test run results have also been enclosed.

    App.js

    import { useEffect, useState } from 'react';
    
    export default function App() {
      const [variant, setVariant] = useState('John');
    
      useEffect(() => {
        setTimeout(() => setVariant('Jane'), 5000);
      }, []);
    
      return (
        <>
          <DataDisplay variant={variant} />
        </>
      );
    }
    const useJohn = (variant) => {
      if (variant === 'John') {
        // fetch & parse some data
        return { parsedData: { date: new Date(), name: 'John Doe' } };
      } else {
        return null;
      }
    };
    const useJane = (variant) => {
      if (variant === 'Jane') {
        // fetch & parse some data
        return { parsedData: { date: new Date(), name: 'Jane Smith' } };
      } else {
        return null;
      }
    };
    
    const DataDisplay = ({ variant }) => {
    
      const parsedDataJohn = useJohn(variant);
      const parsedDataJane = useJane(variant);
      const { parsedData } = parsedDataJohn ? parsedDataJohn : parsedDataJane;
    
      return <div>{`${parsedData.date.toString()} ${parsedData.name}`}</div>;
    };
    

    Test run: Browser displays – on load and after 5 seconds
    Browser displays - on load
    Browser displays - after 5 seconds

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search