27

Lets say I'm doing a simple CRUD app in react. My functional component is basically just the form.

  • In the CREATE case I pass in an empty object via props
  • In the UPDATE case I pass in an object with the values via props (I got the data in the parent component with an API call)

I looks like this:

const MyForm = (props) => {

 const [myValues, setMyValues] = useState(props.myValues);
 const [errors, setErrors] = useState(0);
 (...)
}

In the UPDATE case, I run (of course) into the issue that props.myValues is still empty when the component is mounted, and not set again (updated) when the api call from the parent component has finished thus leaving the form values empty.

Using a class component, I'd solve that with getDerivedStateFromProps(). Is there anything like that in a functional component? Or am I doing this wrong from the beginning? Thanks for any advice!

Thread Pitt
  • 716
  • 1
  • 6
  • 20

3 Answers3

57

Yes, you can of course use useEffect.

In your case the code should look like this:

const MyForm = (props) => {

 const [myValues, setMyValues] = useState(props.myValues);
 const [errors, setErrors] = useState(0);

 useEffect(() => {
   setMyValues(props.myValues);
 }, [props.myValues]);

}
Vishal
  • 6,238
  • 10
  • 82
  • 158
4

Another way to do this is the Fully uncontrolled component with a key strategy. If you go that route, your component stays the same:

const MyForm = (props) => {

 const [myValues, setMyValues] = useState(props.myValues);
 const [errors, setErrors] = useState(0);
 (...)
}

But there is one extra piece to its usage:

<MyForm myValues={myValues} key={myValues} />

Using the key prop like this means a new instance of this component will be created if myValues changes, meaning the default value you have provided to your internal myValues state will be reset to whatever the new values are.

This avoids the double render you get if you useEffect, but comes at the cost of a new component instance. I don't find this to be a problem if you do it at the individual input level, but you may not want to rebuild your whole form.

Rob Allsopp
  • 3,309
  • 5
  • 34
  • 53
0

When you write:

const [myValues, setMyValues] = useState(props.myValues);

then myValues props is only used for initialize the myValues state. Dan Abramov relates to this point in his latest article.
I would suggest:
1. Rename myValues prop to initialValues
2. Call the api after submit, and change your state according to the result.

It will look like:

const MyForm = (props) => {
  const { initialValues, onSubmit } = this.props
  const [myValues, setMyValues] = useState(initialValues);
  ...
  const handleSubmit = () => {
     // Assume that onSubmit is a call to Fetch API
     onSubmit(myValues).then(response => response.json())
                       .then(updatedValues => setMyValues(updatedValues)) 
  }
} 
Benefits
  • 78
  • 6