Update parent's state through callback prop from children (React.js) - javascript

I have a Page.jsx using a Form.jsx component:
<Form isValid={this.enableButton} isInvalid={this.disableButton}>
<Input validation={{ presence: true }} />
</Form>
The point is: Form needs to check each Input validness to proceed. To achieve this, I am doing this in Form.jsx:
// ...
allInputsAreValid: function () {
return _.all(this.state.inputsValidation, function (inputsValidation) {
return inputsValidation.error === false;
});
}
// ...
Then, in render method of Form.jsx:
if (this.allInputsAreValid()) {
this.props.isValid();
} else {
this.props.isInvalid();
}
Finally, the methods enable/disableButton (on Form.jsx):
// ...
enableButton: function () {
this.setState({
canSubmit: true
});
},
disableButton: function () {
this.setState({
canSubmit: false
});
}
//...
Changing the state in these methods, the console throws an error:
Uncaught Error: Invariant Violation: setState(...): Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
Why? How to fix?

I am assuming "page.jsx" contains methods "enableButton" and "disableButton".
while updating the state do you have any other state properties apart from "canSubmit"? if you have other state properties along with "canSubmit" you are forcing them to become undefined by doing
this.setState({
canSubmit: true
);
So i am assuming there is a state property called "xyz" existed along with "canSubmit" state property. so update the state like below
this.setState({
canSubmit: true,
xyz:this.state.xyz
);
or better you try to use react update addon to update state. more find here https://facebook.github.io/react/docs/update.html
Update
Try the below code in your page.jsx file
shouldComponentUpdate(object nextProps, object nextState){
if(this.state.canSubmit==nextState.canSubmit){
return false
}
}

You need to move this logic out of the render method:
if (this.allInputsAreValid()) {
this.props.isValid();
} else {
this.props.isInvalid();
}
Update:
Let's modify your <Input /> components to accept an onChange prop. I'm not sure if you are using ES6 classes or React.createClass, but I will go the ES6 classes route here.
class Input extends React.Component {
render() {
return <input type="text" onChange={this.props.onChange} />;
}
}
Then modify your Form.jsx to give the inputs an onChange property.
<Form isValid={this.enableButton} isInvalid={this.disableButton}>
<Input
validation={{ presence: true }}
onChange={() => {
if (this.allInputsAreValid()) {
this.props.isValid();
} else {
this.props.isInvalid();
}
}}
/>
</Form>

Related

React ref to component not exposing method

I'm using a ref to component to imperatively trigger a reset on a filter form from its parent.
In the same component we have:
handleFilterReset() {
// this.filterForm is defined but reset() isn't exposed
// see console.log(this.filterForm) output below
this.filterForm.reset()
}
render() {
return (
<FilterBox onReset={::this.handleFilterReset}>
<FilterForm ref={(ref) => { this.filterForm = ref }} />
</FilterBox>
)
}
And in FilterForm we have:
class FilterForm extends React.Component {
reset() {
// this is not being called
}
}
console.log output:
ProxyComponent {props: Object, context: Object, refs: Object, updater: Object, _reactInternalInstance: ReactCompositeComponentWrapper…}
It seems to me that everything is done according to the official docs. However, I get the following error:
Uncaught TypeError: this.filterForm.reset is not a function
at SalesChannelsList.handleFilterReset
Thanks
Found it ! It was "because" of React Intl.
1) Use the withRef option set to true when using injectIntl:
injectIntl(SalesChannelsFilterForm, { withRef: true })
2) In the ref callback of your component, you can access your instance with the following code
ref={(ref) => this.filterForm = ref.refs.wrappedInstance}
However this will crash because the ref callback is being called twice during the render(), the 1st time with a null value. So you should 1st verify that it has been defined. My complete solution:
In the render() method, on the component:
ref={::this.setFilterFormRef}
Then the handler:
setFilterFormRef(ref) {
if (ref && ref.refs) {
this.filterForm = ref.refs.wrappedInstance
}
}
Enjoy!
I would use ref="refName" instead of having a function (which is created again every render) and then access it through this.refs.
Anyway, here is a working example:
https://www.webpackbin.com/bins/-KjHtMcw3LcVEycggzWU

sync state with props to achive two way binding for form input in reactjs

i have a very long form (75 input to be exact), since i'm using redux to manage my application state, whenever i want to edit this form i want to setState of form to the prop to allow editing.
Example Code:
class VisitCard extends Component {
constructor(props) {
super(props); //props.visit = {name:'randome name', data:'etc..'}
this.state = Object.assign({},props.visit);
this.bindInput = this.bindInput.bind(this);
}
//BindInput will return props for Inputs, to achive two way binding
bindInput(config){
const {name,...props} = config;
return {
value : this.state[name],
onChange: event => this.setState({[name]:event.target.value}),
...props
}
}
render(){
return <div>
<input {...this.bindInput({name:'name', type:'text'})} />
<input {...this.bindInput({name:'data', type:'text'})} />
</div>
}
}
above code works perfect, problem is when this component mounts, it give me error "Cannot update during an existing state transition"
also sometimes if the value is not predefined in the props, the value for input will be undefined, so after props load from server and updates component i get another error "trying to change input from uncontrolled to controled" that is because this.state[name] was undefined then i got a value
so what am'i doing wrong ? how can i link the state of component with props value and make sure that if props changed, state does change too, while at sametime, if state changes this does not affect props.
I hope modify your code to match the below logic will resolve your issues. Look for comments inside the code for explanations
class VisitCard extends Component {
constructor(props) {
super(props);
//set your state to have a key that holds your prop value.
this.state = { visit: props.visit };
this.bindInput = this.bindInput.bind(this);
}
componentWillReceiveProps(nextProps) {
//if your props is received after the component is mounted, then this function will update the state accordingly.
if(this.props.visit !== nextProps.visit) {
this.setState({visit: nextProps.visit});
}
}
bindInput(config){
const {name,...props} = config;
// return defaultValue which you get from the props.
// you can add `value: this.state.visit[name]` to the below object only if you want your input to be controlled, else it can be ignored.
return {
defaultValue : this.props.visit[name],
onChange: event => this.setState(
{visit: { ...this.state.visit,
[name]:event.target.value}
}),
...props
}
}
render(){
// render empty if your props has not yet arrived.
if(!this.props.visit) {
return (<div />);
}
// render after you have values in props
return (<div>
<input {...this.bindInput({name:'name', type:'text'})} />
<input {...this.bindInput({name:'data', type:'text'})} />
</div>);
}
}

"Cannot update during an existing state transition" error in React

I'm trying to do Step 15 of this ReactJS tutorial: React.js Introduction For People Who Know Just Enough jQuery To Get By
The author recommends the following:
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
return (
<div className="alert alert-warning">
<strong>Oops! Too Long:</strong>
</div>
);
} else {
return "";
}
},
render() {
...
{ this.overflowAlert() }
...
}
I tried doing the following (which looks alright to me):
// initialized "warnText" inside "getInitialState"
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
this.setState({ warnText: "Oops! Too Long:" });
} else {
this.setState({ warnText: "" });
}
},
render() {
...
{ this.overflowAlert() }
<div>{this.state.warnText}</div>
...
}
And I received the following error in the console in Chrome Dev Tools:
Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be
a pure function of props and state; constructor side-effects are an
anti-pattern, but can be moved to componentWillMount.
Here's a JSbin demo. Why won't my solution work and what does this error mean?
Your solution does not work because it doesn't make sense logically. The error you receive may be a bit vague, so let me break it down. The first line states:
Cannot update during an existing state transition (such as within render or another component's constructor).
Whenever a React Component's state is updated, the component is rerendered to the DOM. In this case, there's an error because you are attempting to call overflowAlert inside render, which calls setState. That means you are attempting to update state in render which will in then call render and overflowAlert and update state and call render again, etc. leading to an infinite loop. The error is telling you that you are trying to update state as a consequence of updating state in the first place, leading to a loop. This is why this is not allowed.
Instead, take another approach and remember what you're trying to accomplish. Are you attempting to give a warning to the user when they input text? If that's the case, set overflowAlert as an event handler of an input. That way, state will be updated when an input event happens, and the component will be rerendered.
Make sure you are using proper expression. For example, using:
<View onPress={this.props.navigation.navigate('Page1')} />
is different with
<View onPress={ () => this.props.navigation.navigate('Page1')} />
or
<View onPress={ () => {
this.props.navigation.navigate('Page1')
}} />
The two last above are function expression, the first one is not. Make sure you are passing function object to function expression () => {}
Instead of doing any task related to component in render method do it after the update of component
In this case moving from Splash screen to another screen is done only after the componentDidMount method call.
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Button,
Image,
} from 'react-native';
let timeoutid;
export default class Splash extends Component {
static navigationOptions = {
navbarHidden: true,
tabBarHidden: true,
};
constructor(props) {
super(props)
this.state = { navigatenow: false };
}
componentDidMount() {
timeoutid=setTimeout(() => {
this.setState({ navigatenow: true });
}, 5000);
}
componentWillUnmount(){
clearTimeout(timeoutid);
}
componentDidUpdate(){
const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}
}
render() {
//instead of writing this code in render write this code in
componenetDidUdpate method
/* const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}*/
return (
<Image style={{
flex: 1, width: null,
height: null,
resizeMode: 'cover'
}} source={require('./login.png')}>
</Image>
);
}
}
Call the component props at each time as new render activity. Warning occurred while overflow the single render.
instead of
<Item onPress = { props.navigation.toggleDrawer() } />
try like
<Item onPress = {() => props.navigation.toggleDrawer() } />
You can also define the function overflowAlert: function() as a variable like so and it will not be called immediately in render
overflowAlert = ()=>{//.....//}

React render method that depends on asynchronous request and state change

I am trying to learn ReactJS and Redux, and have come across a problem that I cannot seem to get over.
I have a React component, that gets data from an asynchronous request.
export class MyPage extends React.Component {
constructor(props) {
super(props)
this.state = {
enableFeature: false,
}
this.handleEnableFeatureChange = this.handleEnableFeatureChange.bind(this)
}
componentWillMount () {
this.fetchData()
}
fetchData () {
let token = this.props.token
this.props.actions.fetchData(token)
}
handleEnableFeatureChange (event) {
this.setState({ enableFeature: event.target.checked })
}
render () {
if (this.props.isFetching) {
return (
<div>Loading...</div>
)
} else {
return (
<div>
<label>Enable Feature
<input type="checkbox"
className="form-control"
checked={this.props.enableFeature}
onChange={this.handleEnableFeatureChange}
/>
</label>
</div>
)
}
}
}
So, my problem now is that, when I change the state of the checkbox, I want to update the state of my data. However, every time I update the state of my data, the react component method shouldComponentUpdate kicks in, and uses the current props to render the original data.
I would like to see how such cases are handled in general.
Thanks.
Try to do it like the following, i.e.
Use componentWillReceiveProps to assign props.enableFeature to state.enableFeature. From documentation
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.
componentWillReceiveProps() is not invoked if you just call this.setState()
Use this state to load the value of checkbox
Manipulate this state (onchange) to update the value of checkbox
Following code can work in your case
export class MyPage extends React.Component {
static propTypes = {
isFetching: React.PropTypes.bool,
enableFeature: React.PropTypes.bool,
token: React.PropTypes.string,
actions: React.PropTypes.shape({
fetchData: React.PropTypes.func
})
};
state = {
enableFeature: false,
};
componentWillMount () {
this.fetchData();
}
/* Assign received prop to state, so that this state can be used in render */
componentWillReceiveProps(nextProps) {
if (this.props.isFetching && !nextProps.isFetching) {
this.state.enableFeature = nextProps.enableFeature;
}
}
fetchData () {
const { token } = this.props;
this.props.actions.fetchData(token)
}
handleEnableFeatureChange = (event) => {
this.setState({ enableFeature: event.target.checked })
};
render () {
return (<div>
{ this.props.isFetching && "Loading..." }
{
!this.props.isFetching && <label>
Enable Feature
<input
type="checkbox"
className="form-control"
checked={this.state.enableFeature}
onChange={this.handleEnableFeatureChange}
/>
</label>
}
</div>);
}
}
Note: The above code was not executed, but should work (babel's stage-0 code)
Change it to checked={this.state.enableFeature}

React setting input's value

I've seen on many React tutorials regarding managing input values. The following pattern:
On Parent passing props to Input Component, handleInputText sets the state for anyValue:
<InputComponent textValue={this.state.anyValue} onInputtingText={this.handleInputText}/>
On Input Component, onEvent can be ==> onChange, onBlur...:
<input type='text' ref='inputRef' value={this.props.textValue} onEvent={this.handleInput}/>
InputComponent's handleInput:
handleInput(){
this.setState(this.refs.inputRef.value)
}
Now my findings, I try to log it when the parents function when setting the state and it logs the initial. This are some tentative conclusions:
Whenever an event its trigger the value of the input is not the current value of the InputComponent. It is the value set on the parent to that value.
Both the input's value and this.props.textValue match on the second triggering of the event.
My question is, how do you handle this the react way? Or do you have to check this inside the handleInput function?
Thanks in advance.
You can set state in this.handleInputText and call it inside InputComponent,
var App = React.createClass({
getInitialState() {
return { anyValue: '' };
},
handleInputtingText(value) {
this.setState({ anyValue: value });
},
render() {
return <div>
<p>{ this.state.anyValue }</p>
<InputComponent
textValue={ this.state.anyValue }
onInputtingText={ this.handleInputtingText }
/>
</div>
}
});
var InputComponent = React.createClass({
handleInput(e) {
this.props.onInputtingText(e.target.value);
},
render: function() {
return <input
type="text"
value={this.props.textValue}
onChange={ this.handleInput }
/>;
}
});
Example

Categories

Resources