question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Hooks, useImperativeMethods and multiple refs

See original GitHub issue

I am not sure where to post this question, feel free to delete it if it doesn’t belong here.

Here is a small Accordion with AccordionPanels. It uses hooks to manage state and when some panel is opened we need to scroll to it, so we need a ref.

My question is: Is this how hooks are supposed to be used? Do you see some problems with this approach?

App.js

function App() {
    const panels = [
        'Panel 1',
        'Panel 2',
        'Panel 3'
    ];

    const [currentIndex, onClick, refs] = useAccordion(panels.length);

    return <Accordion>
        {panels.map((panel, index) => (
            <AccordionPanel
                ref={refs[index]}
                key={index}
                label={panel}
                isOpen={currentIndex === index}
                onClick={() => onClick(index)}
            />
        ))}
    </Accordion>;
}

Accordion.js

function useAccordion(panelsCount) {
    const [currentIndex, setCurrentIndex] = useState(undefined);
    let refs = {};

    for (let i = 0; i <= panelsCount; i++) {
        refs[i] = createRef();
    }

    useEffect(() => {
        if (currentIndex !== undefined) {
            refs[currentIndex].current.scrollTo();
        }
    }, [currentIndex]);

    function onClick(newIndex) {
        setCurrentIndex(currentIndex === newIndex ? undefined : newIndex);
    }

    return [currentIndex, onClick, refs];
}

const AccordionPanel = React.forwardRef((props, ref) => {
    const containerRef = useRef();

    useImperativeMethods(ref, () => ({
        scrollTo: () => console.info('do scrolling')
    }));

    return <div onClick={props.onClick} ref={containerRef}>
        {props.label}, {props.isOpen && 'open'}
    </div>;
});

function Accordion(props) {
    return <div>{props.children}</div>;
}

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:16 (7 by maintainers)

github_iconTop GitHub Comments

16reactions
sebmarkbagecommented, Dec 12, 2018

I guess there is currently no true idiomatic way of dealing with sets of refs. The issue is that the set itself grows or shrinks between updates but the true value changes in the commit phase as a side-effect. I think for most cases, you just want to change the set as a side-effect too. You can do that simply by mutating a persistent Map.

If you’re dealing with raw children and want a ref to each item in the list, you should use the React.Children helpers to create a list where each item has a unique key.

Then you can attach a ref to that which stores the result in a Map based on that key.

let refs = useRef(new Map()).current;

let childrenWithKeys = React.Children.toArray(children);
return childrenWithKeys.map(child =>
  React.cloneElement(child, {ref: inst => inst === null ? refs.delete(child.key) :  refs.set(child.key, inst)})
);

If it is a custom data structure like the example above then you can do the same thing but that data structure needs a key as part of it:

const panels = [
    {key: 'a', name: 'Panel 1' },
    {key: 'b', name: 'Panel 2' },
    {key: 'c', name: 'Panel 3' },
];

let refs = useRef(new Map()).current;

return panels.map(panel =>
  <AccordionPanel
      ref={inst => inst === null ? refs.delete(panel.key) : refs.set(panel.key, inst)}
      key={panel.key}
      label={panel.name}
      isOpen={currentKey === panel.key}
      onClick={() => onClick(panel.key)}
  />
);
11reactions
Jessidhiacommented, Nov 8, 2018

This doesn’t preserve old refs if panelsCount changes (they’ll be reattached after the current render commits), but you can use useMemo for this:

const refs = React.useMemo(() => 
  Array.from(
    { length: panelsCount },
    () => React.createRef()
  ),
  [panelsCount]
)
Read more comments on GitHub >

github_iconTop Results From Across the Web

How can I use multiple refs for an array of elements with hooks?
As you cannot use hooks inside loops, here is a solution in order to make it work when the array changes over the...
Read more >
Hooks API Reference - React
This page describes the APIs for the built-in Hooks in React. ... which is more suited for managing state objects that contain multiple...
Read more >
How to add multiple refs to one useRef() hook | Eliaslog.pw
The ref property on our JSX-div can take a function where the current element is passed in as the first argument. Our refs...
Read more >
Intro to React Hooks | CSS-Tricks
useImperativeMethods customizes the instance value that is exposed to parent components when using ref . As always, imperative code using refs ...
Read more >
Get Hooked on React! | Object Computing, Inc.
Hooks make it easier to reuse state logic between multiple components. In most cases this removes ... This is demonstrated in the "Ref...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found