简体   繁体   中英

How to avoid setState in useEffect hook causing second render

I'm writing a custom hook that takes an id as input and then should do the following tasks:

  • get data for that id synchronously from a store
  • subscribe to changes in this store and update data accordingly

I came up with the following implementation which has the downside that I am setting state directly from inside the effect hook, causing a second render.

function useData({id}) {
    // set initial data on first render
    const [data, setData] = useState(Store.getData(id));

    useEffect(() => {
        // when id changes, set data to whatever is in store at the moment
        // here we call setData directly from inside useEffect, causing a second render
        setData(Store.getData(id));
        // subscribe to changes to update data whenever it changes in the store
        return Store.onChange(id, setData);
    }, [id]);

    return data;
}

A second approach I tried was to add a dummy state that is only there to cause a re-render. In this approach I am directly returning the data received from Store.getData(). This ensures that I will get the freshest data with every render and the useEffect ensures that every onChange trigger will cause a new render.

function useData({id}) {
    // adding some dummy state that we only use to force a render
    const [, setDummy] = useState({});
    const refresh = useCallback(() => {
        setDummy({});
    }, []);

    useEffect(() => {
        // subscribe to changes cause a refresh on every change
        return Store.onChange(id, refresh);
    }, [id, refresh]);

    return Store.getData[id];
}

The second approach works well but it feels weird to add this dummy state. Sure, I could put this into another useRefresh hook but I am not sure if this would really be a good practice.

Is there any better way of implementing this, without calling setData directly from inside useEffect and without relying on some unused dummy state?

So by now you use useState inside your hook just to re-trigger rendering of host component once store is changed. How about taking change handler from the outside?

function useData(id, onChanged) {
  useEffect(() => {
    return store.onChange(id, onChanged);
  }, [id, onChanged]);

 return store.getData(id);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM