javascriptreactjsreact-hooksfetchuse-effect

Refreshing data fetched with a custom hook after closing a modal


I'm using a hook to fetch data in my app. The hook looks something like this:

const initialState = {
  response: null,
  loading: true,
  error: null
}
const useGetFetch(url, token, user, parser) => {
  const [state, dispatch] = useReducer(fetchReducer, initialState)

  useEffect (() => {
    if(user){
      fetch(...)
      .then((data)=> data.json())
      .then(dispatch(...)) // setting the state here
      .catch((error) => dispatch(...)) // set error state
    }
  }, [user])

 return [state.response, state.loading, state.error]
}

Specifically, I'm using this hook inside a form component, to fetch some data to use as options inside a select element.

However, I need to be able to let the user to add new options on the fly. To facilitate this, I've created a button next to the select element. If the user presses this button, a modal will pop up with another form that makes it possible create a new option. This new option is sent to the server via a post request and if successful the modal closes.

The problem I'm facing is that after the modal closes, I have no way of updating the initial data fetched with my custom data fetching hook, unless I refresh the page. Is there a way to make this happen somehow without triggering a page refresh?

My initial hunch was to use a callback function and pass it to the the useGetFetch and bind it to the useEffect hook. I could then pass it to the form in the modal and call the function after a successful submission, but this hasn't worked.

Another option I'm looking is the useCallback hook, but I don't currently understand it well enough to know whether or not it could be helpful.

EDIT

Specifically what I did was create a trigger function outside the parent component:

const trigger = () => console.log("click")

Bound it to the fetch hook:

const useGetFetch(url, token, user, parser, trigger) => {
  const [state, dispatch] = useReducer(fetchReducer, initialState)

  useEffect (() => {
    if(user){
      fetch(...)
      .then((data)=> data.json())
      .then(dispatch(...)) // setting the state here
      .catch((error) => dispatch(...)) // set error state
    }
  }, [user, trigger])

 return [state.response, state.loading, state.error]
}

And passed it to the child component inside the parent component:

const trigger = () => console.log("click")
const Parent = () => {
  // load a bunch of stuff
  // ...
    const { show, toggle } = useModal()

    const [optionsData, Loading, Error] = useGetFetch(
      optionsUrl,
      token,
      user,
      parser,
      trigger
  )
  // ...
  return (
    <div>
      {// ...}
      <button
              onClick={(e) => {
                e.preventDefault()
                toggle()
              }}
            >
              Add
            </button>
            <Modal show={show} toggle={toggle}>
              <ModalForm
                show={show}
                toggle={toggle}
                trigger={trigger}
              ></ModalForm>
            </Modal>
           {// ...}
    <div>
  )
}

Inside ModalForm trigger is called after a successful post request has been performed. After closing the modal, the data is not updated. My guess was that calling trigger inside ModalForm would have triggered useEffect inside of the useGetFetch but it doesn't seem to work like that.


Solution

  • If if understood your question clearly i think what you are trying to do is force a refetch whenever user closes the modal.

    You can do that by triggering a force rerender inside the useGetFetch hook like below:

    const useGetFetch(url, token, user, parser) => {
      const [state, dispatch] = useReducer(fetchReducer, initialState)
      // calling refetch will force a rerender on the hook
      const [shouldRefetch, refetch] = useState({}); 
    
      useEffect (() => {
        if(user){
          fetch(...)
          .then((data)=> data.json())
          .then(dispatch(...)) // setting the state here
          .catch((error) => dispatch(...)) // set error state
        }
      }, [user, shouldRefetch]); // note the dep array
    
     // returning the refetch functions so we can call the refetch() on modal close.
     return [state.response, state.loading, state.error, refetch];
    }
    

    And thats it for the useGetFetch, now you will use the hook like so :-

    const Parent = () => {
      const { show, toggle } = useModal();
    
      // notice the refetch method
      const [optionsData, Loading, Error, refetch] = useGetFetch(
        optionsUrl,
        token,
        user,
        parser
      );
    
      return (
        <div>
          <button
            onClick={e => {
              e.preventDefault();
              toggle();
            }}
          >
            Add
          </button>
          <Modal show={show} toggle={toggle}>
            <ModalForm
              show={show}
              toggle={toggle}
              trigger={() => {
                // probably onClose would be better name instead of `trigger`
    
                // calling the refetch method with empty object, this will cause rerender
                refetch({});
              }}
            ></ModalForm>
          </Modal>
        </div>
      );
    };