Home > Software engineering >  Problems using useRef / useImperativeHandle in mapping components
Problems using useRef / useImperativeHandle in mapping components

Time:10-02

I have a dashboard with different components. Everything is working with a separate start-button on each component, now I need to have a common start-button, and for accessing the children's subfunctions from a parent, I understand that in React you should use the useRef.(but its perhaps not correct, but I'm struggling to see another way). I would like to have the flexibility to choose which component to start from this "overall start-button"

I have a component list that i map through shown below.

return(
{ComponentsList.map((item) => {
      return (
       <Showcomponents
        {...item}
        key={item.name}
       />
)

This works fine, but I would like, as mentioned, to access a function called something like "buttonclick" in each of the children, so I tested this with a pressure-gauge component

The function "exposed" via the forwardRef and the useImparativeHandle

const ShowRadialGauge = forwardRef((props, ref) => {
 useImperativeHandle(ref, () => ({
  buttonclick() {
   setStart(!start);
  },
 }));
)

then in my dashboard I changed to :

const gaugepressure = useRef();

return(
  <div>
    <Button onClick={() => gaugepressure.current.buttonclick()}>
      Start processing
    </Button>
    <ShowRadialGauge ref={gaugepressure} />
  <div>
)

This works fine if I use the useRef from the dashboard and instead of mapping over the components, I add them manually.

I understand the useRef is not a props, but its almost what I want. I want to do something like this:

return(
{ComponentsList.map((item) => {
  return (
    <Showcomponents
      {...item}
      key={item.name}
      **ref={item.ref}**
   />
)

where the ref could be a part of my component array (as below) or a separate array.

export const ComponentsList = [
 {
  name: "Radial gauge",
  text: "showradialgauge",
  component: ShowRadialGauge,
  ref: "gaugepressure",
 },
 {
  name: "Heatmap",
  text: "heatmap",
  component: Heatmap,
  ref: "heatmapstart",
 },
]

Anyone have any suggestions, or perhaps do it another way?

CodePudding user response:

You are on the right track with a React ref in the parent to attach to a single child component. If you are mapping to multiple children though you'll need an array of React refs, one for each mapped child, and in the button handler in the parent you will iterate the array of refs to call the exposed imperative handle from each.

Example:

Parent

// Ref to hold all the component refs
const gaugesRef = React.useRef([]);

// set the ref's current value to be an array of mapped refs
// new refs to be created as needed
gaugesRef.current = componentsList.map(
  (_, i) => gaugesRef.current[i] ?? React.createRef()
);

const toggleAll = () => {
  // Iterate the array of refs and invoke the exposed handle
  gaugesRef.current.forEach((gauge) => gauge.current.toggleStart());
};

return (
  <div className="App">
    <button type="button" onClick={toggleAll}>
      Toggle All Gauges
    </button>
    {componentsList.map(({ name, component: Component, ...props }, i) => (
      <Component
        key={name}
        ref={gaugesRef.current[i]}
        name={name}
        {...props}
      />
    ))}
  </div>
);

Child

const ShowRadialGauge = React.forwardRef(({ name }, ref) => {
  const [start, setStart] = React.useState(false);

  const toggleStart = () => setStart((start) => !start);

  React.useImperativeHandle(ref, () => ({
    toggleStart
  }));

  return (....);
});

Edit problems-using-useref-useimperativehandle-in-mapping-components

The more correct/React way to accomplish this however is to Edit problems-using-useref-useimperativehandle-in-mapping-components (forked)

CodePudding user response:

First of all, why do you need refs to handle click when you can access it via onClick. The most common use case for refs in React is to reference a DOM element or store value that is persist between renders

My suggestion are these

  • First, try to make it simple by passing a function and then trigger it via onClick
  • Second if you really want to learn how to use imperativeHandle you can reference this video https://www.youtube.com/watch?v=zpEyAOkytkU.
  • Related