Home > Mobile >  Issue with useDispatch() in custom hook
Issue with useDispatch() in custom hook

Time:07-19

I'm trying to dispatch a Redux action from a custom hook with useDispatch but I'm getting an "Invalid hook call" error. If I'm understanding the rules of hooks correctly, I don't think I'm breaking any of the rules as I'm calling useDispatch from a custom hook. What am I missing??

CODE

Main selector for functional component

export const getFlattenedDevicesList = createSelector(
  getDevicesList,
  (deviceList) => {
    return (
      deviceList
        .map(makeOrganizationKey)
        .map(makeProjectKey)
        .map(makeDeviceProfileKey)
        .map(DispatchLSU) // custom hook
    );
  }
);

Custom hook

export function DispatchLSU(resource) {
  const dispatch = useDispatch();
  console.log(resource.device_id);
  const deviceId = resource.device_id;
  // useEffect(() => {
  //   dispatch(getHealthLsu(deviceId));
  // });
  dispatch(getHealthLsu(deviceId));

  const result = {
    ...resource,
  };

  return result;
};

The goal is to dispatch the action for each entry in deviceList then insert new data from API call.

CodePudding user response:

Hooks rule: Only Call Hooks from React Functions

This code is breaking hooks rules, Only Call Hooks from React Functions.

You are probably calling getFlattenedDevicesList with useSelector then inside of this you call DispatchLSU who call useDispatch.

So you have a hook inside a hook that takes a callback:

useSelector -> getFlattenedDevicesList -> DispatchLSU -> useDispatch

How to call a selector and dispatch with hooks ?

If you want to call dispatch and your selector inside a hook. Here is how to achieve it

export function DispatchLSU() {
  const dispatch = useDispatch();
  const resource = useSelector(getFlattenedDevicesList);
  const deviceId = resource.device_id;
  useEffect(() => {
    dispatch(getHealthLsu(deviceId));
  }, []);

  const result = {
    ...resource,
  };

  return result;
};

Additional notes

  • Convention is to name custom hooks with useXXX
  • Instead of dispatching inside the hook method, you can use useDispatch or useCallback to avoid making a dispatch when you render
  • Instead of having a selector that return all resources you can have another one that return only the deviceId

CodePudding user response:

Your issue is that getFlattenedDevicesList is a normal function and a normal function can not call a hook. Hooks can only be called from the top level of either 1. a react component or 2. another hook. You can convert getFlattenedDevicesList to a hook by using another hook inside it, but then you can only do so at the top level.

i.e.

const getFlattenedDevicesList = () => {
  // you can only use hooks here

  if (true) {
    // you can't use hooks here
    // or anywhere that's not at top level of getFlattenedDevicesList
  }
}

The entrypoint or root of where you start calling that hook from, along with the entire hook chain it may or may not be calling, must be either a component or a hook.

Example:

const example = () => {}

That is a normal function, not a hook.

const useExample = () => {}

That is a normal JS function, not a hook.

const useExample = () => {
  const [value, setValue] = useState()
}

That's a custom hook since it's using another hook, react will figure out that it needs to be a hook as well.

const example = () => {
  const [value, setValue] = useState()
}

That's also exactly the same custom hook as above and will work exactly the same as the hook above, it's just called different.

If you want to chain hooks:

const useHook1 = () => {
  const [value, setValue] = useState()

  return {value, setValue}
}

const useHook2 = () => {
  const {value, setValue} = useHook1()

  return {value}
}

That's valid because any hook can also call another hook, just like you did when using useState.

However if you now want to use useHook2 somewhere that has to be called from another hook or a functional component.

i.e.

const Component = (): JSX.Element => {
  const {value} = useHook2()
}

Is valid.

However if you try to call useHook1 or useHook2 from any normal function or anything that's not a hook or a function component it will break.

To fix it you can do something like:

const useDispatch = () => {
  const dispatch = (resource) => {
    // do whatever here
  }

  return dispatch
}

const useExample = () => {
  const dispatch = useDispatch()

  dispatch(// resource)
}

Note: The naming of the hook is irrelevant, you can call it anything you want. React will figure out it's a hook based on the fact that it's using other hooks or something like useState. However it is strongly recommended that you do prefix it with use, i.e. useSomeHook, because 1. it makes it easy for other developers to see what is a hook and what's a normal function and 2. a lot of tools like eslint is built around a use prefix in order to apply hook rules and validations so if you don't name it with "use" those things will not work correctly.

  • Related