3

I've been finding myself writing a lot of code recently that looks like this:

const SomeComponent: React.FunctionComponent<SomeComponentProps> = props => {
  const { value } = props;
  const [thisValue, setThisValue] = React.useState(value);

  const handleChange = React.useCallback(e => {
    setThisValue(e.target.value);
  }, []);
 ....

Full example on CodePen.

Where I'm passing in an initialisation value from props, and then modifying that value within the component.

It does what I want, but I'm becoming suspicious of it.

Is this an antipattern?

dwjohnston
  • 11,163
  • 32
  • 99
  • 194

2 Answers2

3

Here is a good (slightly old) article that discusses initialising state with props:

https://medium.com/@justintulk/react-anti-patterns-props-in-initial-state-28687846cc2e

The React docs call this an anti-pattern:

"Using props to generate state in getInitialState often leads to duplication of “source of truth”, i.e. where the real data is. This is because getInitialState is only invoked when the component is first created."

From the docs, there is an exception to this which is:

"Only use this pattern if you intentionally want to ignore prop updates. In that case, it makes sense to rename the prop to be called initialColor or defaultColor. You can then force a component to “reset” its internal state by changing its key when necessary."

That being said, I also find myself doing this from time to time.

An example where I find it useful is components that allow users to edit complex state. You can initialise the state with props and the component internally handles changes to this state. If you want to reset the state when the props change, you have two options:

The first is to listen for changes and call setState like:

componentDidUpdate(prevProps, prevState) {
  if (prevProps.inputValue !== this.props.inputValue) {
    this.setState({ inputVal: this.props.inputValue })
  }
}

The recommended option is to give the component a key which depends on the props that should trigger a reset. Then when the props change, the component state will be re-initialised:

<MyComponent initProp={someValue} key={`key_${someValue}`/>

I would only recommend these options for complex state. In most examples the parent component could hold this state and the inner component could use a callback to to update the parent with changes.

Matt
  • 688
  • 5
  • 15
  • *"However, it’s **not** an anti-pattern if you make it clear that the **`prop` is only seed data** for the component’s internally-controlled state:"*, seems that the OP only intends it to just initialize `thisValue`. – Joseph D. Nov 01 '19 at 05:27
0

Here is an example of an anti pattern where you don't let the props lead and only set state on mount or constructor. I'm trying to make a form component that is stateful but will signal the consumer (parent) when the user submits it. The consumer has an option to reset the form:

function App() {
  const [initial, setInitial] = React.useState({
    name: 'init',
  });
  const submit = React.useCallback(form => {
    console.log('form is:',form);
  }, []);
  return (
    <div>
      <button onClick={() => setInitial({ name: 'reset' })}>
        reset
      </button>
      <Form initial={initial} submit={submit} />
    </div>
  );
}

function Form({ initial, submit }) {
  const [form, setForm] = React.useState(initial);
  const onChange = React.useCallback(e => {
    const name = e.target.value;
    setForm(form => ({ ...form, name }));
  }, []);
  const onSubmit = React.useCallback(
    e => {
      e.preventDefault();
      setForm(form => {
        submit(form);
        return form;
      });
    },
    [submit]
  );
  //witout this effect reset will not work
  React.useEffect(() => setForm(initial), [initial]);
  return (
    <form onSubmit={onSubmit}>
      <input
        type="text"
        value={form.name}
        onChange={onChange}
      />
      <button type="submit">Save</button>
    </form>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
HMR
  • 37,593
  • 24
  • 91
  • 160