简体   繁体   中英

How to return a function from React Hooks that need access to the state?

I'm building a custom hook to manage the data coming from a fetch/axios/promise/whateveryouwant function, this hooks allow me to update the data whenever I want using an update function that I return at the end of my hook.

So for now I have two states inside my hooks:

const [data, setData] = useState<T>(defaultValue); // the data coming from the fetch, T is defined by the user of the hook
const [query, setQuery] = useState<Array<{ key: string; value: string }>>([]); // array of query option for the URL

I wanted to implement a dynamic way to add query string to the URL, so I used the query state to have an array of object representing what I wanted but I cannot access the value of the query inside of the update function when calling from outside. Here is the hooks, I highlited the important part:

export default function useFetchV2<T>(
  defaultValue: T,
  getData: (queryString?: string) => Promise<T>
): {
  update: () => void;
  data: T;
  updateQueryValue: (key: string, value: string) => void; // update, data, updateQueryValue are the return value of the hook.
} {
  const [data, setData] = useState<T>(defaultValue);
  const [query, setQuery] = useState<Array<{ key: string; value: string }>>([]); // THE STATE I WANT TO ACCESS

  console.log(query, data); // Here the state is updating well and everything seems fine

  const update = useCallback((): void => {
    let queryString;

    console.log(query, data);

   // But here is the problem, query and data are both at their default value. The state inside the hooks is correct but not here.

    if (query.length > 0) { // query.length always at 0
      queryString = _.reduce(
        query,
        (acc, el) => {
          return `${acc}${el.key}=${el.value.toString()}`;
        },
        '?'
      );
      console.log(queryString);
    }
    getData(queryString).then(res => setData(res));
  }, [data, getData, query]);

  const updateQueryValue = useCallback(
    (key: string, value: string): void => {
      const index = query.findIndex(el => el.key === key);
      if (index !== -1) {
        if (!value) {
          const toto = [...query];
          _.pullAt(toto, index);
          setQuery(toto);
        } else {
          setQuery(prev =>
            prev.map(el => {
              if (el.key === key) {
                return { key, value };
              }
              return el;
            })
          );
        }
      } else {
        console.log(key, value); // everything is logging well here, good key and value
        setQuery([...query, { key, value }]); // setQuery correctly update the state
      }
      update();
    },
    [query, update]
  );

  useEffect(() => {
    update();
  }, []);

  return { update, data, updateQueryValue };
}

It might be the way I'm exporting the function, I'm still not used to the scope.

I called updateQueryValue from a component. The function is called, state is changed, but the update function can't see the difference.

The code is calling setQuery and then immediately calling update and expecting query to be updated value, but it will always be the current value of query . Getting that to happen is the use case of useEffect .

  } else {
    console.log(key, value); // everything is logging well here, good key and value
    setQuery([...query, { key, value }]); // setQuery correctly update the state
  }
  
  // This will *always* see the current value of `query`, not the one that
  // was just created in the `else` block above.
  update();
  1. It sounds like the code should run update after query is updated, which is exactly what useEffect is for.
const updateQueryValue = useCallback(
    (key: string, value: string): void => {
      const index = query.findIndex(el => el.key === key);
      if (index !== -1) {
        if (!value) {
          const toto = [...query];
          _.pullAt(toto, index);
          setQuery(toto);
        } else {
          setQuery(prev =>
            prev.map(el => {
              if (el.key === key) {
                return { key, value };
              }
              return el;
            })
          );
        }
      } else {
        setQuery([...query, { key, value }]); // setQuery correctly update the state
      }

      // This call isn't needed, it will always "see" the old value of
      // `query`
      // update();
    },
    [query, update]
  );

  // Call `update` once when this custom hook is first called as well as
  // whenever `query` changes.
  useEffect(() => {
    update();
  }, [query]); // <-- this is new, add `query` as a dependency
  1. useCallback is only needed if something is relying on the identity of the function to make decisions; eg if a component receives the function as a prop, it checks whether the prop changes to decide whether to re-render. update in this case does not need a useCallback and might make this hook slightly harder to debug.
function update() {
    let queryString;
    if (query.length > 0) { // query.length always at 0
      queryString = _.reduce(
        query,
        (acc, el) => {
          return `${acc}${el.key}=${el.value.toString()}`;
        },
        '?'
      );
      console.log(queryString);
    }
    getData(queryString).then(res => setData(res));
  });

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