'Passing selectors and action creators as props to a generic component

Let’s say I have a custom Input component (similar to an HTML <input/>) which can be used to represent any value in the Redux store. The API I’m currently using is that Input takes two props value and onChange and is then used as follows:

const ParentComponent = () => {
  const dispatch = useDispatch();
  const valueA = useSelector(selectValueA);
  const valueB = useSelector(selectValueB);
  const valueC = useSelector(selectValueC);

  const handleChangeA = newValue => dispatch(valueAChanged(newValue));
  const handleChangeB = newValue => dispatch(valueBChanged(newValue));
  const handleChangeC = newValue => dispatch(valueCChanged(newValue));

  return (
    <div>
      <Input value={valueA} onChange={handleChangeA}/>
      <Input value={valueB} onChange={handleChangeB}/>
      <Input value={valueB} onChange={handleChangeC}/>
    </div>
}

It works fine but the issue is that whenever valueA changes, then the whole ParentComponent rerenders instead of just the corresponding Input, which causes performance issues and goes against the best practice "have components select only the data they need from the store".

A solution would be to make wrapper components:

const ParentComponent = () => {
  return (
    <div>
      <InputA/>
      <InputB/>
      <InputC/>
    </div>
  );
};

const InputA = () => {
  const dispatch = useDispatch();
  const value = useSelector(selectValueA)
  const handleChange = newValue => dispatch(valueAChanged(newValue));
  return <Input value={value} onChange={handleChange}/>;
};

const InputB = () => {
  const dispatch = useDispatch();
  const value = useSelector(selectValueB)
  const handleChange = newValue => dispatch(valueBChanged(newValue));
  return <Input value={value} onChange={handleChange}/>;
};

const InputC = () => {
  const dispatch = useDispatch();
  const value = useSelector(selectValueC)
  const handleChange = newValue => dispatch(valueCChanged(newValue));
  return <Input value={value} onChange={handleChange}/>;
};
…

It would fix the problem, but as you can see it is a whole lot of boilerplate that I’d rather avoid.

So I was wondering if I could instead directly pass the selector itself and the action creator as props:

const ParentComponent = () => {
  return (
    <div>
      <ConnectedInput selector={selectValueA} actionCreator={valueAChanged}/>
      <ConnectedInput selector={selectValueB} actionCreator={valueBChanged}/>
      <ConnectedInput selector={selectValueC} actionCreator={valueCChanged}/>
    </div>
  );
};

const ConnectedInput = ({selector, actionCreator}) => {
  const dispatch = useDispatch();
  const value = useSelector(selector);
  const handleChange = newValue => dispatch(actionCreator(newValue));
  return <Input value={value} onChange={handleChange}/>;
};

I think it looks very neat and I can’t see anything wrong with it (although I haven’t tried it yet), but it also seems like a pretty unusual pattern, at least I couldn’t find any similar code.

So is there any problem with it that I’m not seeing? Or is there any other solution to connect generic components to the store?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source