最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Refreshing data fetched with a custom hook after closing a modal - Stack Overflow

programmeradmin4浏览0评论

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 ponent, 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 ponent:

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 ponent inside the parent ponent:

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.

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 ponent, 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 ponent:

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 ponent inside the parent ponent:

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.

Share Improve this question edited Jun 24, 2020 at 12:48 P4nd4b0b3r1n0 asked Jun 24, 2020 at 12:04 P4nd4b0b3r1n0P4nd4b0b3r1n0 2,4115 gold badges28 silver badges36 bronze badges 2
  • Okay as far as i understood the problem is that you want to refetch the data when the user closes the modal? am i right? – Anurag Hazra Commented Jun 24, 2020 at 13:07
  • Yes you're right. – P4nd4b0b3r1n0 Commented Jun 24, 2020 at 13:16
Add a ment  | 

4 Answers 4

Reset to default 4

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>
  );
};

I think you are missing , inside useEffect function thats why you getting X is not a function Error

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

I've implemented a solution by using a boolean but thought there must be a better way of doing this. The accepted solution of using an object is similar but it I do not like the exposed API or look of it refresh({}) or refresh(!resfreshSomething).

So instead I came up with the following solution:

const useGetFetch(url, token, user, parser) => {
  const [state, dispatch] = useReducer(fetchReducer, initialState)
  const [shouldRefetch, refetch] = useState({}); 
  const refresh = () => refetch({});

  // call upon useEffect (() => { ...

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

This way you can simple use refresh(); outside your hook.

import React from "react";

export const useFetch = ({ method, url, body }) => {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);

  const fetchAPI = async () => {
    try {
      setLoading(true);
      const response = await axios({ url, method, data: body });
      setData(response.data);
    } catch (error) {
      setError(error.response.data);
    } finally {
      setLoading(false);
    }
  };

  React.useEffect(() => {
    fetchAPI();
  }, []);

  return {
    loading,
    error,
    data,
    refetch: fetchAPI,
  };
};
发布评论

评论列表(0)

  1. 暂无评论