7

I'm starting to use hooks in React and I got stuck, when I realized I would need an array of hooks to solve my problem. But according to the Rules of Hooks

Only Call Hooks at the Top Level

I'm not allow to call hooks inside a loop (and I guess also not in map).

My custom hook subscribes to an API and adds data to the state when there is an update:

export const useTrace = (id) => {
  [trace, setTrace] = useState([])

  useEffect(() => {
    Api.getCurrentTrace(id)
      .then(currentTrace => {
        setTrace(currentTrace)
      })
  }, [id])
  
  useEffect(() => {
    Api.subscribeTraceUpdate(onUpdateTrip)
    
    return () => {
      Api.unsubscribeTraceUpdate(onUpdateTrip)
    }
  }, [])

  const onUpdateTrip = msg => {
    if (msg.id === id) {
      setTrace([msg.data].concat(trace))
    }
  }
}

In my component I have a state with an array of IDs. For each ID I would like to use the useTrace(id) hook somehow like this:

import DeckGL from '@deck.gl/react'

function TraceMap({ ids }) {
  const data = ids.map((id) => ({
    id,
    path: useTrace(id)
  }))

  const pathLayer = new PathLayer({
    id: 'path-layer',
    data,
    getPath: d => d.path
  })

  return <DeckGL
    layers={[ pathLayer ]}
  />
}

For the sake of simplicity I got ids as a property instead of having a state.

1
  • Maybe think about doing this in a more performant process. If you are using arrays to update state on any change instead do a test and compare the array data rather than your current intention. Commented Jan 8, 2021 at 14:03

2 Answers 2

2

Why not have a useTraces custom hook rather than useTrace. This new hook can take an array of ids instead of a single id.

export const useTraces = (ids) => {
  [traces, setTraces] = useState([]);

  useEffect(() => {
    (async () => {
      const traces = await Promise.all(
        ids.map((id) => Api.getCurrentTrace(id))
      );
      setTraces(traces);
    })();
  }, [ids]);

  // ... 
};
Sign up to request clarification or add additional context in comments.

4 Comments

I'm using that hook also in an other component, where I only need the trace of an single ID. I agree, having a useTraces hook would solve that problem. The way you expressed it, would create an Api.getCurrentTraces call for every ID, when only one ID is added or removed.
You can further optimise this hook to only send requests for changed ids. Regarding the other component, you can just send an array of single id.
I hoped that there is a way to use reacts mechanism somehow, where it compares the old IDs (keys) with the new ones and only update objects where the ID (key) changed.
The problem here is what if you need to request a trace for a single ID? The public API for the hook becomes rather awkward to use (it's a good idea to show it, because this looks reasonable without the ugly calls to setTraces).
1

Another idea might be to create a sub component and use your hook in each of them.

2 Comments

What if the parent needs the state in aggregate, though? Then the children would need to pass their data up via a callback. Not terrible, but also breaches the "when state is shared, hoist it to the parent" rule of React.
If, yes, of course. But, I could not see any use by the parent in the given use case. But I totally agree, if the parent needs the state, the parent manages it. That's a basic principle in React.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.