What's the proper way of passing a ref to a prop? - javascript

I'm trying to pass a ref of a component to another component. Since string refs are being deprecated I'm using callback refs.
So I have something similar to this:
<One ref={c => this.one = c}/>
<Two one={this.one}/>
The problem is that whenever I try to access this.props.one inside Two I get undefined.
I have even tried this on Two:
componentDidMount(){
setTimeout(()=>{
console.log(this.props.one);
},5000)
}
It seems the problem is that when the prop is created, the ref doesn't exist yet since it's created once One is mounted. But I don't know how to "refresh" the props on Two to get the ref to the mounted component.
So what's the proper way of passing a ref to another component?
Edit
Some users have suggested to encapsulate that logic in a higher component, which in itself renders those other child components.
The problem with that approach is that you can't create reusable logic and you have to repeat the same logic over and over in those encapsulating components.
Let's say you want to create a generic <Form> component which encapsulates the submit logic to your store, error checking, etc. And you do something like this:
<Form>
<Input/>
<Input/>
<Input/>
<Input/>
<SubmitButton/>
</Form>
In this example <Form> can't access the instances (and methods) of the children since this.props.children doesn't return those instances. It returns some list of pseudo components.
So how can you check if a certain <Input/> has detected a validation error without passing a ref?
You have to encapsulate those components in another component with the validation logic. For example in <UserForm>. But since each form is different the same logic has to be copied in <CategoryForm>, <GoupForm>, etc. This is terribly inefficient which is why I want to encapsulate the validation logic in <Form> and pass references of the <Input> components to <Form>.

In general the "ref" feature is an anti-pattern in React. It exists to enable side-effect driven development, however in order to benefit the most from the React way of programming you should try to avoid "refs" if possible.
As for your particular issue, passing a child a ref to it's sibling is a chicken vs. egg scenario. The ref callback is fired when the child is mounted, not during render which is why your example doesn't work. One thing you can try is pushing the ref into state and then reading from state into the other child. So:
<One ref={c => !this.state.one && this.setState({ one: c })}/>
<Two one={this.state.one}/>
Note: without the !this.state.one this will cause an infinite loop.
Here is a codepen example of this working (look at the console to see the sibling ref logged): http://codepen.io/anon/pen/pbqvRA

This is now much simpler using the new ref api (available since React 16 - thanks to perilandmishap for pointing that out).
class MyComponent extends React.Component {
constructor (props) {
super(props);
this.oneRef = React.createRef();
}
render () {
return (
<React.Fragment>
<One ref={this.oneRef} />
<Two one={this.oneRef} />
</React.Fragment>
}
}
}
You would consume the prop in Two like:
this.props.one.current
A few things of note with this approach:
The ref will be an object with a current property. That property will be null until the element/component is mounted. Once it's mounted, it will be the instance of One. It should be safe to reference it once <Two /> is mounted.
Once the <One /> instance is unmounted, the current property on the ref returns to being null.

In general, if you need to pass a reference to something that may not be set at call time, you can pass a lambda instead:
<One ref={c => this.one = c}/>
<Two one={() => this.one}/>
and then reference it as
this.props.one()
If it has been set when you call it, you'll get a value. Before that, you'll get undefined (assuming it hasn't otherwise been initialized).
It bears noting that you won't necessarily re-render when it becomes available, and I would expect it to be undefined on the first render. This is something that using state to hold your reference does handle, but you won't get more than one re-render.
Given all that, I would recommend moving whatever code was using the ref to One in Two up into the component that is rendering One and Two, to avoid all the issues with both this strategy, and the one in #Carl Sverre's answer.

Related

ReactJS How to pass a prop to a component in state

function App() {
const [currentPanel, setCurrentPanel] = useState(ProfilePanel); // ProfilePanel is a component
return (
<div className={styles.App}>
{currentPanel}
</div>
);
}
In code i set the component "ProfilePanel" to a "curentPanlel" state, then in App i change the component in state, and this render an another panel. the problem is that i dont know how to pass props when i render it like this.
i tried the {currentPanel()} but is return an error.
please help to find a method to solve this, or if this method to render a component in state are absolutly wrong tell how to do this another way.
the problem is that i dont know how to pass props when i render it like this
You'd do it by using an initial capital letter for the state member (CurrentPanel instead of currentPanel), and then using it as normal (<CurrentPanel someProp="some value" />). (It has to be initially-capped because that's how JSX knows it's supposed to be a component, not a tag name.) But, you'll struggle to set a different component function in state, because component functions are, well, functions, and when you pass a function to a state setter, it thinks you're using the callback version of the state setter and calls your function, rather than setting it in state.
If you absolutely have to hold a component function in state, wrap it in an object, but it's much more likely that there's a better solution to the overall problem you're trying to solve.

React - Change state from external component

I know that I will ask a question that brake some rules about the core/basic way to use React... but maybe with this example, someone helps me to solve the problem that I facing.
This is not the full code of my project, but show exactly the idea of my problem:
https://codesandbox.io/s/change-state-from-external-component-zi79e
The thing is I need to change a state from a child component from the parent component, but I don't want to run a render method in my parent or handle the state in the parent component.
Exists a way to achieve this? In my project, I have a parent that creates multiple generic children and it will be more difficult to handle this request.
And specifically, I need to change the state of one child (MyFirstChild), after another child (SecondChild) read the keystroke and run an API to get some values from my backend; after that, I need to send the change to "MyFirstChild" to change his state.
The parent component has ~50 child components and I blocked the re-render method (With the method shouldComponentUpdate)
The expected answer is: "It's not possible, or, you broke the good use of React"...
But, maybe using forwardRef or ref, or something else that I not see can help me to work around this...
To change the state of a child component from the parent without having to run a render method (in the parent): one possible solution would be to use Redux. With Redux from the parent you can dispatch an Action (execute an Action that changes the state of Redux).
And in the child component, you receive that part of the state that you change (it could be a string, object, etc) as a prop. So when it is changed from the parent, your child component will render again without having to run a render method in the parent.
https://react-redux.js.org/introduction/basic-tutorial
You could also use Context for the same purpose.
However, I saw your code example, I am not sure the exact reason of why you don't want to make a render in the parent, but the easiest solution for what you need would be to send the function that you want to execute in the parent as a prop and also the title.
If you want to change things from the parent, without re-rendering again with Redux, it would be something like this:
In the parent
const changeTitle = (newTitle) => {
this.props.setTitle(newTitle);
}
return (
<div className="App">
<ChildComponent />
</div>
);
const mapDispatchToProps = dispatch => ({
setTitle: newTitle => dispatch(setTitleACTION(newTitle)),
});
In the child
return (
<div>
<h1>
{this.props.title}
</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
const mapStateToProps = ({ title }) => ({
title,
});
export default connect(mapStateToProps, null)(ChildComponent);
Again if you make this with Redux, you can get the "title" prop from the Redux store, and in the parent, you will change that variable (calling to a Redux action) without rendering the parent again.
If you want to fire the event from the child component, you can dispatch the action from the child component or you could call a function (that you receive from props from the parent) and call that function whenever you need.
Can't we use props here for passing data instead of trying to manipulate state from outside of component?
Based on #david paley explanation...
If the only way to achieve this is using Redux, I post my solution (It's the same example, but, implementing Redux)... hope that works for anyone else.
https://codesandbox.io/s/change-state-from-external-component-redux-rmzes?file=/src/App.js

React createRef() vs callback refs. Is there any advantage of using one over the other?

I have started working on React recently and understood how refs can be used to get hold of a DOM node. In the React docs, they mention the two approaches of creating Refs. Can you please let me know in what situation a callback ref is better than createRef()? I find createRef to be simpler. Although the docs say "callback refs give you more fine grain control" I can't understand in what way.
Thank you
Besides what jmargolisvt said, one thing I found callback is very interesting that I can set multiple refs in an array so that I can control it better.
Like this:
class A extends React.Component {
constructor(props) {
super(props);
this.inputs = [];
}
render() {
return [0, 1, 2, 3].map((key, index) => (
<Input
key={key}
ref={input => this.inputs[index] = input}
/>)
);
}
}
createRef is returning either a DOM node or a mounted instance of a component, depending on where you call it. Either way, what you have in hand is indeed straightforward as you've noted. But what if you want to do something with that reference? What if you want to do it when the component mounts?
Ref callbacks are great for that because they are invoked before componentDidMount and componentDidUpdate. This is how you get more fine-grained control over the ref. You are now not just grabbing DOM elements imperatively, but instead dynamically updating the DOM in the React lifecycle, but with fine-grained access to your DOM via the ref API.
In terms of use cases, callback refs can do anything createRef can do, but not vice versa. createRef gives us a simplified syntax, but that's it.
Things you can't do with createRef:
React to a ref being set or cleared
Use an externally and internally provided ref on the same React element at the same time. (e.g. you need to measure a DOM element's clientHeight whilst, at the same time, allowing an externally provided ref (via forwardRef) to be attached to it.)
Practically you will see no difference except callback ref returns null before initial rendering.
This answer is a little biased on React-Native but still it is applicable if a React component similar to the following example.
<Animated.View> is a wrapper component for <View> that can be animated.
However if you want to access the <View> directly for something like calling the measure() method, then you can do it like:
interface State {
ref: View;
}
public render() {
<Animated.View ref={(component) => {
if (component !== null) {
this.state.ref = component.getNode();
}
}}
>
...
</Animated.View>
}
Otherwise, you need to do: this.state.ref.getNode().
TL;DR: you have control of what to do with an element or how to store it.
If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element. This is because a new instance of the function is created with each render, so React needs to clear the old ref and set up the new one. You can avoid this by defining the ref callback as a bound method on the class, but note that it shouldn’t matter in most cases.

How to handle props changes without using componentWillReceiveProps in React

I've been working on a project which is coded with React. I have a component set that I implemented many components for my own requirements. Many of these act like a composite component. For example, TextBox component which has its own label, own error message mechanism and own input filter etc. Moreover, you know, components have props to manage sth.
Everytime to update my component view (render), I use componentWillReceiveProps and I compare the props changes.
But everytime implementing the componentWillReceiveProps method is so repulsive.
Is there any way to pass props from top to down without using componentWillReceiveProps. I don't want to compare props changes manually. Is there any way to do it automatically.
When I change the props in parent, I'd like to update all views just changing the some prop values from top to down.
I'm not an react expert and performance is not my first purpose also!
One more thing that the answer is not use Redux!
I'm waiting your creative approaches and helpful ideas.
Without seeing the code for the particular thing you're working on, I may be missing something about what you're doing...
As others have commented, React will re-render your component if new props are provided, regardless of whether or not you implement componentWillReceiveProps -- the only reason to implement it is to do some kind of specific comparison or set a state based on new prop values.
From the React docs (emphasis mine):
componentWillReceiveProps() is invoked before a mounted component receives new props. If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.
Note that React may call this method even if the props have not changed, so make sure to compare the current and next values if you only want to handle changes. This may occur when the parent component causes your component to re-render.
In other words, if you have a component like:
<TextBox title={"Foo"} content={"Bar"} />
That internally passes prop changes on to a couple of child components like:
class TextBox extends React.Component {
render() {
return (
<div className={'text-box'}>
<Title text={this.props.title} />
<Body text={this.props.content} />
</div>
);
}
}
Then each time new props are passed to <TextBox>, <Title> and <Body> will also get re-rendered with their new text props, and there's no reason to use componentWillReceiveProps if you're just looking to update with prop changes. React will automatically see the changes and re-render. And React handles diffing and should fairly efficiently re-render only things that have changed.
However, if you have a separate state value that needs to be set in response to props, for example, if you wanted to show a "changed" state (or whatever) on the component if the new props are different, then you could implement componentWillReceiveProps, like:
class TextBox extends React.Component {
componentWillReceiveProps(nextProps) {
if (this.props.content !== nextProps.content) {
this.setState({changed: true});
}
}
render() {
const changed = this.state.changed ? 'changed' : 'unchanged';
return (
<div className={`text-box ${changed}`}>
<Title text={this.props.title} />
<Body text={this.props.content} />
</div>
);
}
}
If you're trying to prevent re-render in cases where it's unnecessary for performance, do as Andrey suggests and use shouldComponentUpdate: https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate
TLDR; unless you're setting component state from props, there's likely no need to run new props through componentWillReceiveProps
UPDATE Feb 2018: in a future release, React will be deprecating componentWillReceiveProps in favor of the new getDerivedStateFromProps, more info here: https://medium.com/#baphemot/whats-new-in-react-16-3-d2c9b7b6193b
There are few suggestions:
Don't copy props into state in componentWillReceiveProps - just render directly from this.props
If your component need performance tweak (and only if there is problem with performance):
start from using shouldComponentUpdate in generic form like advised here https://facebook.github.io/react/docs/shallow-compare.html
If generic approach doesn't work for you - write custom code
The general approach, how to develop text-box-like components is to keep it stateless.Component renders props directly, and notifies parent component about changes, it don't cares about managing value.
Hope this will help
Please consider pureComponent which by defualt implements the shouldComponentUpdate inside which shallow equals is used for comparison between previous and next
try following codes:
class MyComponent extends PureComponent {...}

React's setProps() deprecated warning

In parent component App I render child component PersonsTable:
var App = React.createClass({
addPerson: function() {
// building personToAdd object and passing to personsTable...
this.refs.personsTable.setState({personToAdd: person});
},
render: function() {
return (
<div>
<PersonsTable ref="personsTable" />
...
</div>
);
},
});
Now I'm passing a new object by calling setState method of PersonsTable.
If I call setProps method, it returns a warning
Uncaught Invariant Violation: setProps(...): You called setProps on a component with a parent. This is an anti-pattern since props will get reactively updated when rendered. Instead, change the owner's render method to pass the correct value as props to the component where it is created.
How do I correctly pass such an object to PersonsTable without using setState method?
Here is full code https://jsfiddle.net/jpt5uy1k/
It looks like you're trying to implement an anti-pattern by calling setState on a ref. It's better to pass props to the child and use those. If you need state, set it in a parent and pass it down as a prop.
<div>
<PersonsTable aProp={someCoolInfoOrParentState} />
...
</div>
Refs should be used only when you need to "reach out" and get hold of an actual DOM node for one reason or another. Also, you generally want state in as few places as possible, since it can be complicated and requires extra "machinery" to make work. Better to keep state in a single place and thread it down through other components.
See this from https://github.com/reactjs/react-basic:
We tend to prefer our data model to be immutable. We thread functions through that can update state as a single atom at the top.

Categories

Resources