简体   繁体   中英

React clone element to modify a child component and keep ref in a functional component

I used to have refs in my component when rendering, and it worked:

// props.children is ReactElement<HTMLDivElement>[]
const [childRefs] = useState<RefObject<any>[]>(props.children.map(() => createRef()));

// working code, all the variables (props, childRefs etc) are defined earlier in scope
return <div {...props}>
    {
        props.children.map((c, i) => <div key={i} ref={childRefs[i]}>{c}</div >)
    }
</div>

Basically I'm using the refs to imperatively directly set some transforms to style as mouse moves on JS mousemove event.

However, I now need to inject some CSS class into the passed component automatically. I've created a component (named Layer ) that takes the child element, clones it, sets the CSS class, and returns it:

function Layer(props:LayerProps){
    const elem = cloneElement(props.children, {...props.children.props,
        className: styles['layer']
    });
    return elem;
}

I've updated the main component like this too:

return <div {...props}>
    {
        props.children.map((c, i) => <Layer key={i} ref={childRefs[i]}>{c}</Layer>)
    }
</div>

However now my ref s aren't passed now, understandibly, as the Layer functional component can't have a ref (as it's a function). When I try to set the ref to Layer it can't, and have this error (understandably):

(property) ref: React.RefObject<any>
Type '{ children: ReactElement<HTMLDivElement, string | JSXElementConstructor<any>>; key: number; ref: RefObject<any>; }' is not assignable to type 'IntrinsicAttributes & LayerProps'.
  Property 'ref' does not exist on type 'IntrinsicAttributes & LayerProps'.ts(2322)

If I try to forward the ref using forwardRef it doesn't have anything to set that ref to as I'm just modifying the passed child element and returning it, not returning a new element like <div>...</div> that I could forward ref to like <div ref={forwardedRef}>...</div> .

How can I modify the CSS class and keep a ref to the object? I know how to do each one (if I just need to add class I cloneElement , if I just need to ref it I use forwardRef and pass it to the child component in JSX) yet I couldn't figure out being able to do both at the same time.

How can I do it?

Okay, after a bit digging and experimenting I've realized I can give ref "prop" (which isn't technically a real prop, but anyway) in cloneElement just like any prop.

I've ended up forwarding ref to the functional component, and provided ref as a prop to the newly cloned element, and it worked.

Yet, the TypeScript definitions are incorrectly flagging ref property as non-existent while it works perfectly. I needed it to cast the props to any to silence the linter error though:


const Layer = forwardRef((props:LayerProps, ref:any) => {
    const elem = cloneElement(props.children, {...props.children.props,
        className: `${props.children.props.className ?? ''} ${styles['layer']}`,
        ref: ref
    } as any); // as any fixes ref complaining
    return elem;
});

And in many component:

 return <div {...props}>
        {
            props.children.map((c, i) => <Layer key={i} ref={childRefs[i]}>{c}</Layer>)
        }
    </div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM