Climooo
Climooo

Reputation: 295

Problem with React Hook "useCallback" and Pure components

Before React Hooks, I had a code like this:

interface State {
  f1: number;
  f2: number;
}

class OldClassParent extends React.Component<{}, State> {
  constructor(props: {}) {
    super(props);
    this.state = {
      f1: 0,
      f2: 0
    };
  }

  public render() {
    const { f1, f2 } = this.state;
    return (
      <>
        {/* Each pure component re-render only if its value has changed (OK) */}
        <MyPureCmp value={f1} name="f1" onChange={this.onChange} />
        <MyPureCmp value={f2} name="f2" onChange={this.onChange} />
      </>
    );
  }

  private onChange = (name: string, newValue: number) => {
    // @ts-ignore
    this.setState({
      [name]: newValue
    });
  }
}

Now, I try to have the same behavior with React-hooks. I made the following:

const NewHookParent: React.FC = () => {
  const [state, setState] = useState({
    f1: 0,
    f2: 0
  });

  // useCallback does not help me here
  const onChange = useCallback((name: string, newValue: number) => {
    setState({...state, [name]: newValue});
  }, [state, setState]);

  const { f1, f2 } = state;
  return (
    <>
      {/* Each pure component re-render when any value change 
        as the onChange handler is never the same (NOT OK) */}
      <MyPureCmp value={f1} name="f1" onChange={onChange} />
      <MyPureCmp value={f2} name="f2" onChange={onChange} />
    </>
  );
}

The problem is that I have lost the re-rendering optimization I had before. In this example, for simplicity, I used only two fields, but actually I may have any number of fields (and it can be dynamic, not known at compile time). What should I do ?

Upvotes: 0

Views: 428

Answers (1)

Ori Drori
Ori Drori

Reputation: 191976

The setState function produced by useState, accepts an updater function. The updater is called with the current state. Using this allows replaces passing the state to useCallback, and since the setState itself won't change, useCallback will return the same function all the time.

const onChange = useCallback((name: string, newValue: number) => {
  setState(state => ({...state, [name]: newValue}));
}, [setState]);

Upvotes: 1

Related Questions