React - How to pass `ref` from child to parent component? - reactjs

I have a parent and a child component, I want to access the ref of an element which is in the child component, in my parent component. Can I pass it with props?
// Child Component (Dumb):
export default props =>
<input type='number' ref='element' />
// Parent Component (Smart):
class Parent extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const node = this.refs.element; // undefined
}
render() {
return <Dumb { ...this.props }/>
}
}

You could use the callback syntax for refs:
// Dumb:
export default props =>
<input type='number' ref={props.setRef} />
// Smart:
class Parent extends Component {
constructor(props) {
super(props);
}
setRef(ref) {
this.inputRef = ref;
}
render(){
return <Dumb {...this.props} setRef={this.setRef} />
}
}

With react^16.0.0 you would use React.createRef(). Using #Timo's answer, it would look like this:
// Dumb:
export default props =>
<input type='number' ref={props.setRef} />
// Smart:
class Parent extends Component {
constructor(props) {
super(props);
this.ref1 = React.createRef()
}
render(){
return <Dumb {...this.props} setRef={this.ref1} />
}
}

As per DOC:
You may not use the ref attribute on functional components because
they don't have instances. You should convert the component to a class
if you need a ref to it, just like you do when you need lifecycle
methods or state.
So i think, if you want to use the ref, you need to use class.
Check this: https://github.com/facebook/react/issues/4936

If you need dynamic refs, because you have an array or something, like I did. Here is what I came up with after reading the answers above.
Also this assumes the myList is an array of objects with a key property. Anyways you get it.
Also this solution works without any issues from TypeScript as well.
const Child = props => <input ref={refElem => setRef(props.someKey, refElem)} />
class Parent extends Component {
setRef = (key, ref) => {
this[key] = ref; // Once this function fires, I know about my child :)
};
render(){
return (
{myList.map(listItem => <Child someKey={listItem.key} setRef={this.setRef} />)}
)
}
}
Anyways hope this helps someone.

Related

Render child component in parent after re-rendering sibling component

I have a parent component housing two children components(AddPersonForm and PeopleList). When I submit a name via the AddPersonForm, I expect it to be rendered in the PeopleList component, but it doesn't.
Here is my AddPersonForm:
class AddPersonForm extends React.Component {
state = {
person: ""
}
handleChange = (e) => this.setState({person: e.target.value});
handleSubmit = (e) => {
if(this.state.person != '') {
this.props.parentMethod(this.state.person);
this.setState({person: ""});
}
e.preventDefault();
}
render() {
return (
<form onSubmit={this. handleSubmit}>
<input type="text" placeholder="Add new contact" onChange={this.handleChange} value={this.state.person} />
<button type="submit">Add</button>
</form>
);
}
My PeopleList component:
class PeopleList extends React.Component {
constructor(props) {
super(props);
const arr = this.props.data;
this.state = {
listItems: arr.map((val, index) => <li key={index}>{val}</li> );
}
}
render() {
return <ul>{this.state.listItems}</ul>;
}
}
Now the parent component, ContactManager:
class ContactManager extends React.Component {
state = {
contacts: this.props.data
}
addPerson = (name) => {
this.setState({contacts: [... this.state.contacts, name]});
render() {
return (
<div>
<AddPersonForm parentMethod={this. addPerson}×/>
<PeopleList data={this.state.contacts} />
</div>
);
Please what I'm I doing wrong, or not doing?
The issue is in your PeopleList component. The state object which renders your list is created in the constructor when the component mounts, but you have no way of updating it when it recieves new values. It will always give you the initial value.
You could introduce a lifecycle method, componentDidUpdate, which would allow you to compare the previous props to the new props when they arrive, and update the state accordingly. I would recommend you not do this for two reasons:
Storing props directly in a components state is not good practice. You are just creating a copy of the state in the component above and that creates opportunities for confusion and stale values when one of them updates. Ideally, each piece of data should live in only one place.
If all PeopleList is doing is rendering your data, then it doesn't need any state at all. It can act as a display component that maps your props in place and doesn't have to worry about updating itself or managing its own data. This would actually make it a good candidate for conversion into a functional component.
class PeopleList extends React.Component {
render() {
return (
<ul>
{this.props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}
}
You are initializing PeopleList with props when its created and mounted but then you are not using new values of props for updating it.
To fix your issue use current value of prop when rendering:
class PeopleList extends React.Component {
render() {
return <ul>{ this.props.data.map((val, index) => <li key={index}>{val}</li>) }</ul>;
}
}

Is it safe to pass in React component class instance to child component to access to the parent's variables and methods?

I am trying to get data from parent component. I know that I can pass in functions or variables to the child component but its looks cleaner to just pass in this to child component to access to its parent functions or variables.
class Parent extends Component {
state = {
_id: 123456789
}
foo = (val) => {
this.setState({_id:val})
}
render() {
return(
<Child parentInstance={this} />
)
}
}
class Child extends Component {
update = (e) => {
this.props.parentInstance.foo(e.target.value);
}
render() {
return(
<p>{this.props.parentInstance.state._id}</p>
<input onChange={this.update} />
)
}
}
If you find yourself passing this down (as I did many times when learning React!), you’ll find it useful to re-read Thinking in React.
In this instance, you should be passing the ID down as a prop. When the state changes, React knows that it’s used to calculate that prop and so will re-render the child component.
As for the callback, that’s best handled as an individual function. If you find yourself passing many callbacks around in your app, you may want to look into using a more complex state management system than just Reacts internal state values.
class Parent extends Component {
state = {
_id: 123456789
}
foo = (val) => {
this.setState({ _id: val })
}
render() {
return(
<Child parentId={this.state._id} onChange={this.foo} />
)
}
}
class Child extends Component {
update = (e) => {
this.props.onChange(e.target.value);
}
render() {
return(
<p>{this.props.parentId}</p>
<input onChange={this.update} />
)
}
}
It is more typical to pass properties and callbacks as props, not the instance itself.
i.e. <Child parentId={this.state._id} onUpdate={this.foo} />

How to setState for all instances of the same component type in that component

How to setState for all instances of the same component type in that component.
In ParentComponent
render() {
return(
<ChildComponent ... />
<ChildComponent ... />
<ChildComponent ... />
);
}
In ChildComponent
//onClick Handler should set state of all instances
onClick() {
this.setState({value: ''})
}
If you have some value which is used by multiple child components, then the correct way is to take that value one level up (i.e. in parent) and pass that value to those child as prop so that all children share the same value. So maintain a state in parent and pass them as prop to children like this
onClick() {
this.setState({value: ''})
}
render() {
return(
<ChildComponent value={this.state.value} onClick={this.onClick}... />
<ChildComponent value={this.state.value} onClick={this.onClick}... />
);
}
Ok, so...
I'm working on some kind of picker.
There are 3 components of the same type. Each component stores different state.
States depends on what user typed in input field (combined with react-autosuggest).
User fill up 3 inputs, and choose 1 image that is rendered depends on state.
After user click image, all inputs should be cleared (value is in state).
I made that working but not rly satisfied
and this is combined with redux
Parent
I made a ref to each component and save a instance to his state and pass callback to trigger methods in all child instances.
class Parent extends Component {
constructor(props) {
super(props);
this.state = {};
this.clearAllInputs = this.clearAllInputs.bind(this) // coz callback returns child own props
}
componentDidMount() {
this.setState({
firstChild: this.firstChild.getWrappedInstance(),
secondChild: this.secondChild.getWrappedInstance(),
thirdChild: this.thirdChild.getWrappedInstance(),
})
}
clearAllInputs() {
//call methods from all child instances
this.state.firstChild.clearInput();
this.state.secondChild.clearInput();
this.state.thirdChild.clearInput();
}
...
render() {
return(
<Child ref={ context => this.firstChild = context } clearAllInputs={this.clearAllInputs} ... />
<Child ref={ context => this.secondChild = context } clearAllInputs={this.clearAllInputs} ... />
<Child ref={ context => this.thirdChild = context } clearAllInputs={this.clearAllInputs} ... />
);
}
...
}
class Child extends Component {
...
clearInput() {
this.setState( { value : '' } );
}
render() {
return(
...
<img ... onClick={ this.props.clearAllInputs } />
);
}
}
export default connect(state, null, dispatchers, { withRef: true })(Child);
Since you want the same state in all of the child instances, I'd say that what you want to do is actually set the state in the parent, then pass that into all of the children as a prop. You'll need a click handler method in the parent, which you'll pass to the children as well.
Ok, I haven't tested this code, but the basic logic will be something like:
Parent
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
this.state = {
"value": "" // assuming 'value' is a string
}
}
handleClick(value) {
this.setState({ "value": value })
}
render() {
return(
<ChildComponent
handleClick={this.handleClick}
value={this.state.value} />
<ChildComponent
handleClick={this.handleClick}
value={this.state.value} />
<ChildComponent
handleClick={this.handleClick}
value={this.state.value} />
)
}
Child (since you talk about state of a child, setting this up as though it is a stateful component, not a presentational component)
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
this.state = {
"value": "" // assuming 'value' is a string
}
}
componentWillReceiveProps(nextProps) {
this.setState( {"value": nextProps.value} )
}
handleClick() {
const value = "Hey here's a value!"
props.handleClick(value) // call the parent's handleClick
}
render() {
return(
<div>
<button onClick={this.handleClick}>Set value</button>
</div>
)
}
But truth be told, I wouldn't even bother setting the state in the child - just set it in the parent and access it via props.

React handle state update

I'm using the react to build some input forms.
While all children inputs have and their own states to store values I have no idea how to process the to a parent.
Here's example:
class FormComponent extends Component {
constructor(props) {
super(props);
this.state = {
title: null,
someAmount: null
}
}
render() {
let me = this;
return (
<div>
<TextField
value={me.state.title}
onChange={(proxy, value) => {
me.setState({title: value})
me.hanleChnage();
}
}
/>
<TextField
value={Number.parseFloat(me.state.someAmount)}
onChange={(proxy, value) => {
if (!isNaN(Number.parseFloat(value))) {
me.setState({someAmount: value})
me.hanleChnage();
}
}
}
/>
</div>
)
}
handleChange() {
//Calling the parent
//State here is outdated
this.props.onUpdate && this.props.onUpdate(this.state);
}
}
export default FormComponent;
Or where I can find some example of usage of compex forms with much inputs in react.
Thanks!
Sounds like you need to consider moving some of your state into the parent components. The React docs have a good article about this.
To summarize, you can pass your hanleChnage(); function as a prop to your child components if you declare the function in your parent.
function handleChange() { //do something... }
...
<ChildComponent parentOnChange={this.handleChange.bind(this) />
As your components grow in complexity, you might consider using Redux for state management, thus serving as a single source for all state in your application.
Set a child property, (e.g. callParentProperty) to reference a function in the parent component (e.g. parentFunction).
class ParentComponent extends Component{
parentFunction(parameter) {
console.log("This is the form value");
console.log(parameter);
}
render() {
return <FormComponent callParentFunctionProperty={this.parentFunction.bind(this)} />
}
}
class FormComponent extends Component {
...
handleChange() {
...
let formValue = this.state.someAmount;
this.props.callParentFunctionProperty(formValue);
}
}

Context lost in parent component when called from child

I've got a parent component which feeds a onSomeAction prop to a child component:
export default class myComponent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="mycomponent">
<ChildComponent onSomeAction={this.doSomething} />
</div>
)
}
doSomething() {
console.log(this);
}
}
In the child component when something is clicked on I'm calling a method whiih in turns calls the onSomeAction prop:
export default class ChildComponent extends Component {
render() {
return (
<div className="">
<a onClick={() => this.doIt()}>doIt</a>
</div>
)
}
doIt() {
const { onSomeAction } = this.props;
onSomeAction();
}
}
The problem I'm seeing is back in the parent component the this context seems to have been lost - the console.log in the doSomething method returns undefined. Why is this? I need to be able to access
the parent component context.
You need set context in parent component, you can do it with .bind
<ChildComponent onSomeAction={ this.doSomething.bind(this) } />
^^^^^^^^^^^^
Example
There are two options for you on how you can get the element that has been clicked or the whole component scope this.
option one:
instead of logging this you should logg the event target like so:
doSomething(e) {
console.log(e.target);
}
option two:
you have to attach the this keyword to the doSomething method like so:
constructor(props) {
super(props);
this.doSomething = this.doSomething.bind(this);
}
That was you'll be able to access the keyword this in your do something method.
Option3:
If you want to refer with this to the child component then you have to bind(this) on the function call int he child component
Actually you can fix it 3 ways:
<ChildComponent onSomeAction={ this.doSomething.bind(this) } />
<ChildComponent onSomeAction={ () => this.doSomething() } />
<ChildComponent onSomeAction={this.doSomething} />
and add this to constructor: this.doSomething = this.doSomething.bind(this)

Resources