React forwardRef - access ref within component, and in parent - javascript

I need to access the ref to a textarea inside a component. Within the component, its easy enough:
const MyComponent = () => {
const inputRef = useRef();
return <textarea ref={inputRef} />
}
Now the ref is available within MyComponent and I can use it for some internal logic.
There are cases where I need to access the ref from the parent component as well. In that case, I can use forwardRef:
const MyComponent = React.forwardRef((props, ref) => {
return <textarea ref={ref} />
})
// In some parent
const MyParent = () => {
const inputRefFromParent = useRef();
return <MyComponent ref={inputRefFromParent} />
}
Now I can access to ref of the textarea from the parent component, and use it for logic within the parent component.
I find myself in a situation where I need to do some internal logic with the ref within MyComponent, but I may also need to get that ref from MyParent. How can I do this?

You can keep a ref in the MyComponent and expose what you would need in the parent component using useImperativeHandle hook using the ref passed from the MyParent.
Try like below. It exposes the focus method in the textarea to parent. And you can do any other internal things with the access to textAreaRef.
import { useRef, forwardRef, useImperativeHandle } from "react";
const MyComponent = forwardRef((props, ref) => {
const textAreaRef = useRef();
// all the functions or values you can expose here
useImperativeHandle(ref, () => ({
focus: () => {
textAreaRef.current.focus();
}
}));
const internalFunction = () => {
// access textAreaRef
};
return <textarea ref={textAreaRef} />;
});
// In some parent
const MyParent = () => {
const inputRefFromParent = useRef();
// you can call inputRefFromParent.current.focus(); in this compoenent
return <MyComponent ref={inputRefFromParent} />;
};

In addition to Amila's answer, I found another way to do it, by using a ref callback:
const MyComponent = React.forwardRef((props, parentRef) => {
const localRef = useRef();
return <textarea ref={ref => {
parentRef.current = ref;
localRef.current = ref;
}} />
})
So the callback ref keeps finer grain control of the ref to the textarea, and simply assigns its value to both the local ref and the parent ref.

You could do also the following:
const MyComponent = React.forwardRef((props, externalRef) => {
const internalRef = useRef<HTMLElement>();
const ref = useMemo(
() => externalRef || internalRef,
[externalRef, internalRef]
) as React.MutableRefObject<HTMLElement>;
return <textarea ref={ref} />
})

Related

How to use React.forwardRef?

I want to pass ref to a child component with forwardRef, but current for the given ref is always null. Why?
Consider this simple example:
// Details.jsx
import { useRef, forwardRef } from 'react';
const Details = () => {
const usernameRef = useRef(null);
const InputClipboardButton = React.forwardRef((props, ref) => (
<ClipboardButton targetInputRef={ref} />
));
return (
<div>
<input type="text" ref={usernameRef} />
<InputClipboardButton ref={usernameRef} />
</div>
);
};
// ClipboardButton.jsx
const ClipboardButton = ({ targetInputRef }) => {
const copyToClipboard = () => {
console.log(targetInputRef);
}
<button onClick={copyToClipboard}>
Copy
</button>
};
When using forwardRef you must be mindful of the order of the parameters passed (props and ref):
You can define the component like so:
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
Note how forwardRef takes two parameters:
The props
The ref being forwarded
You may also destructure the props value, as well as call the ref property a different name:
const FancyButton = React.forwardRef(({
btnType,
children
}, forwardedRef) => (
<button ref={forwardedRef} type={btnType} className="FancyButton">
{children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref} btnType="button">Click me!</FancyButton>;
Example adapted from React Docs
Clipboard Button is a component, so you must use forwardRef in this component as well
const ClipboardButton = forwardRef((props,ref) => {
const copyToClipboard = () => {
console.log(ref);
}
<button onClick={copyToClipboard}>
Copy
</button>
});

React - change value of parent variable from child

I know this could be a noob question but I'm learning React for a few months and now I'm stucked with this problem. I got this code over here:
import React, { useCallback, useEffect, useRef, useState } from 'react'
import ReactTags from 'react-tag-autocomplete'
const TagsHandler = ({ tagPlaceholder, suggestions }) => {
const [tags, setTags] = useState([])
const reactTags = useRef()
const onDelete = useCallback(
(tagIndex) => {
setTags(tags.filter((_, i) => i !== tagIndex))
},
[tags]
)
const onAddition = useCallback(
(newTag) => {
setTags([...tags, newTag])
},
[tags]
)
useEffect(() => {
suggestions.map((suggestion) => {
suggestion.disabled = tags.some((tag) => tag.id === suggestion.id)
})
}, [tags])
return (
<ReactTags
ref={reactTags}
tags={tags}
suggestions={suggestions}
onDelete={onDelete}
onAddition={onAddition}
placeholderText={tagPlaceholder}
/>
)
}
export default TagsHandler
Which implements a tag list inside my parent component. This parent component has a bool value which enables a save button. I should enable this button whenever a user adds or removes a tag to the list. My question is: how can I handle this bool from the child component? I've read about Redux but I'd like to avoid using it. I was thinking about a SetState function or a callback but I can't figure out the syntax.
Any help would be really appreciated, thanks :)
You can simply create a function in your parent component: toggleButton, and pass the function to your child component.
function Parent = (props) => {
const [isToggle, setIsToggle] = useState(false);
const toggleButton = () => {
setIsToggle(!isToggle)
}
return <Child toggled={isToggle} toggle={toggleButton} />
}
So the general approach is as follows:
In the Parent.jsx:
const [childrenActive, setChildrenActive] = useState(false)
// later in the render function
<Children setIsActive={(newActive) => setChildrenActive(newActive)} />
In the Children.jsx:
const Children = ({ setIsActive }) => {
return <button onClick={() => setIsActive(true)}>Click me</button>
}
So, as you have guessed, we pass a callback function. I would avoid passing setState directly because it makes your component less flexible.
In the parent component, the function that is responsible for changing that bool variable, you pass as a prop to the child component. At the child component you get that props and you can update if you want.

Create the same ref functionality in React Function component as in React Class component

I would like to create the exact same behaviour in function component ref as there is in class component ref.
Heres an example:
const AnotherComponent = () => {
const DoSomething () => console.log(something);
return <div> There is a content </div>;
}
const MyComponent () => {
let newRef = useRef();
useEffect(() => {
newRef.current.DoSomething();
}, []);
return <AnotherComponent ref={newRef} />;
}
I know about forwardRef, but from what I understand it allows you to bind the ref to a specific input to get its events, but not to the WHOLE component with all its functions.
Is it even possible to achieve this with function components ?
Yes, it's completely possible. React refs are for more than just getting access to DOMNodes. Use a combination of React.forwardRef and the useImperativeHandle React hook to forward a React ref to a function component and expose out functions/values.
const AnotherComponent = React.forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
doSomething
}));
const doSomething () => console.log(something);
return <div> There is a content </div>;
});
...
const MyComponent () => {
const newRef = useRef();
useEffect(() => {
newRef.current.doSomething();
}, []);
return <AnotherComponent ref={newRef} />;
}
Forwarding refs
forwardRef
useImperativeHandle

retrieve parent component ref for a hook

I've found myself needing to retrieve the element ref for every parent component that my hook, useExample, is used in. However, I'm stumped as to how I might be able to retrieve something like this or how to even check if there is an element to target?
Usually I would just do something a little "hacky" in a functional component like so:
const Example = WrappedComponent => {
const ref = createRef();
return <WrappedComponent ref={ref} />;
};
However, due to it being a hook and returning information and not a component, I can't target any component, and thus I'm very stumped.
My current code:
const useExample = () => {
const [stateValue, setStateValue] = useState("example");
useEffect(() => {
// Run some code...
}, []);
return stateValue;
};
const Component = () => {
const data = useExample();
return (
<div> /* <--- How do I gain access to this element */
<span>{ data }</span>
</div>
);
};
I could probably pass a created ref which has been attached to the parent div as a parameter to useExample, however this feels cheap and hacky, and I feel there should be a much easier solution.
In the ideal world something like this would be amazing:
const ref = React.getParentRef();
Apologies if there is an obvious answer in the documentation, I'm very new to React and am unsure of the correct question to be asking or what to be looking for in order to find it in the docs.
You can return the ref from the hook
const useExample = () => {
const myRef = React.useRef(null)
const [stateValue, setStateValue] = useState("example");
useEffect(() => {
// Run some code...
}, []);
return [myRef , stateValue];
};
const Component = () => {
const [myRef , data] = useExample();
return (
<div ref={myRef}> /* <--- How do I gain access to this element */
<span>{ data }</span>
</div>
);
};
If data can be a component:
const useExample = () => {
const myRef = React.useRef(null);
const [stateValue, setStateValue] = React.useState("example");
React.useEffect(() => {
const parent = myRef?.current?.parentNode;
console.log(parent);
}, []);
return <div ref={myRef}>{stateValue}</div>;
};
const Component = () => {
const data = useExample();
return (
<div>
<span>{data}</span>
</div>
);
};
export default function App() {
return <Component />;
}
But then you have to access the parent node from the ref, I believe this may cause problems as a component is being returned, and its anti pattern

Enzyme Test React.createRef()

I have a commponent where I use the new React.createRef() api, how to test document.activeElement should be equal current ref commponent.
component :
export class Automatic extends Component {
componentDidMount = () => this.focusContainer()
componentDidUpdate = () => this.focusContainer()
container = React.createRef()
focusContainer = () => this.container.current.focus()
render = () => {
return (
<div
name='automatic'
onKeyPress={this.captureInput}
onBlur={() => setTimeout(() => this.focusContainer(), 0)}
ref={this.container}
tabIndex={0}
>
...
</div>
}
old testing (works):
it('should focus container on mount', () => {
automatic = mount(<Automatic classes={{}} />, mountContext)
document.activeElement.should.be.equal(automatic.ref('container'))
})
new one (doesn't work):
it.only('should focus container on mount', () => {
const container = React.createRef()
automatic = mount(<Automatic classes={{}} />, mountContext)
document.activeElement.should.be.equal(automatic.ref(container.current))
})
Updated with working examples. Added a styled-components example.
Here's how I solved it with Jest (uses different assertions, but concept is the same):
// setup
const MyComponent = React.forwardRef((props, ref) => (
<div>
<span ref={ref}>some element</span>
</div>
))
// test
it('should contain the forwarded ref in the child span', () => {
const ref = React.createRef()
const component = mount(
<Fragment>
<MyComponent ref={ref} />
</Fragment>,
)
expect(component.find('span').instance()).toEqual(ref.current)
})
The idea is to get the instance of the element that has the ref.
It seems to only work when wrapping MyComponent in another element, I used Fragment.
I ran into some trouble when using **Styled-Components. This is because it creates a number of extra elements. Try debugging with console.log(component.debug()). It will show you what enzyme renders.
When debugging you'll see that Styled-Components uses the recommended way to forward props.
You can find the right element using the property selector for forwardedRef:
// setup
const El = styled.div`
color: red;
`
El.displayName = 'El'
const MyComponentWithStyledChild = React.forwardRef((props, ref) => (
<El ref={ref}>some element</El>
))
// test
it('should contain the forwarded ref in a rendered styled-component', () => {
const ref = React.createRef()
const component = mount(
<Fragment>
<MyComponentWithStyledChild ref={ref} />
</Fragment>,
)
// Styled-components sets prop `forwardedRef`
const target = component
.find('[forwardedRef]')
.childAt(0)
.instance()
expect(target).toEqual(ref.current)
})
You can use the same trick if you created a Higher order Component (HoC) where you need to pass ref
it('should focus container on mount', () => {
automatic = mount(<Automatic classes={{}} />, mountContext)
document.activeElement.should.be.equal(automatic.instance().container.current)
})

Categories

Resources