3

I'm trying to implement the simple pattern of:

  • GET list
  • enter new item, save
  • make POST request
  • make new GET request for updated data

Starting with the code from here,

const useApi = () => {
  const [data, setData] = useState({ hits: [] });
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );

  useEffect(() => {
    const fetchData = async () => {
        const result = await axios(url);
        setData(result.data);
    };

    fetchData();
  }, [url]);

  return [{ data }, setUrl];
}

it's easy enough to make the initial fetch on page mount

  const [{ data }, doFetch] = useDataApi(
    'https://hn.algolia.com/api/v1/search?query=redux',
    { hits: [] },
  );

but what I don't understand is how to do a 'refresh' of the data. Since there's no stateful variable that determines if the hook should re-run, how can I manually invoke the hook on a button click?

3 Answers 3

3

Use a separate variable like a simple count mechanism:

const [count, setCount] = useState(0);

<button onClick={() => setCount(count + 1)} />

Then use 'count' as the stateful variable, as well as the url (if the user searched for something different):

useEffect(() => {
  const fetchData = async () => {
    const result = await axios(url);
    setData(result.data);
  };

  fetchData();
}, [url, count]);
Sign up to request clarification or add additional context in comments.

5 Comments

This works but feels anti-patterny. Is this the correct way to do this?
Well it's an odd case in that the data should be updated on the button click. You can't be guaranteed that if you passed the url in from the button for example, that it would be different from the previous click (someone clicking twice). So this is a compromise I suppose, not necessarily anti-pattern.
I feel like it's a pretty common use case--say you just want a "refresh" button.
For anybody else looking at this problem, take a look at the use-hooks/react-hooks-axios source code. It uses the same approach but keeps the count internal and exposes a reFetch function that modifies it.
Nice one @ahota
2

Here is a different approach. Maybe there is a better way but in this situation, it seems working.

You can return fetchData from the useDataApi and then use it in the onClick handler and look for the URL change by the help of useRef. In order to do that you should move fetchData outside of the useEffect and wrap it with useCallback

  ...
  const fetchData = useCallback(
    async () => {
      setIsError(false);
      setIsLoading(true);

      try {
        const result = await axios(url);
        setData(result.data);
      } catch (error) {
        setIsError(true);
      }

      setIsLoading(false);
    },
    [url],
  );


  useEffect(() => {
    fetchData();
  }, [url, fetchData]); // add fetchData here as a second dependency

  return [{ data, isLoading, isError }, setUrl, fetchData]; // add fetchData here as the third returned variable.
};

Then in your App you can use refs to check if the URL is not changed:

function App() {
  const [query, setQuery] = useState('redux');
  const latestQuery = useRef(query); // define the ref
  const [{ data, isLoading, isError }, doFetch, fetchData] = useDataApi(
    'https://hn.algolia.com/api/v1/search?query=redux',
    { hits: [] },
  );

  return (
    <Fragment>
      <form
        onSubmit={event => {
          // check the query change
          if (latestQuery.current === query) {
            fetchData();
          }
          doFetch(
            `https://hn.algolia.com/api/v1/search?query=${query}`,
          );

          // set current query to the ref value
          latestQuery.current = query;

          event.preventDefault();
        }}
      >
      .....

I'm not quite sure this is a better solution since I'm still learning the hooks myself, too :)

Comments

0

I think the flow you describe could be simplified if, after making your initial api request, an action is dispatched to fetch the latest data that's available. Subsequently, the request should be made every time the component mounts, that way your component is not responsible for making sure it has the most updated data. This flow would look something like:

1) GET list

2) enter new item, save

3) make POST request

4) have an operation (outside of the consuming component) handle the response, and if it's successful, dispatch an action to refire the initial GET list call and if not, a call to handle the error.

Comments

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.