Home > Software design >  Would this be considered "breaking the rules of hooks"?
Would this be considered "breaking the rules of hooks"?

Time:02-04

I have a compound component that is used to build a bunch of different (but similar) screens in a react native app. Data comes from a CMS, and depending on the value of a prop called type it needs to have different state variables, different validation, etc. I've written a config object who's methods map to the values of type, and contain the state and functions needed for each use case. The pattern looks like this (edited for example sake):

import { useState } from 'react';

const MyComponent = props => {
  const { type } = props; // possible values of type are 'A', 'B', 'C'

  const config = {
    A() {
      const [value, setValue] = useState('');
      
      function onChange({ target }) {
        setValue(target.value);
      }
      return {
        value,
        onChange
      }
    },
    B() {
      // ...
      return {
        value: '',
        onChange: () => {}
      }
    },
    C() {
      // ...
      return {
        value: '',
        onChange: () => {}
      }
    }
  }[type]();

  return <input value={config.value} onChange={config.onChange} />
};

export default MyComponent;

BUT!

In the react docs it says this:

Don’t call Hooks inside loops, conditions, or nested functions.

My question is - does the above example violate the rules of hooks? It seems to work for what I need it to, but I suspect that it may cause problems. Would appreciate any thoughts / discussion. Cheers!

CodePudding user response:

Short answer: yes.

Long answer:

React depends on hooks to be called in the exact same order between renders (I believe internally the hooks are stored in a linked list, which is altered on mounting and unmounting of the component, if the order changes between renders without a mount and unmount, React has no way to know which hook belongs to which component).

Because your rendering from a CMS, the value of type is unlikely to change between renders, so you probably will never get in to trouble with this particular use case, but it is a bad habit to get in to, and there is almost certainly a better way to handle what you are trying to do. Maybe write a hook that takes type as an argument?

function useMyHook(type:string) {
  const [state, setState] = useState('');
  const onChange = useMemo(() => {
    if (type = 'A')
      return () => doXyz()
  }, [type])
  return {state, onChange}
}

This is contrived, but hopefully will get you thinking of alternate approaches to your problem.

  •  Tags:  
  • Related