How to create a component in a custom hook that handles state within the hook?

Viewed 1144

How can I create a component in a custom hook where the hook holds the state for the component?

My attempt basically does the right thing, but drag-and-drop is not working as expected. Instead the slider will only change the value on click. I think the problem is, that the useState hook gets called outside of the X definition. But how can we create a component in a hook then where I need to work with the state of that internal component within the rest of the hook?

enter image description here

https://codesandbox.io/s/material-demo-milu3?file=/demo.js:0-391

import React from "react";
import Slider from "@material-ui/core/Slider";

function useComp() {
  const [value, setValue] = React.useState(30);
  const X = () => <Slider value={value} onChange={(_, v) => setValue(v)} />;
  return { X, value };
}

export default function ContinuousSlider() {
  const { X, value } = useComp();
  return (
    <div>
      {value}
      <X />
    </div>
  );
}
1 Answers

Whenever the custom hook is called (on each render), a new Slider (Broken) component is created. Since a new component is created, the event handlers are recreated as well, and the drag is cancelled. You can solve this problem in two ways:

Wrap the component in useCallback(), and pass the value when rendering the component (sandbox):

  const Broken = useCallback(({ value }) => (
    <Slider value={value} onChange={changeHandler} />
  ), [changeHandler]);

  // usage
  <Broken value={broken} />

Render the component in the hook, and use the include it in the component (sandbox):

function useComp() {
  const [broken, setBroken] = React.useState(30);

  const changeHandler = useCallback((_, v) => setBroken(v), []);

  const slider = <Slider value={broken} onChange={changeHandler} />;

  return { slider, broken };
}

// usage

<div>
  Broken: {broken}
  {slider}
  OK: {ok}
  <Slider value={ok} onChange={(_, v) => setOk(v)} />
</div>
Related