Call methods on React children components - javascript

I want to write a Form component that can export a method to validate its children. Unfortunately a Form does not "see" any methods on its children.
Here is how I define a potential children of Form:
var Input = React.createClass({
validate: function() {
...
},
});
And here is how I define Form class:
var Form = React.createClass({
isValid: function() {
var valid = true;
this.props.children.forEach(function(component) {
// --> This iterates over all children that I pass
if (typeof component.validate === 'function') {
// --> code never reaches this point
component.validate();
valid = valid && component.isValid();
}
});
return valid;
}
});
I noticed that I can call a method on a child component using refs, but I cannot call a method via props.children.
Is there a reason for this React behaviour?
How can I fix this?

The technical reason is that at the time you try to access the child component, they do not yet really exist (in the DOM). They have not been mounted yet. They have been passed to your<Form> component as a constructor prop or method as a react class. (hence the name class in React.createClass()).
As you point out, this can be circumvented by using refs, but I would not recommend it. In many cases, refs tend to be shortcuts for something that react wasn't intended for, and therefore should be avoided.
It is probably by design that react makes it hard/ impossible for parents to access a child's methods. They are not supposed to. The child's methods should be in the child if they are private to the child: they do something inside the child that should not directly be communicated upward to the parent. If that were the case, than handling should have been done inside the parent. Because the parent has at least all info and data the child has.
Now in your case, I imagine each input (child) component to have some sort of specific validation method, that checks the input value, and based on outcome, does some error message feedback. Let's say a red outline around incorrect fields.
In the react way, this could be achieved as follows:
the <Form> component has state, which includes a runValidation boolean.
as soon as runValidation is set to true, inside a setState( { runValidation: true }); react automatically re-renders all children.
if you include runValidation as a prop to all children.
then each child can check inside their render() function with something like if (this.props.runValidation) { this.validate() }
which will execute the validate() function in the child
the validate function can even use the child's state (state is not changed when new props come in), and use that for the validation message (e.g. 'please add more complicated symbols to your password`)
Now what this does not yet fix, is that you may want to do some checking at form level after all children have validated themselves: e.g. when all children are OK, submit the form.
To solve that, you could apply the refs shortcut to the final check and submit. And implement a method in your <Form> inside a componentDidUpdate() function, to check if each child is OK (e.g. has green border) AND if submit is clicked, and then submit. But as a general rule, I strongly recommend against using refs.
For final form validation, a better approach is:
add a non-state variable inside your <Form> which holds booleans for each child. NB, it has to be non-state, to prevent children from triggering a new render cycle.
pass a validateForm function as a (callback) prop to each child.
inside validate() in each child, call this.props.validateForm(someChildID) which updates the corresponding boolean in the variable in the Form.
at the end of the validateForm function in the Form, check if all booleans are true, and if so, submit the form (or change Form state or whatever).
For an even more lengthy (and way more complicated) solution to form validation in react (with flux) you could check this article.

I'm not sure if i'm missing something, but after trying what #wintvelt suggested i ran into a problem whenever i called the runValidation method inside the render method of React, since in my case runValidation changes the state by calling setState in it, thus triggering the render method which obviously is a bad practice since render method must be pure, and if i put the runValidation in willReceiveProps it won't be called the first time because the if condition is not true yet (this condition is changed in the parent component using setState, but in the first call of willReceiveProps it's still false).

Related

Angular: How to check validation of child component

When check validation of child component, Which is better take a reference of child component and call its validate method or make an output with Boolean flag and handle it in parent component?
When you add reference to a child, you have following benefits:
You have access to all methods in child component.
You have access to other variables in the same.
You can reset the form after submitting.
In case of form control, you can play with validators.
I hope, you will get the answer from above all points.

How do I use an output value to show or hide the same component?

I'm getting an output boolean value from a child component in html.
Now I need to use the same value to hide that same tag where I'm getting it.
<!-- where I get the value. -->
<app-pcomponent (outageEvent)='receiveEvent($event)'></app-pcomponent>
...
receiveEvent($event) {
this.value = $event as boolean;
}
I need to use the same value i.e. $event/this.outage to show or hide the same component <app-pcomponent>.
just a short recap:
Your child component has a async call. The result of that call decides if the child should be shown.
There are quite a few things that are relevant:
The code in a component (in ngOnInit or in the constructor) will only be executed when the component is shown at least ones.
If you subscribe in a component some observable, it is a best practice to unsubscribe later (at least, when the component is destroyed). This is to safe guard yourself from memory leaks and "funny" behavior. Because if you don´t do that, and the user proceeds to some completly different pages in your application, then the subscription will STILL kick in. That means, that your async call (to the backend ?) will still be executed every few minutes.
if you "hide" your component by an *ngIf, then you do not hide it, you delete it from the DOM. As a result the "ngOnDestroy" method will be called. Thats the one where i would in most cases unsubscribe my subscriptions. With the destruction of your child component, the parent component will not get any notice about the async result. That means the *ngIf will stay false for ever...
if you "hide" your component by the HTML attribut "hidden" '''''' then the component will be created, but not visible for the user. As a result all codings (like the async call) will get executed. On the downside, even if this component is not currently shown, it will eat computation time (for the digestive cycle and everything else). Therefor it is a best practice to use "hidden" with care and try to avoid it where possible.
My personal solution would be to create two components.
The first child will handle the async call. It will (depending on the result of the call) show the second component. Thats the one with all the nice content.
As a result nothing from my components leak outside to the outer parent. My First component has only the task to handle the call and depending on the result show the child (the second component). If the second component needs data from the async call, it will be provided by a #Input() into the second component.
My personal best practice: Components are cheap, use them to seperate your code, make that way clean and maintainable.
warm regards
Jan
You can use *ngIf
<app-pcomponent (outageEvent)='receiveEvent($event)' *ngIf="value"></app-pcomponent>
receiveEvent(value: boolean) {
this.value = value;
}

Updating react state of a component-parent when several children sent events simultaneously

I'm using ReactJs with Redux.
There is a parent component, which has several children.
Children are just input elements with build-in validation.
<ParamInput
type="number"
value={item.capacity}
name="capacity"
min="1"
validators={[Validator.required]}
onValidation={this.validate}
/>
I'd like the parent to be aware which of its children are invalid.
For this each child can fire "onValidate" event, parent listens to this event and collects the validation data in his state. This works pretty good if the validate event fired by one child at a time.
However, once several children trigger this event at the same time, I run into a problem. For example, I run onValidation in componentDidMount of a child, to know validation information for an initial value.
Parent processes the event and updates its state:
validate(isValid, propertyName) {
const newValidityState = { ...this.state.validity, ...{[propertyName]: isValid} };
this.setState({validity : newValidityState });
}
The problem is in combination of following:
'validate' will be called for each child at the same time
I amend the existing value of this.state.validity
'setState' does not happen immediately
Thus, by the end the state will have data only about the last child which called 'onValidate', because at the time 'newValidityState' was created this.state.validity didn't get updates from other changes of state.
How can I overcome this? I'm aware that I can use promises for setState, but I don't see how I can apply them for this case.
Would be grateful for your suggestions.
This may not be an ideal solution. But give it a try. Instead of updating validity propery of state, state should have properties for each ParamInput
for eg: validation_propertyName and its value should be boolean indicating validity.
validate(isValid, propertyName) {
this.setState({["validation_"+propertyName]: isValid });
}

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

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.

How to perform child component validation without infinite loop in react.js

I have really simple example in react. Parent component pass data to the child component throug props. Inside child component i can change this data, also new data can be passed to the child component from the parent at any time. Also on the parent i have save button. After pressing it i should show validation message if some validations did not pass.
I have implemented it in such a way. From parent i pass callback to notify parent that validity of the data has changed. Inside child i perform validation in three places:
componentDidMount - to validate initial data
componentWillReceiveProps - to validate data that can be passed from the parent
onChange to validate entered data
Any time child perform validation of data i call callback to inform parent about validity. The problem is in componentWillReceiveProps. With setState in parent this part causes infinite loop - see picture below.
Please check jsfiddle Here in console you can see that i restricted infinite loop to 10 iterations to prevent you browser from crash.
As you can see there is infinite loop - because React call componentWillReceiveProps in not too smart way - every render cycle instead of callind it only when props actually changed.
I am really would like to know what is the react way to solve this issue. Should i store child state only in child ? I also tried to store child validity in parent out of state - but my coworkers said that this is not react way - why ?
Here's how I see your challenge:
you want to have your validation method in your child component, because it is specific to this type of child (e.g. a password input field). - but in react, no outside component (except grandchildren) are allowed to directly call this method.
you want your parent to know about the validaty of each of the children components (e.g. to determine of all the fields validate and a form submit is allowed) - so you need the result of the validation method in the parent component, and this must be state
Your co-workers are correct: react does not like you storing component-wide variables outside of state. Because such variables are completely independent from react lifecycle methods etc. and would get you in debugging hell quickly.
I would advise you to make following changes to prevent the endless loop:
do NOT store the validity of the child in the child state. Save the result + any message in the parent, and pass it as a props to the child. Child only renders the props.
implement shouldComponentUpdate(), which checks if any of the props or state variables have changed. If not return false, otherwise, return true.
move your call to validate() from componentWillReceiveProps to componentWillUpdate(). This ìs called after shouldComponentUpdate(). So only if props or child state have changed, will validation (and re-render) take place.

Categories

Resources