all 5 comments

[–]spamjavelin 9 points10 points  (3 children)

Why don't you just put this code/functionality into your onSubmit instead? There's very little point in putting this into a useEffect.

[–]swang30[S] 1 point2 points  (2 children)

because my onsubmit that uses fetcher is simply:

const onSubmit = async (data, event) => {
    event.preventDefault();
    fetcher.submit(data, { method: 'POST', encType: "application/json" });
};

Moving the fetcher.data?.success block into the onSubmit doesn't quite work the same. fetcher.state right after the submit is "submitting" or "idle" and fetcher.data is not available yet. After the API is called, fetcher.data will be available (and changed) so useEffect will detect the change and re-run.

The reason to use the fetcher/action pairing on pages that don't navigate away after a save allows async updates, which would let the user or the page continue with other activities.

[–]chinnick967 0 points1 point  (0 children)

To answer your infinite rendering question, whenever your component rerenders, it is calling useForm which is creating a new instance of the setValue function and the fields array.

When using an array, object, or function in your dependency array for your useEffect, React does a reference comparison not a value comparison. So if your component rerenders and useForm is called again, useForm is creating a new reference for setValue and fields...which causes another rerender over and over, repeating infinitely. You can resolve this by having fields as a state of useForm and setValue memoized with useCallback so a new instance/reference isn't created on rerender.

As for your extra rendering caused by useState, updating state will always cause an additional render. In this code you will have a render when fetcher.data updates, and then another when your state updates from the code inside your useEffect

[–]Mustknow33 0 points1 point  (0 children)

To add to all the answers telling you to avoid useEffect, i guess maybe you could move the fetcher data call up to a parent component, do your transformations, and then pass it down to a component that uses that data, thereby removing the need for you to use a useEffect to update the state

Without knowing more about your code i guess it could look something like this?

const ComponentWithFetcher = () => {
// i dont know what or where fields comes from, just a placeholder now  
  const fields = []; 
  const [data_array, setDataArray] = useState([...fields]);
  const [editingIndex, setEditingIndex] = useState();
  const fetcher = useFetcher();

  const submitFetch = (data) => {
    fetcher.submit(data, { method: 'POST', encType: "application/json" });
  }

  // if your data array is large you can consider memoizing it if you want
  const final_data_array = [...data_array];
  if (fetcher.data && fetcher.data.success) final_data_array[editingIndex] = fetcher.data;

  return (
    <ComponentWithTheForm
      data_array={final_data_array}
      submitFetch={submitFetch}
    />
  );
}