charlie
charlie

Reputation: 165

How to pass ref to children of children using array of refs React?

I'm trying to pass multiple times refs to children of children but it is not working. I have a functional component called AvatarBuilder that uses the Avatars component. This component renders a list of Avatar components. The idea is to have in AvatarBuilder references to each of the Avatar component.

Here is the code snippet summarized:

const AvatarBuilder = props => {
    ...
    // in this dummy example i would have 5 avatars
    const URLS=[1,2,3,4,5];
    const refs = URLS.map(item => ({ ref: React.createRef() }));
    return (
        <>
            <Avatars
                ref={refs}
                urlList={URLS}
            />
        </>
    );

const Avatars = React.forwardRef((props, ref) => {
    let urlList = props.urlList.map((url, index) => {
        return (
            <Avatar
                ref={ref[index].ref}
                url={url}
            />
        )
    })
    return (
        <ul className="Avatars" >
            {urlList}
        </ul>
    )
});

const Avatar = React.forwardRef((props, ref) => {
    return (
        <li className="Avatar">
            <img
                src={props.url}
                ref={ref}
            />
        </li>
    )
});

I get the following warning and the refs array is not updated when all the components are mounted.

index.js:1 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `ForwardRef`.
    in h (at Avatar.js:11)
    in li (at Avatar.js:10)
    in ForwardRef (at Avatars.js:10)
    in ul (at Avatars.js:24)
    in ForwardRef (at AvatarBuilder.js:189)
    in AvatarBuilder (at App.js:19)
    in div (at App.js:14)
    in App (at src/index.js:9)
    in StrictMode (at src/index.js:8)

Any idea how should this be fixed? Thanks!

Upvotes: 0

Views: 5628

Answers (2)

Shubham Khatri
Shubham Khatri

Reputation: 281686

For a functional component, you must use useRef and not React.createRef since a new instance of refs will be created on in render.

If you use React.createRef, then make use of useMemo to memoize the refs

const AvatarBuilder = props => {
    // in this dummy example i would have 5 avatars
    const URLS=[1,2,3,4,5];
    const refs = React.useMemo(() =>URLS.map(item => ({ ref: React.createRef() })), []); // create refs only once
    React.useEffect(() => {
       console.log(refs);
    },[])
    return (
            <Avatars
                ref={refs}
                urlList={URLS}
            />
    );
}
const Avatars = React.forwardRef((props, ref) => {
    let urlList = props.urlList.map((url, index) => {
        return (
            <Avatar
                ref={ref[index].ref}
                url={url}
            />
        )
    })
    return (
        <ul className="Avatars" >
            {urlList}
        </ul>
    )
});

const Avatar = React.forwardRef((props, ref) => {
    return (
        <li className="Avatar">
            <img
                src={props.url}
                ref={ref}
            />
        </li>
    )
});

ReactDOM.render(<AvatarBuilder/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app"/>

Upvotes: 2

kyun
kyun

Reputation: 10264

Try this one.

const AvatarBuilder = props => {
    ...
    // in this dummy example i would have 5 avatars
    const URLS=[1,2,3,4,5];
    const refs = URLS.map(item => React.createRef());
    return (
        <>
            <Avatars
                refs={refs}
                urlList={URLS}
            />
        </>
    );

// you don't need to make it with `fowardRef()`
const Avatars = (props) => {
    const {refs} = props;
    let urlList = props.urlList.map((url, index) => {
        console.log(url, index, typeof (index), ref);
        return (
            <Avatar
                ref={refs[index]}
                url={url}
            />
        )
    })
    return (
        <ul className="Avatars" >
            {urlList}
        </ul>
    )
};

const Avatar = React.forwardRef((props, ref) => {
    return (
        <li className="Avatar">
            <img
                src={props.url}
                ref={ref}
            />
        </li>
    )
});

Upvotes: 1

Related Questions