Maintaining Component Refs Through React.cloneElement - javascript

I have been testing the possible limitations/dangers of using React.cloneElement() to extend a component's children. One possible danger I've identified is the possible overwriting of props such as ref and key.
However, as per React's 0.13 release candidate (back in 2015):
However, unlike JSX and cloneWithProps, it also preserves refs. This means that if you get a child with a ref on it, you won't accidentally steal it from your ancestor. You will get the same ref attached to your new element.
[...]
Note: React.cloneElement(child, { ref: 'newRef' }) DOES override the ref so it is still not possible for two parents to have a ref to the same child, unless you use callback-refs.
I have written a small React application that clones children components pushed through, testing for the validity of refs at two levels:
class ChildComponent extends React.Component{
constructor(props){
super(props);
this.onClick = this.onClick.bind(this);
this.extendsChildren = this.extendChildren(this);
}
onClick(e) {
e.preventDefault();
try{
alert(this._input.value);
}catch(e){
alert('ref broken :(');
}
}
extendChildren(){
return React.Children.map(this.props.children, child => {
return React.cloneElement(
child,
{
ref: ref => this._input = ref
}
);
});
}
render() {
return(
<div>
<button onClick={this.onClick}>
ChildComponent ref check
</button>
{this.extendChildren()}
</div>
);
}
}
class AncestorComponent extends React.Component{
constructor(props){
super(props);
this.onClick = this.onClick.bind(this);
}
onClick(e) {
e.preventDefault();
try{
alert(this._input.value);
}catch(e){
alert('ref broken :(');
}
}
render() {
return (
<div>
<p>
The expected behaviour is that I should be able to click on both Application and ChildComponent check buttons and have a reference to the input (poping an alert with the input's value).
</p>
<button onClick={this.onClick}>
Ancestor ref check
</button>
<ChildComponent>
<input ref={ref => this._input = ref} defaultValue="Hello World"/>
</ChildComponent>
</div>
);
}
}
However, cloningElements inside my ChildComponent overwrites the AncestorComponent's ref prop from the input field, where I would expect that ref prop to be preserved, alongside the new ref I defined as part of the React.cloneElement.
You can test this by running the CodePen.
Is there anything I'm doing wrong, or has this feature been dropped since?

As per Dan Abramov's response, overwriting the reference, even with a callback, is still going to overwrite the reference. You'll need to call the current reference as part of the callback declaration:
return React.Children.map(this.props.children, child =>
React.cloneElement(child, {
ref(node) {
// Keep your own reference
this._input = node;
// Call the original ref, if any
const {ref} = child;
if (typeof ref === 'function') {
ref(node);
}
}
)
);

Related

shouldComponentUpdate() is not being called

Problem
I've parent class which contains list of items and renders component for each item of the list. When some item has been changed (even only one), all items in the list are being rerendered.
So I've tried to implement shouldComponentUpdate(). I am using console.log() to see if it is called but I can't see the log. I've found question shouldComponentUpdate is not never called and tried to return return (JSON.stringify(this.props) !=JSON.stringify(nextProps)); but component still renders itself again. So I've tried just to return false (like do not ever update) but it still does. As the last try I've used PureComponent but it is still being rerendered.
Question
How can I stop children re-rendering if the parent list changes and why is ShouldComponentUpdate never called?
Edit
I've noticed something what I didn't mention in question, I'm sorry for that. I am using context. If I don't use context -> it's ok. Is there any chance to stop re-render while using context? (I'm not using context on updated item - values of context didn't change).
Example
I've parent class which iterates list and renders TaskPreview component for each item of list:
class Dashboard extends React.Component
{
constructor(props) {
this.state = {
tasks: {},
};
}
onTaskUpdate=(task)=>
this.setState(prevState => ({
tasks: {...prevState.tasks, [task._id]: task}
}));
// ... some code
render() {
return (
<div>
{(!Object.entries(this.props.tasks).length)
? null
: this.props.tasks.map((task,index) =>
<TaskPreview key={task._id} task={task} onChange={this.onTaskUpdate}/>
})}
</div>
)
}
}
and I've children TaskPreview class:
class TaskPreview extends React.Component
{
shouldComponentUpdate(nextProps) {
console.log('This log is never shown in console');
return false; // just never!
}
render() {
console.log('task rendered:',this.props.task._id); // indicates rerender
return(<div>Something from props</div>);
}
}
TaskPreview.contextType = TasksContext;
export default TaskPreview;
As #Nicolae Maties suggested I've tried to use Object.keys for iteration instead of direct map but it still doesn't call "shouldComponentUpdate" and still being re-rendered even if there is no changes.
Updated code:
render() {
return (
<div>
{(!Object.entries(this.props.tasks).length)
? null
: Object.keys(this.props.tasks).map((key,index) => {
let task = this.props.tasks[key];
<TaskPreview key={task._id} task={task}/>
}
})}
</div>
)
}
Component is being re-rendered because of .contextType.
TaskPreview.contextType = TasksContext;
Also as is mentioned in documentation:
The propagation from Provider to its descendant consumers (including .contextType and useContext) is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component skips an update. Source: reactjs.org/docs/context
You have to use context somehow else or do not use it at all.
You can use Context.Consumer which won't force re-render of current component but it might force re-render of its children.
<TasksContext.Consumer>
{value => /* render something based on the context value */}
</TasksContext.Consumer>
Instead of return (JSON.stringify(this.props) != JSON.stringify(nextProps)); in your shouldComponentUpdate() life cycle, try specifying tasks object like this return (JSON.stringify(this.props.tasks) != JSON.stringify(nextProps.tasks));
Maybe react is creating new instances of your component and replaces the old instances with them. That's why you're probably not getting your lifecycle method invoked. That can happen if the key property you're assigning in the map always changes.
use from pureComponent and array as state:
class Dashboard extends React.PureComponent
{
constructor(props) {
this.state = {
tasks: this.props.tasks
}
}
onTaskUpdate=(task)=>
this.setState(prevState => ({
tasks: [...prevState.tasks, task] // render only new task
}));
render() {
const {tasks} = this.state
return (
<div>
{tasks.map(task => <TaskPreview key={task._id} task={task} />)}
</div>
)
}
}
class TaskPreview extends React.PureComponent
{
render() {
console.log('task rendered:',this.props.task._id); // indicates rerender
return(<div>Something from props</div>);
}
}
In the shouldComponentUpdate() method of your TaskPreview component, you should check if the next props have changes in comparison to the current props. Then if there are changes, return true to update the component, otherwise false.
The following example compares all the fields of props object with the new props object. But you can only check the props you are interested in.
shouldComponentUpdate(nextProps) {
return !!(Object.keys(nextProps).find(key => nextProps[key] !== this.props[key]));
}
I tried with below code snippet, shouldComponentUpdate worked as I expected. Could you share your Dashboard initial props ?
class Dashboard extends React.Component {
constructor(props) {
this.state = {
tasks: {}
};
}
onTaskUpdate = task =>
this.setState(prevState => ({
tasks: { ...prevState.tasks, [task._id]: task }
}));
// ... some code
render() {
return (
<div>
{!Object.entries(this.props.tasks).length
? null
: Object.keys(this.props.tasks).map((key, index) => {
let task = this.props.tasks[key];
return (
<TaskPreview
key={task._id}
task={task}
onChange={this.onTaskUpdate}
/>
);
})}
</div>
);
}
}
class TaskPreview extends React.Component {
shouldComponentUpdate(nextProps) {
console.log("This log is never shown in console");
return nextProps.task._id != this.props.task._id;
}
render() {
console.log("task rendered:", this.props.task); // indicates rerender
return (
<button onClick={() => this.props.onChange(this.props.task)}>
Something from props
</button>
);
}
}
my initial props for Dashboard component is :
<Dashboard tasks={{test:{_id:'myId', description:'some description'}}}/>

Component is not re-rendering after state changed

I'm trying to fetch videos from youtube and display on browser.. I'm able to get videos and update state property. ( checked with console log)
But SearchResults component is not re-rendering when I update the state property. Here is my component
class Application extends React.Component {
state = {videos: []};
handleFormSubmit = async term => {
const res = await youtubeApi.get("search", {
params: {q: term}
});
this.setState({videos: res.data.items})
};
render() {
return (
<div className="container">
<SearchForm onFormSubmit={this.handleFormSubmit}/>
<SearchResults videos={this.state.videos}/>
</div>
)
}
}
SearchResults component
class SearchResults extends React.Component {
state = {featured: null, suggested: []};
componentDidMount() {
if (this.props.videos.length > 0)
this.setState({featured: this.props.videos[0].id.videoId});
if (this.props.videos.length > 1)
this.setState({suggested: this.props.videos.slice(1)});
}
handleSidebarVideoClick = id => {
this.setState({featured: id})
};
render() {
return (
<div className="row">
<div className="col-md-8">
<Video video={this.state.featured}/>
</div>
<div className="col-md-4">
<Sidebar videos={this.state.suggested} onSidebarVideoClick={this.handleSidebarVideoClick}/>
</div>
</div>
)
}
}
You need to have getDerivedStateFromProps() in your SearchResults component in order to expect a new props value when it changes on parent component.
ComponentDidMount is called once the component is mounted, not when it receives prop values when it changes every time.
So you should have something like this:
static getDerivedStateFromProps(nextProps) {
if (nextProps.videos.length > 0) {
return {
featured: nextProps.videos[0].id.videoId
};
}
if (nextProps.videos.length > 1) {
return {
suggested: nextProps.videos.slice(1)
};
}
return null;
}
Ref: https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
It is unlikely you checked the component's state property. You most likely checked this.state which does get updated. The question is what is this.
There is no evidence you bound your event handlers to the component as recommended in the dev guide. So the keyword this doesn't reflect the Component, it reflects the DOM element receiving the event.
To test this, console log the value of this in you event handlers.
To fix:
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.handleSidebarVideoClick = this.handleSidebarVideoClick.bind(this);
in their respective classes constructors.
Ref: https://reactjs.org/docs/handling-events.html

Deprecation warning using this.refs

I have a React component and I want to toggle a css class when clicked.
So I have this:
export class myComponent extends React.Component {
constructor() {
super();
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
render() {
return (
<div>
<div onClick={this.clicked}><span ref="btn" className="glyphicon"> </span></div>
</div>
);
}
handleClick() {
this.refs.btn.classList.toggle('active');
}
componentDidMount() {
this.refs.btn.addEventListener('click', this.handleClick);
this.setState({
clicked: this.state.clicked = true,
});
}
componentWillUnmount() {
this.refs.btn.removeEventListener('click', this.handleClick);
this.setState({
clicked: this.state.clicked = false,
});
}
}
This problem is that ESLint keeps telling me "this.refs" is depreciated.
What do I do instead? How can I fix it so it's not using depreciated code?
The Lint rule you are referring to is called no-string-refs and warns you with:
"Using string literals in ref attributes is deprecated (react/no-string-refs)"
You are getting this warning because have implemented the deprecated way of using refs (by using strings). Depending on your React version, you can do:
React 16.3 and later
constructor() {
super();
this.btnRef= React.createRef();
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
render() {
return (
<div>
<div onClick={this.addVote}><span ref={this.btnRef} className="glyphicon"> </span></div>
</div>
);
}
React 16.2 and older
constructor() {
super();
this.btnRef; //not necessary to declare the variable here, but I like to make it more visible.
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
render() {
return (
<div>
<div onClick={this.addVote}><span ref={(el) => this.btnRef = el} className="glyphicon"> </span></div>
</div>
);
}
For even better readability, you could also do:
render() {
let myRef = (el) => this.btnRef = el;
return (
<div>
<div onClick={this.addVote}><span ref={myRef} className="glyphicon"> </span></div>
</div>
);
}
Have a look at what the official documentation says on Refs and the DOM, and this section in particular:
Legacy API: String Refs
If you worked with React before, you might be
familiar with an older API where the ref attribute is a string, like
"textInput", and the DOM node is accessed as this.refs.textInput. We
advise against it because string refs have some issues, are considered
legacy, and are likely to be removed in one of the future releases. If
you're currently using this.refs.textInput to access refs, we
recommend the callback pattern instead.
The reason this ESLint rule exists is that string Refs are on their way out. However, for the code above I would recommend to not use a Ref in the first place.
Don't Overuse Refs
React's advantage is that it is declarative. Meaning, we have state and an expression (returned JSX) of how the UI (more precisely the DOM) should look given a certain state.
Whatever can be done using just state and UI expression, should be done this way. The problem with the use of a Ref in the code above is that it makes the code imperative. We can't understand how the DOM will look just from the JSX. Here is how you could achieve the same result in a declarative way:
export class myComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false
};
}
handleClick = () => { // with arrow function there is no need for binding.
this.setState(
prevState => {
return {
active: !prevState.active
}
}
)
}
render() {
return (
<div>
<span
onClick={this.handleClick}
className={`glyphicon ${this.state.active && "active"}`}
>
Hello World
</span>
</div>
);
}
}
Refs should be used when state and UI expression aren't enough, and you need access to the actual DOM. For example, focusing on an input field, scrolling to an element, or getting the exact width and height of an element.
If you do use Refs, avoid string refs
String refs harm performance, aren't composable, and are on there way out.
string refs have some issues, are considered legacy, and are likely to
be removed in one of the future releases. [Official React documentation]
[resource1][1], [resource2][1]
Option #1: Use React.createRef
class MyComponent extends Component {
constructor(props) {
super(props)
this.myRef = React.createRef() // create a ref object
}
render() {
return <div ref={this.myRef}></div> // Attach the ref property to a dom element
}
}
Option #2: Use a ref callback
class MyComponent extends Component {
constructor(props){ // Optional, declare a class field
super(props)
this.myRef=null
}
render() {
return <div ref={ (ref) => this.myRef=ref }></div>
} // Attach the dom element to a class field
}
you can try a more declarative way. I changed your code to reflect this. You just need to remind that a component will refresh and call render in every state/props change. So, we can create the class of your element inside render method.
import React from 'react'
export default class myComponent extends React.Component {
constructor() {
super();
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
render() {
let btnClass = 'glyphicon'
if(this.state.clicked){
btnClass+=' active'
}
return (
<div>
<div onClick={this.handleClick}><span ref="btn" className={btnClass}> </span></div>
</div>
);
}
handleClick() {
this.setState({
clicked: !this.state.clicked
})
}
}

ReactJS - MouseClick gets triggered without a click

I'm new to React.JS and trying to create a click event on an element inside a rendered component.
Here is my code:
class InputPanel extends React.Component{
handleClick(i,j) {
this.props.dispatch(actions.someMethod());
// e.preventDefault();
}
render() {
const { dispatch, board } = this.props;
return(
<div>
{
board.map((row, i) => (
<div>{row.map((cell, j) => <div className="digit"
onClick={this.handleClick(i,j)}>{cell}</div>)}</div>
))
}
</div>
);
}
};
My problem is that "handleClick" gets triggered after page load without any mouse clicked!
I've read about React.JS lifecycle and thought about registering to click event in componentDidMount method, but i'm really not sure about it:
Is there any easier way ? (or: Am I doing something wrong that triggers click ?)
If adding componentDidMount method is the right way - how can I get the element I create in render method ?
You should not use .bind when passing the callback as a prop. There’s a ESLint rule for that. You can read more about how to pass callback without breaking React performance here.
Summary:
make sure you aren’t calling functions but pass functions as handlers in your props.
make sure you do not create functions on every render, for that, you need to bind your handlers in parent component, pass correct the required data (such as indices of iteration) down the child component and have it call the parent’s handler with the data it has
Ideally you’d create another component for the rows and pass the callback there. Moreover, ideally you’d bind the onClick in the parent component’s constructor (or componentWillMount). Otherwise every time render runs a new function is created (in both anonymous function handler () => { this.onClick() } and this.onClick.bind and defeat React’s vdom diff causing every row to rerender every time.
So:
class InputPanel extends React.Component{
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick(i,j) {
this.props.dispatch(actions.someMethod());
// e.preventDefault();
}
render() {
const { dispatch, board } = this.props;
return(
<div>
{board.map((row, i) => <div>
{row.map((cell, j) => <Digit
onClick={this.handleClick})
i={i}
j={j}
cell={cell}
/>)}
</div>)}
</div>
);
}
};
class Digit extends React.Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.onClick(this.props.i, this.props.j);
}
render() {
return <div
className="digit"
onClick={this.handleClick}
>{this.props.cell}</div>
}
}
It is because you are calling this.handleClick() function instead of providing a function definition as onClick prop.
Try changing the div line like this:
<div className="digit" onClick={ () => this.handleClick(i,j) }>{cell}</div>
Also you have to bind this.handleClick() function. You can add a constructor and bind all the member functions of a class there. that's the best practice in ES6.
constructor(props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);
}
You call this function in render. You should only transfer function and bind params.
onClick={this.handleClick.bind(null,i,j)}
You should use .bind().
class InputPanel extends React.Component{
handleClick(i,j) {
this.props.dispatch(actions.someMethod());
// e.preventDefault();
}
render() {
const { dispatch, board } = this.props;
return(
<div>
{
board.map((row, i) => (
<div>{row.map((cell, j) => <div className="digit"
onClick={this.handleClick.bind(null,i,j)}>{cell}</div>)}</div>
))
}
</div>
);
}
};

Call child method from parent

I have two components:
Parent component
Child component
I was trying to call Child's method from Parent, I tried this way but couldn't get a result:
class Parent extends Component {
render() {
return (
<Child>
<button onClick={Child.getAlert()}>Click</button>
</Child>
);
}
}
class Child extends Component {
getAlert() {
alert('clicked');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}
Is there a way to call Child's method from Parent?
Note: Child and Parent components are in two different files.
First off, let me express that this is generally not the way to go about things in React land. Usually what you want to do is pass down functionality to children in props, and pass up notifications from children in events (or better yet: dispatch).
But if you must expose an imperative method on a child component, you can use refs. Remember this is an escape hatch and usually indicates a better design is available.
Previously, refs were only supported for Class-based components.
With the advent of React Hooks, that's no longer the case
Modern React with Hooks (v16.8+)
const { forwardRef, useRef, useImperativeHandle } = React;
// We need to wrap component in `forwardRef` in order to gain
// access to the ref object that is assigned using the `ref` prop.
// This ref is passed as the second parameter to the function component.
const Child = forwardRef((props, ref) => {
// The component instance will be extended
// with whatever you return from the callback passed
// as the second argument
useImperativeHandle(ref, () => ({
getAlert() {
alert("getAlert from Child");
}
}));
return <h1>Hi</h1>;
});
const Parent = () => {
// In order to gain access to the child component instance,
// you need to assign it to a `ref`, so we call `useRef()` to get one
const childRef = useRef();
return (
<div>
<Child ref={childRef} />
<button onClick={() => childRef.current.getAlert()}>Click</button>
</div>
);
};
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Documentation for useImperativeHandle() is here:
useImperativeHandle customizes the instance value that is exposed to parent components when using ref.
Legacy API using Class Components (>= react#16.4)
const { Component } = React;
class Parent extends Component {
constructor(props) {
super(props);
this.child = React.createRef();
}
onClick = () => {
this.child.current.getAlert();
};
render() {
return (
<div>
<Child ref={this.child} />
<button onClick={this.onClick}>Click</button>
</div>
);
}
}
class Child extends Component {
getAlert() {
alert('getAlert from Child');
}
render() {
return <h1>Hello</h1>;
}
}
ReactDOM.render(<Parent />, document.getElementById('root'));
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Callback Ref API
Callback-style refs are another approach to achieving this, although not quite as common in modern React:
const { Component } = React;
const { render } = ReactDOM;
class Parent extends Component {
render() {
return (
<div>
<Child ref={instance => { this.child = instance; }} />
<button onClick={() => { this.child.getAlert(); }}>Click</button>
</div>
);
}
}
class Child extends Component {
getAlert() {
alert('clicked');
}
render() {
return (
<h1>Hello</h1>
);
}
}
render(
<Parent />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
You can use another pattern here:
class Parent extends Component {
render() {
return (
<div>
<Child setClick={click => this.clickChild = click}/>
<button onClick={() => this.clickChild()}>Click</button>
</div>
);
}
}
class Child extends Component {
constructor(props) {
super(props);
this.getAlert = this.getAlert.bind(this);
}
componentDidMount() {
this.props.setClick(this.getAlert);
}
getAlert() {
alert('clicked');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}
What it does is to set the parent's clickChild method when child is mounted. In this way when you click the button in parent it will call clickChild which calls child's getAlert.
This also works if your child is wrapped with connect() so you don't need the getWrappedInstance() hack.
Note you can't use onClick={this.clickChild} in parent because when parent is rendered child is not mounted so this.clickChild is not assigned yet. Using onClick={() => this.clickChild()} is fine because when you click the button this.clickChild should already be assigned.
Alternative method with useEffect:
Parent:
const [refresh, doRefresh] = useState(0);
<Button onClick={() => doRefresh(prev => prev + 1)} />
<Children refresh={refresh} />
Children:
useEffect(() => {
performRefresh(); //children function of interest
}, [props.refresh]);
Here I will give you the four possible combinations that can happen:
Class Parent | Hook Child
Hook Parent | Class Child
Hook Parent | Hook Child
Class Parent | Class Child
1. Class Parent | Hook Child
class Parent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return (<View>
<Child ref={this.myRef}/>
<Button title={'call me'}
onPress={() => this.myRef.current.childMethod()}/>
</View>)
}
}
const Child = React.forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
childMethod() {
childMethod()
}
}))
function childMethod() {
console.log('call me')
}
return (<View><Text> I am a child</Text></View>)
})
2. Hook Parent | Class Child
function Parent(props) {
const myRef = useRef()
return (<View>
<Child ref={myRef}/>
<Button title={'call me'}
onPress={() => myRef.current.childMethod()}/>
</View>)
}
class Child extends React.Component {
childMethod() {
console.log('call me')
}
render() {
return (<View><Text> I am a child</Text></View>)
}
}
3. Hook Parent | Hook Child
function Parent(props) {
const myRef = useRef()
return (<View>
<Child ref={myRef}/>
<Button title={'call me'}
onPress={() => myRef.current.childMethod()}/>
</View>)
}
const Child = React.forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
childMethod() {
childMethod()
}
}))
function childMethod() {
console.log('call me')
}
return (<View><Text> I am a child</Text></View>)
})
4. Class Parent | Class Child
class Parent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
render() {
return (<View>
<Child ref={this.myRef}/>
<Button title={'call me'}
onPress={() => this.myRef.current.childMethod()}/>
</View>)
}
}
class Child extends React.Component {
childMethod() {
console.log('call me')
}
render() {
return (<View><Text> I am a child</Text></View>)
}
}
https://facebook.github.io/react/tips/expose-component-functions.html
for more answers ref here Call methods on React children components
By looking into the refs of the "reason" component, you're breaking encapsulation and making it impossible to refactor that component without carefully examining all the places it's used. Because of this, we strongly recommend treating refs as private to a component, much like state.
In general, data should be passed down the tree via props. There are a few exceptions to this (such as calling .focus() or triggering a one-time animation that doesn't really "change" the state) but any time you're exposing a method called "set", props are usually a better choice. Try to make it so that the inner input component worries about its size and appearance so that none of its ancestors do.
I wasn't satisfied with any of the solutions presented here. There is actually a very simple solution that can be done using pure Javascript without relying upon some React functionality other than the basic props object - and it gives you the benefit of communicating in either direction (parent -> child, child -> parent). You need to pass an object from the parent component to the child component. This object is what I refer to as a "bi-directional reference" or biRef for short. Basically, the object contains a reference to methods in the parent that the parent wants to expose. And the child component attaches methods to the object that the parent can call. Something like this:
// Parent component.
function MyParentComponent(props) {
function someParentFunction() {
// The child component can call this function.
}
function onButtonClick() {
// Call the function inside the child component.
biRef.someChildFunction();
}
// Add all the functions here that the child can call.
var biRef = {
someParentFunction: someParentFunction
}
return <div>
<MyChildComponent biRef={biRef} />
<Button onClick={onButtonClick} />
</div>;
}
// Child component
function MyChildComponent(props) {
function someChildFunction() {
// The parent component can call this function.
}
function onButtonClick() {
// Call the parent function.
props.biRef.someParentFunction();
}
// Add all the child functions to props.biRef that you want the parent
// to be able to call.
props.biRef.someChildFunction = someChildFunction;
return <div>
<Button onClick={onButtonClick} />
</div>;
}
The other advantage to this solution is that you can add a lot more functions in the parent and child while passing them from the parent to the child using only a single property.
An improvement over the code above is to not add the parent and child functions directly to the biRef object but rather to sub members. Parent functions should be added to a member called "parent" while the child functions should be added to a member called "child".
// Parent component.
function MyParentComponent(props) {
function someParentFunction() {
// The child component can call this function.
}
function onButtonClick() {
// Call the function inside the child component.
biRef.child.someChildFunction();
}
// Add all the functions here that the child can call.
var biRef = {
parent: {
someParentFunction: someParentFunction
}
}
return <div>
<MyChildComponent biRef={biRef} />
<Button onClick={onButtonClick} />
</div>;
}
// Child component
function MyChildComponent(props) {
function someChildFunction() {
// The parent component can call this function.
}
function onButtonClick() {
// Call the parent function.
props.biRef.parent.someParentFunction();
}
// Add all the child functions to props.biRef that you want the parent
// to be able to call.
props.biRef {
child: {
someChildFunction: someChildFunction
}
}
return <div>
<Button onClick={onButtonClick} />
</div>;
}
By placing parent and child functions into separate members of the biRef object, you 'll have a clean separation between the two and easily see which ones belong to parent or child. It also helps to prevent a child component from accidentally overwriting a parent function if the same function appears in both.
One last thing is that if you note, the parent component creates the biRef object with var whereas the child component accesses it through the props object. It might be tempting to not define the biRef object in the parent and access it from its parent through its own props parameter (which might be the case in a hierarchy of UI elements). This is risky because the child may think a function it is calling on the parent belongs to the parent when it might actually belong to a grandparent. There's nothing wrong with this as long as you are aware of it. Unless you have a reason for supporting some hierarchy beyond a parent/child relationship, it's best to create the biRef in your parent component.
I hope I'm not repeating anything from above but what about passing a callback prop that sets the function in the parent? This works and is pretty easy. (Added code is between the ////'s)
class Parent extends Component {
/////
getAlert = () => {} // initial value for getAlert
setGetAlertMethod = (newMethod) => {
this.getAlert = newMethod;
}
/////
render() {
return (
<Child setGetAlertMethod={this.setGetAlertMethod}>
<button onClick={this.getAlert}>Click</button>
</Child>
);
}
}
class Child extends Component {
/////
componentDidMount() {
this.props.setGetAlertMethod(this.getAlert);
}
/////
getAlert() => {
alert('clicked');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}
you can use ref to call the function of the child component from the parent
Functional Component Solution
in functional component, you have to use useImperativeHandle for getting ref into a child like below
import React, { forwardRef, useRef, useImperativeHandle } from 'react';
export default function ParentFunction() {
const childRef = useRef();
return (
<div className="container">
<div>
Parent Component
</div>
<button
onClick={() => { childRef.current.showAlert() }}
>
Call Function
</button>
<Child ref={childRef}/>
</div>
)
}
const Child = forwardRef((props, ref) => {
useImperativeHandle(
ref,
() => ({
showAlert() {
alert("Child Function Called")
}
}),
)
return (
<div>Child Component</div>
)
})
Class Component Solution
Child.js
import s from './Child.css';
class Child extends Component {
getAlert() {
alert('clicked');
}
render() {
return (
<h1>Hello</h1>
);
}
}
export default Child;
Parent.js
class Parent extends Component {
render() {
onClick() {
this.refs.child.getAlert();
}
return (
<div>
<Child ref="child" />
<button onClick={this.onClick}>Click</button>
</div>
);
}
}
I'm using useEffect hook to overcome the headache of doing all this so now I pass a variable down to child like this:
import { useEffect, useState } from "react";
export const ParentComponent = () => {
const [trigger, setTrigger] = useState(false);
return (
<div onClick={() => { setTrigger(trigger => !trigger); }}>
<ChildComponent trigger={trigger}></ChildComponent>
</div>
);
};
export const ChildComponent = (props) => {
const triggerInvokedFromParent = () => {
console.log('TriggerInvokedFromParent');
};
useEffect(() => {
triggerInvokedFromParent();
}, [props.trigger]);
return <span>ChildComponent</span>;
};
We can use refs in another way as-
We are going to create a Parent element, it will render a <Child/> component. As you can see, the component that will be rendered, you need to add the ref attribute and provide a name for it.
Then, the triggerChildAlert function, located in the parent class will access the refs property of the this context (when the triggerChildAlert function is triggered will access the child reference and it will has all the functions of the child element).
class Parent extends React.Component {
triggerChildAlert(){
this.refs.child.callChildMethod();
// to get child parent returned value-
// this.value = this.refs.child.callChildMethod();
// alert('Returned value- '+this.value);
}
render() {
return (
<div>
{/* Note that you need to give a value to the ref parameter, in this case child*/}
<Child ref="child" />
<button onClick={this.triggerChildAlert}>Click</button>
</div>
);
}
}
Now, the child component, as theoretically designed previously, will look like:
class Child extends React.Component {
callChildMethod() {
alert('Hello World');
// to return some value
// return this.state.someValue;
}
render() {
return (
<h1>Hello</h1>
);
}
}
Here is the source code-
Hope will help you !
If you are doing this simply because you want the Child to provide a re-usable trait to its parents, then you might consider doing that using render-props instead.
That technique actually turns the structure upside down. The Child now wraps the parent, so I have renamed it to AlertTrait below. I kept the name Parent for continuity, although it is not really a parent now.
// Use it like this:
<AlertTrait renderComponent={Parent}/>
class AlertTrait extends Component {
// You will need to bind this function, if it uses 'this'
doAlert() {
alert('clicked');
}
render() {
return this.props.renderComponent({ doAlert: this.doAlert });
}
}
class Parent extends Component {
render() {
return (
<button onClick={this.props.doAlert}>Click</button>
);
}
}
In this case, the AlertTrait provides one or more traits which it passes down as props to whatever component it was given in its renderComponent prop.
The Parent receives doAlert as a prop, and can call it when needed.
(For clarity, I called the prop renderComponent in the above example. But in the React docs linked above, they just call it render.)
The Trait component can render stuff surrounding the Parent, in its render function, but it does not render anything inside the parent. Actually it could render things inside the Parent, if it passed another prop (e.g. renderChild) to the parent, which the parent could then use during its render method.
This is somewhat different from what the OP asked for, but some people might end up here (like we did) because they wanted to create a reusable trait, and thought that a child component was a good way to do that.
For functional components easiest way is
Parent Component
parent.tsx
import React, { useEffect, useState, useRef } from "react";
import child from "../../child"
const parent: React.FunctionComponent = () => {
const childRef: any = useRef();
}
const onDropDownChange: any = (event): void => {
const target = event.target;
childRef.current.onFilterChange(target.value);
};
return <child ref={childRef} />
export default parent;
Child Component
child.tsx
import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle, } from "react";
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
onFilterChange(id) {
console.log("Value from parent", id)
},
}));
})
Child.displayName = "Child";
export default Child;
The logic is simple.
Create a function in parent using child or use ref.
I prefer the creating function in parent using child. There are multiple ways to do it.
When using functional components
In Parent
function Parent(){
const [functionToCall, createFunctionToCall] = useState(()=>()=>{})
return (
<Child createFunctionToCall={createFunctionToCall} />
)
}
In Child
function Child({createFunctionToCall}){
useEffect(()=>{
function theFunctionToCall(){
// do something like setting something
// don't forget to set dependancies properly.
}
createFunctionToCall(()=>theFunctionToCall)
},[createFunctionToCall])
}
This pattern is similar to #brickingup answer. But in this version you can set as many child actions you want.
import { useEffect } from "react";
export const Parent = () => {
const childEvents = { click: () => {} };
return (
<div onClick={() => childEvents.click()}>
<Child events={childEvents}></Child>
</div>
);
};
export const Child = (props) => {
const click = () => {
alert("click from child");
};
useEffect(() => {
if (props.events) {
props.events.click = click;
}
}, []);
return <span>Child Component</span>;
};
We're happy with a custom hook we call useCounterKey. It just sets up a counterKey, or a key that counts up from zero. The function it returns resets the key (i.e. increment). (I believe this is the most idiomatic way in React to reset a component - just bump the key.)
However this hook also works in any situation where you want to send a one-time message to the client to do something. E.g. we use it to focus a control in the child on a certain parent event - it just autofocuses anytime the key is updated. (If more props are needed they could be set prior to resetting the key so they're available when the event happens.)
This method has a bit of a learning curve b/c it's not as straightforward as a typical event handler, but it seems the most idiomatic way to handle this in React that we've found (since keys already function this way). Def open to feedback on this method but it is working well!
// Main helper hook:
export function useCounterKey() {
const [key, setKey] = useState(0);
return [key, () => setKey(prev => prev + 1)] as const;
}
Sample usages:
// Sample 1 - normal React, just reset a control by changing Key on demand
function Sample1() {
const [inputLineCounterKey, resetInputLine] = useCounterKey();
return <>
<InputLine key={inputLineCounterKey} />
<button onClick={() => resetInputLine()} />
<>;
}
// Second sample - anytime the counterKey is incremented, child calls focus() on the input
function Sample2() {
const [amountFocusCounterKey, focusAmountInput] = useCounterKey();
// ... call focusAmountInput in some hook or event handler as needed
return <WorkoutAmountInput focusCounterKey={amountFocusCounterKey} />
}
function WorkoutAmountInput(props) {
useEffect(() => {
if (counterKey > 0) {
// Don't focus initially
focusAmount();
}
}, [counterKey]);
// ...
}
(Credit to Kent Dodds for the counterKey concept.)
Parent component
import Child from './Child'
export default function Parent(props) {
const [childRefreshFunction, setChildRefreshFunction] = useState(null);
return (
<div>
<button type="button" onClick={() => {
childRefreshFunction();
}}>Refresh child</button>
<Child setRefreshFunction={(f) => {
setChildRefreshFunction(f);
}} />
</div>
)
}
Child component
export default function Child(props) {
useEffect(() => {
props.setRefreshFunction(() => refreshMe);
}, []);
function refreshMe() {
fetch('http://example.com/data.json')....
};
return (
<div>
child
</div>
)
}
You can achieve this easily in this way
Steps-
Create a boolean variable in the state in the parent class. Update this when you want to call a function.
Create a prop variable and assign the boolean variable.
From the child component access that variable using props and execute the method you want by having an if condition.
class Child extends Component {
Method=()=>{
--Your method body--
}
render() {
return (
//check whether the variable has been updated or not
if(this.props.updateMethod){
this.Method();
}
)
}
}
class Parent extends Component {
constructor(){
this.state={
callMethod:false
}
}
render() {
return (
//update state according to your requirement
this.setState({
callMethod:true
}}
<Child updateMethod={this.state.callMethod}></Child>
);
}
}
Another way of triggering a child function from parent is to make use of the componentDidUpdate function in child Component. I pass a prop triggerChildFunc from Parent to Child, which initially is null. The value changes to a function when the button is clicked and Child notice that change in componentDidUpdate and calls its own internal function.
Since prop triggerChildFunc changes to a function, we also get a callback to the Parent. If Parent don't need to know when the function is called the value triggerChildFunc could for example change from null to true instead.
const { Component } = React;
const { render } = ReactDOM;
class Parent extends Component {
state = {
triggerFunc: null
}
render() {
return (
<div>
<Child triggerChildFunc={this.state.triggerFunc} />
<button onClick={() => {
this.setState({ triggerFunc: () => alert('Callback in parent')})
}}>Click
</button>
</div>
);
}
}
class Child extends Component {
componentDidUpdate(prevProps) {
if (this.props.triggerChildFunc !== prevProps.triggerChildFunc) {
this.onParentTrigger();
}
}
onParentTrigger() {
alert('parent triggered me');
// Let's call the passed variable from parent if it's a function
if (this.props.triggerChildFunc && {}.toString.call(this.props.triggerChildFunc) === '[object Function]') {
this.props.triggerChildFunc();
}
}
render() {
return (
<h1>Hello</h1>
);
}
}
render(
<Parent />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='app'></div>
CodePen: https://codepen.io/calsal/pen/NWPxbJv?editors=1010
Here my demo: https://stackblitz.com/edit/react-dgz1ee?file=styles.css
I am using useEffect to call the children component's methods. I have tried with Proxy and Setter_Getter but sor far useEffect seems to be the more convenient way to call a child method from parent. To use Proxy and Setter_Getter it seems there is some subtlety to overcome first, because the element firstly rendered is an objectLike's element through the ref.current return => <div/>'s specificity.
Concerning useEffect, you can also leverage on this approach to set the parent's state depending on what you want to do with the children.
In the demo's link I have provided, you will find my full ReactJS' code with my draftwork inside's so you can appreciate the workflow of my solution.
Here I am providing you my ReactJS' snippet with the relevant code only. :
import React, {
Component,
createRef,
forwardRef,
useState,
useEffect
} from "react";
{...}
// Child component
// I am defining here a forwardRef's element to get the Child's methods from the parent
// through the ref's element.
let Child = forwardRef((props, ref) => {
// I am fetching the parent's method here
// that allows me to connect the parent and the child's components
let { validateChildren } = props;
// I am initializing the state of the children
// good if we can even leverage on the functional children's state
let initialState = {
one: "hello world",
two: () => {
console.log("I am accessing child method from parent :].");
return "child method achieve";
}
};
// useState initialization
const [componentState, setComponentState] = useState(initialState);
// useEffect will allow me to communicate with the parent
// through a lifecycle data flow
useEffect(() => {
ref.current = { componentState };
validateChildren(ref.current.componentState.two);
});
{...}
});
{...}
// Parent component
class App extends Component {
// initialize the ref inside the constructor element
constructor(props) {
super(props);
this.childRef = createRef();
}
// I am implementing a parent's method
// in child useEffect's method
validateChildren = childrenMethod => {
// access children method from parent
childrenMethod();
// or signaling children is ready
console.log("children active");
};
{...}
render(){
return (
{
// I am referencing the children
// also I am implementing the parent logic connector's function
// in the child, here => this.validateChildren's function
}
<Child ref={this.childRef} validateChildren={this.validateChildren} />
</div>
)
}
You can apply that logic very easily using your child component as a react custom hook.
How to implement it?
Your child returns a function.
Your child returns a JSON: {function, HTML, or other values} as the example.
In the example doesn't make sense to apply this logic but it is easy to see:
const {useState} = React;
//Parent
const Parent = () => {
//custome hook
const child = useChild();
return (
<div>
{child.display}
<button onClick={child.alert}>
Parent call child
</button>
{child.btn}
</div>
);
};
//Child
const useChild = () => {
const [clickCount, setClick] = React.useState(0);
{/* child button*/}
const btn = (
<button
onClick={() => {
setClick(clickCount + 1);
}}
>
Click me
</button>
);
return {
btn: btn,
//function called from parent
alert: () => {
alert("You clicked " + clickCount + " times");
},
display: <h1>{clickCount}</h1>
};
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I tried using createRef or useRef. Somehow they all return null.
Secondly, this answer proposes to pass a prop that sets a function that seems the most reasonable to me. But if your child component is used in multiple places, you should add that extra prop to other places. Also if you want to call a method in the grandchild, this method might be too verbose or mouthful.
So I made my own function store in a very primitive way.
Below is functionStore.js file
const fns = {};
export function setFn(componentName, fnName, fn) {
if (fns[componentName]) {
fns[componentName][fnName] = fn;
} else {
fns[componentName] = { fnName: fn };
}
}
export function callFn(componentName, fnName) {
fns[componentName][fnName]();
}
I just set the functions that need to be called from any component.
import { setFn } from "./functionStore";
export class AComponent extends React.Component {
componentDidMount() {
setFn("AComponent", "aFunc", this.aFunc);
}
aFunc = () => { console.log("aFunc is called!"); };
}
Then I just call it from some other component
import { callFn } from "./functionStore";
export class BComponent extends React.Component {
// just call the function
bFunc = () => {
callFn("AComponent", "aFunc");
};
}
One disadvantage is the function to be called should be parameterless. But this might be fixed somehow as well. Currently, I don't need to pass parameters.
I think that the most basic way to call methods is by setting a request on the child component. Then as soon as the child handles the request, it calls a callback method to reset the request.
The reset mechanism is necessary to be able to send the same request multiple times after each other.
In parent component
In the render method of the parent:
const { request } = this.state;
return (<Child request={request} onRequestHandled={()->resetRequest()}/>);
The parent needs 2 methods, to communicate with its child in 2 directions.
sendRequest() {
const request = { param: "value" };
this.setState({ request });
}
resetRequest() {
const request = null;
this.setState({ request });
}
In child component
The child updates its internal state, copying the request from the props.
constructor(props) {
super(props);
const { request } = props;
this.state = { request };
}
static getDerivedStateFromProps(props, state) {
const { request } = props;
if (request !== state.request ) return { request };
return null;
}
Then finally it handles the request, and sends the reset to the parent:
componentDidMount() {
const { request } = this.state;
// todo handle request.
const { onRequestHandled } = this.props;
if (onRequestHandled != null) onRequestHandled();
}
Here's a bug? to look out for:
I concur with rossipedia's solution using forwardRef, useRef, useImperativeHandle
There's some misinformation online that says refs can only be created from React Class components, but you can indeed use Function Components if you use the aforementioned hooks above. A note, the hooks only worked for me after I changed the file to not use withRouter() when exporting the component. I.e. a change from
export default withRouter(TableConfig);
to instead be
export default TableConfig;
In hindsight the withRouter() is not needed for such a component anyway, but usually it doesn't hurt anything having it in. My use case is that I created a component to create a Table to handle the viewing and editing of config values, and I wanted to be able to tell this Child component to reset it's state values whenever the Parent form's Reset button was hit. UseRef() wouldn't properly get the ref or ref.current (kept on getting null) until I removed withRouter() from the file containing my child component TableConfig

Categories

Resources