How to change props to state in React Hooks?

Viewed 41043

In Simple react class component we used to change the props to state this way:

constructor(props) {
    super(props)

    this.state = {
      pitch: props.booking.pitch,
      email: props.booking.email,
      firstName: props.booking.firstName,
      arrivalDate: props.booking.arrivalDate
    }
} 

But I am not aware of how to do it in new feature as Hooks, but I am trying to do it this way.

const GenerateDescHook = ({ description: initialDesc }) => {
  const [description, setDescription] = useState(null)

useEffect(() => {
    setDescription(initialDesc)
  }, {})

 function handleChangeVariance(newVariance) {
    setDescription({
      ...description,
      template: {
        ...description.template,
        variance_name: newVariance,
      },
    })
  }

}

Basically, I just need to change description props, which coming from another parent component to turn to state. Pls, could you show me how to do it in new way as Hooks way?

4 Answers

The problem in your code is with {}. UseEffect hook expects and array, not an object. Use [initialDesc] instead.

This is how you reset component state when props change

const GenerateDescHook = ({ description: initialDesc }) => {
  const [description, setDescription] = useState(null)

  useEffect(() => {
    setDescription(initialDesc)
  }, [initialDesc]);
}

This is how you initialize component state to prop value on first render only

const GenerateDescHook = ({ description: initialDesc }) => {
  const [description, setDescription] = useState(initialDesc);
}

You can pass your initial state as first argument to useState like this:

const GenerateDescHook = ({ description: initialDesc }) => {
  const [description, setDescription] = useState(initialDesc)

  ...

The initial value of your state is the one passed into useState :

const GenerateDescHook = ({ description: initialDesc }) => {
  const [description, setDescription] = useState(initialDesc)

As the documentation states :

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Is the equivalent of :

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

It is difficult to achieve a state which depends on prop value and at the same avoid unnecessary re-renders.

Here is my attempt in creating such a hook:

/**
 * Replacement for useState which depends on prop value and avoids unnecessary re-renders.
 */
export function useStateBasedOnProps(propValue) {
    const [, update] = useState(false);

    const [state, setState] = useMemo(() => {
        const state = {};
        return [
            state,
            (value) => {
                if (value instanceof Function) {
                    state.value = value(state.value);
                } else {
                    state.value = value;
                }
                update((tick) => !tick);
            }
        ];
    }, [update]);

    if (state.prevPropValue !== propValue) {
        state.prevPropValue = propValue;
        state.value = propValue;
    }

    return [state.value, setState];
}

Here is a usage example:

function IncButton({initialValue}) {
    const [value, setValue] = useStateBasedOnProps(initialValue);
    const increment = useCallback(() => setValue((prev) => prev + 1), [setValue]);
    return <button onClick={increment}>Click to increment: {value}</button>;
}

Full example: https://gist.github.com/mdevils/b861610780300784292194f65886517a

Related