user210757
user210757

Reputation: 7386

Use local state for forms when data is in redux

Use React for ephemeral state that doesn’t matter to the app globally and doesn’t mutate in complex ways. For example, a toggle in some UI element, a form input state. Use Redux for state that matters globally or is mutated in complex ways. For example, cached users, or a post draft.

My redux state is only representative of what has been saved to my backend database.

For my use case there is no need for any other part of the application to know about a record in an adding/editing state..

However, all my communication with my API is done through redux-thunks. I have found, getting data from redux into local state for editing is tricky.

The pattern I was trying to use:

const Container = () => {
    // use redux thunk to fetch from API
    useEffect(() => {
      dispatch(fetchThing(id));
    }, [dispatch, id]);

    // get from redux store
    const reduxThing = useSelector(getThing);

    const save = thing => {
      dispatch(saveThing(thing));
    };

    return (
      {!fetching && 
       <ThingForm 
        defaults={reduxThing} 
        submit={save}
      />}
    );
};

const ThingForm = ({defaults, submit}) => {
    const [values, setValues] = useState({ propA: '', propB: '', ...defaults});
    const handleChange = { /*standard handleChange code here*/ };

    return (
        <form onSubmit={() => submit(values)}>
          <input type="text" name="propA" value={values.propA} onChange={handleChange} />
          <input type="text" name="propB" value={values.propB} onChange={handleChange} />
        </form>
    );
};

How I understand it, ThingForm is unmounted/mounted based upon "fetching." However, it is a race condition whether or not the defaults get populated. Sometimes they do, sometimes they don't.

So obviously this isn't a great pattern.

Is there an established pattern for moving data into local state from redux for editing in a form?

Or should I just put my form data into redux? (I don't know if this would be any easier).


EDIT: I think this is essentially what I am fighting: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html But no recommendation really clearly fits. I am strictly using hooks. I could overwrite with useEffect on prop change, but seems kind of messy.

Upvotes: 2

Views: 1114

Answers (1)

Praneeth Paruchuri
Praneeth Paruchuri

Reputation: 1032

EDIT:

const Container = () => {
    // use redux thunk to fetch from API
    useEffect(() => {
      dispatch(fetchThing(id));
    }, [dispatch, id]);

    // get from redux store
    const reduxThing = useSelector(getThing);

    const save = thing => {
      dispatch(saveThing(thing));
    };

    return (
      {!fetching && 
       <ThingForm 
        defaults={reduxThing} 
        submit={save}
      />}
    );
};

const ThingForm = ({defaults, submit}) => {
    const [values, setValues] = useState({ propA: '', propB: '', ...defaults});
    const handleChange = { /*standard handleChange code here*/ };


   useEffect(() => {
     setValues({...values, ...defaults})
   }, [defaults]);

    const submitValues = (e) => {
      e.preventDefault();
      submit(values)
    }

    return (
        <form onSubmit={submitValues}>
          <input type="text" name="propA" value={values.propA} onChange={handleChange} />
          <input type="text" name="propB" value={values.propB} onChange={handleChange} />
        </form>
    );
};

What you are doing is the right way, there's no reason why you should put the form data in the redux store. Like you said, "there is no need for any other part of the application to know about a record in an adding/editing state" And that's correct.

The only problem you have is here:

{!fetching && 
       <ThingForm 
        defaults={reduxThing} 
        submit={save}
      />}

Assuming fetching is true on every dispatch:

Instead of trying to hide the component (unmounting essentially), you should maybe use a spinner that overlays the page?

I don't know the rest of your code to comment on a better approach.

You also don't have to add dispatch to the dependency array

useEffect(() => {
      dispatch(fetchThing(id));
    }, [id]);

From the react docs:

React guarantees that dispatch function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.

Upvotes: 2

Related Questions