React render is not called on supposed setState call - javascript

I am trying to implement a button which switches between two displayed forms.
However, this is not working, as no change occurs on button click.
I have the following code:
import React, {Component} from "react";
import ShortenForm from "./ShortenForm";
import UnshortenForm from "./UnshortenForm";
class FormSelector extends Component {
constructor(props) {
super(props);
this.state = {
shorten: this.props.shorten // Pass true or false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({shorten: event.target.value});
}
render() {
let form;
let button;
if (this.state.shorten) {
form = <ShortenForm placeholder='Enter URL to shorten'/>;
button = <button onClick={this.handleChange} value={false}>Change</button>;
} else {
form = <UnshortenForm placeholder='Enter URL to unshorten'/>;
button = <button onClick={this.handleChange} value={true}>Change</button>;
}
return (
<React.Fragment>
{form}
{button}
</React.Fragment>
);
}
}
export default FormSelector;

You should use updater argument of setState in this case:
this.setState((state, props) => {
return {shorten: !state.shorten};
});
As, from docs:
updater argument: (state, props) => stateChange
Both state and props received by the updater function are guaranteed
to be up-to-date. The output of the updater is shallowly merged with
state.

In your handleChange function, you're setting state to the value of event.target.value, but the value is a string, so both 'true' and 'false' will return true.
This means that this.state.shorten will always be true, so the ShortenForm will always render.
You shouldn't be using the value of a button to determine state, as the value attribute is treated differently across browsers.
You also don't need to create two different buttons and choose which one to render based on state. Just render the same button that calls the same function every time. All the function does is invert the current state value:
handleChange = () => this.seState(prev => ({ shorten: !prev.shorten })
You don't need to provide a value to the button:
return (
<>
{form}
<button onClick={this.handleChange}>Change</button>
</>
)

I see a few answers here, but I think your key question could still be addressed. So I'm going to try and do that here, and maybe consolidate all this information just a bit.
The value prop that you're passing is actually a string, not a boolean value. Hence, you're assigning the string "true" when you mean to be assigning the boolean true - this is what messes up your if condition, as in Javascript (as in other languages) a non-empty string is what we call a truthy value, and hence your if condition will always evaluate to true. This is your key issue here, above all else. Replacing this with a boolean value and negating it (as the other answers show) most definitely works, but this won't work when you have multiple forms. If this is the case, I would use string identifiers, embracing the fact that value passes a string, and render different forms accordingly.
I've modified your class, this should give you a good idea of what I mean. You can also find a CodeSandbox here, which should give you a slightly more interactive idea
import React, { Component } from "react";
class FormSelector extends Component {
constructor(props) {
super(props);
this.state = {
shorten: this.props.shorten
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
console.log("Setting: " + event.target.value);
this.setState({ shorten: event.target.value });
}
render() {
// This bit is just for illustrative purposes
console.log(this.state.shorten);
console.log(typeof this.state.shorten);
if (this.state.shorten === "short") {
return (
<React.Fragment>
<span>Shortened</span>
<button onClick={this.handleChange} value={"unshort"}>
Change
</button>
</React.Fragment>
);
} else {
return (
<React.Fragment>
<span>Unshortened</span>
<button onClick={this.handleChange} value={"short"}>
Change
</button>
</React.Fragment>
);
}
}
}
export default FormSelector;

Related

Pass state from class component to function component in react

I have a class where I declare:
constructor() {
super();
this.state = {
checked: false,
house: [],
selectedHouse: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(checked) {
this.setState({ checked });
}
render() {
return (
<React.Fragment>
<TSwitch handleChange={this.handleChange.bind(this)} house={this.state.house} houseClicked={this.h}></TSwitch>
</React.Fragment>
);
}
I then want to set state.checked from a child component:
function TSwitch(props) {
const handleChange = (house) => (evt) => {
props.handleChange(house);
};
return (
<div>
{props.house.map((house) => {
return (
<label>
<span>Switch with default style</span>
<Switch onChange={handleChange} checked={this.state.checked} />
</label>
);
})}
</div>
);
}
I am able to call handleChange but I want to be able to change the value of state.checked from the <TSwitch/> component.
This is what your parent component should be like:
constructor() {
super();
this.state = {
checked: false,
house: [],
selectedHouse: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(checked) {
this.setState({ checked });
}
render() {
return (
<React.Fragment>
<TSwitch handleChange={this.handleChange} isChecked={this.state.checked} house={this.state.house}></TSwitch>
</React.Fragment>
);
}
This is what your child component should look like:
function TSwitch(props) {
return (
<div>
{props.house.map((house) => {
return (
<label>
<span>Switch with default style</span>
<Switch onChange={x => props.handleChange(x)} checked={props.isChecked} />
</label>
);
})}
</div>
);
}
NOTE: You are using a Switch component, I'm not sure if the variable x will be a boolean or an object, but most probably it should be a boolean: true or false. If this doesn't work, log the value of x & see if its an object, and pass the boolean in props.handleChange. Although I still think this won't be needed. Good luck!
1.
Let's start with your direct question
I want to be able to change the value of state.checked from the <TSwitch/> component
1.1 You've correctly passed your mutator function handleChange from the Parent class to TSwitch but your abstraction function handleChange inside that child, that you've duplicated, is unnecessary and should be removed completely.
1.2 Next, going back to the class' handleChange function, you need to modify the handleChange function definition in the parent component, by fixing the argument you passed it -- which will be the event object, passed implicitly since you registered it as a callback to onChange={handleChange} inside Tswitch. At invocation time, it will be called, and the evt argument that's given to onChange from React, will be passed into handleChange. But, you don't need it. It carries no information of necessity to you. So I would ignore it entirely.
// # parent component
handleChange(evt) {
// NOTE: i'm going to ignore the evt, since I don't need it.
// NOTE: i'm going to use optional callback given by setState, to access prevState, and toggle the checked state boolean value.
this.setState((prevState) => ({ checked: !prevState.checked }));
}
2.
Now let's clean up your code and talk about some best practices
2.1 You dont' need to be using React.Fragment here. Why? because Fragments were introduced in React 16 to provide a declarative API for handling lists of components. Otherwise, they're unecessary abstractions. Meaning: if you're not directly dealing with sibling components, then you don't need to reach for React.Fragment just go with a <div/> instead; would be more idiomatic.
2.2. If <TSwitch></TSwitch> isn't going to have a direct descendent, then you should change your usage syntax to <TSwitch/>.
2.3 If 2.2 didnt' get picked up by a linter, then I highly advised you install one.
2.4 You can continue using explicit bindings of your class handlers in your constructor if you'd like. It's a good first step in learning React, however, there's optimal ways to remove this boilerplate via Babel's transform properties plugins.
This will work:
handleChange(checked) {
this.setState({ checked:!checked });
}

React setState re-render

First of all, I'm really new into React, so forgive my lack of knowledge about the subject.
As far as I know, when you setState a new value, it renders again the view (or parts of it that needs re-render).
I've got something like this, and I would like to know if it's a good practice or not, how could I solve this kind of issues to improve, etc.
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
key: value
}
this.functionRender = this.functionRender.bind(this)
this.changeValue = this.changeValue.bind(this)
}
functionRender = () => {
if(someParams !== null) {
return <AnotherComponent param={this.state.key} />
}
else {
return "<span>Loading</span>"
}
}
changeValue = (newValue) => {
this.setState({
key: newValue
})
}
render() {
return (<div>... {this.functionRender()} ... <span onClick={() => this.changeValue(otherValue)}>Click me</span></div>)
}
}
Another component
class AnotherComponent extends Component {
constructor (props) {
super(props)
}
render () {
return (
if (this.props.param === someOptions) {
return <div>Options 1</div>
} else {
return <div>Options 2</div>
}
)
}
}
The intention of the code is that when I click on the span it will change the key of the state, and then the component <AnotherComponent /> should change because of its parameter.
I assured that when I make the setState, on the callback I throw a console log with the new value, and it's setted correctly, but the AnotherComponent doesn't updates, because depending on the param given it shows one thing or another.
Maybe I need to use some lifecycle of the MyComponent?
Edit
I found that the param that AnotherComponent is receiving it does not changes, it's always the same one.
I would suggest that you'll first test it in the parent using a simple console.log on your changeValue function:
changeValue = (newValue) => {
console.log('newValue before', newValue);
this.setState({
key: newValue
}, ()=> console.log('newValue after', this.state.key))
}
setState can accept a callback that will be invoked after the state actually changed (remember that setState is async).
Since we can't see the entire component it's hard to understand what actually goes on there.
I suspect that the newValue parameter is always the same but i can't be sure.
It seems like you're missing the props in AnotherComponent's constructor. it should be:
constructor (props) {
super(props) // here
}
Try replacing the if statement with:
{this.props.param === someOptions? <div>Options 1</div>: <div>Options 2</div>}
also add this function to see if the new props actually get to the component:
componentWillReceiveProps(newProps){
console.log(newProps);
}
and check for the type of param and someOptions since you're (rightfully) using the === comparison.
First, fat arrow ( => ) autobind methods so you do not need to bind it in the constructor, second re-renders occur if you change the key of the component.
Ref: https://reactjs.org/docs/lists-and-keys.html#keys

React state with calculated fields

I have a react component, which has properties and state. Some fields of state contain input data (uplifted from input control), but there is also fields in the state that must be Calculated based on current State and Props:
The question: what is the best way to update calculated fields of the state (based on other fields of state and props)?
Ugly way to do it:
componentDidUpdate(){
this.setState({calculatedField:calculate(this.props,this.state)}))
}
In this case I get infinite loop of updates or in the best case (if I use PureComponent) double rendering invocation.
The best solution I found so far (but still ugly):
Is to create a calculated object in state, which contains calculated fields and updated in componentWillUpdate avoiding setState:
componentWillUpdate(nextProps,nextState){
nextState.calculated.field1=f(nextProps,nextState)
}
class ParentComponent extends React.Component {
constructor(props, ctx) {
super(props,ctx)
this.state={A:"2"}
}
render() {
console.log("rendering ParentComponent")
return <div>
<label>A=<input value={this.state.A} onChange={e=>{this.setState({A:e.target.value})}} /></label> (stored in state of Parent component)
<ChildComponent A={this.state.A} />
</div>
}
}
class ChildComponent extends React.PureComponent {
constructor(props,ctx) {
super(props,ctx);
this.state={
B:"3",
Calculated:{}
}
}
render() {
console.log("rendering ChildComponent")
return <div>
<label>B=<input value={this.state.B} onChange={e=>{this.setState({B:e.target.value})}} /></label> (stored in state of Child component state)
<div>
f(A,B)=<b>{this.state.Calculated.result||""}</b>(stored in state of Child component)
<button onClick={e=>{ this.setState({Calculated:{result:new Date().toTimeString()}}) }}>Set manual value</button>
</div>
</div>
}
componentWillUpdate(nextProps, nextState) {
this.state.Calculated.result = getCalculatedResult(nextProps.A, nextState.B)
}
componentWillReceiveProps(nextProps) {
this.state.Calculated.result = getCalculatedResult(nextProps.A, this.state.B)
}
componentWillMount() {
this.state.Calculated.result = getCalculatedResult(this.props.A, this.state.B)
}
}
function getCalculatedResult(a,b) {
const aNum = Number(a)||0
const bNum = Number(b)||0;
const result = (aNum*bNum).toString();
return result;
}
ReactDOM.render(<ParentComponent/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>
This is also ugly solution and React does not recommended to mutate state avoiding setState. So what is right solution for that?
NOTE:
In my real application I cannot recalculate f(a,b) every single time during rendering, because it's actually complex object, so I need to cache it somehow and the best way is in the state.
If you are using React 16.8.0 and above, you can use React hooks API. I think it's useMemo() hook you might need. For example:
import React, { useMemo } from 'react'
const MyComponent = ({ ...props }) => {
const calculatedValue = useMemo(
() => {
// Do expensive calculation and return.
},
[a, b]
)
return (
<div>
{ calculatedValue }
</div>
)
}
For more details, refer to the React documentation
I wouldn't advice you to store your calculated value inside your state. My approach would be more like this:
import PropTypes from 'prop-types';
import React from 'react';
class Component extends React.Component {
static defaultProps = { value: 0 };
static propTypes = { value: PropTypes.number };
state = { a: 0, b: 0 };
result = () => this.state.a + this.state.b + this.props.value;
updateA = e => this.setState({ a: +e.target.value });
updateB = e => this.setState({ b: +e.target.value });
render() {
return (
<div>
A: <input onChange={this.updateA} value={this.state.a} />
B: <input onChange={this.updateB} value={this.state.b} />
Result: {this.result()}
</div>
);
}
}
The problem with storing the calculation inside your state is, that the calculation can be mutated by multiple sources. If you use my solution, there is no way, that anything can overwrite the calculation WITHOUT using the correct function to calculate them.
You can save calculated result in this.calculated instead of this.state. It is dependent data. All data which causes update and render is already in state and props.
class Component extends React.Component {
constructor(props) {
super(props)
state = {
b: 0
}
}
updateThis = (event) => {
this.setState({ b: event.target.value });
}
componentWillUpdate(nextProps,nextState){
this.calculated.field1=f(nextProps.a, nextState.b)
}
render() {
return (
<form>
A = <input onChange={this.props.updateParent} value={this.props.a} /> <br>
B = <input onChange={this.updateThis} value={this.state.b} /> <br>
f(A,B) = {this.calculated.field1} <br>
</form>
);
}
}
class ParentComponent extends React.Component {
constructor(props) {
super(props)
state = {
a: 0
}
}
render() {
return (
<Component
updateParent={event=>this.setState({a: event.target.value})}
a={this.state.a}
/>
}
}
}
You're first attempt is the right way to solve this problem. However, you need to add a check to see if state has actually changed:
componentDidUpdate(prevProps, prevState){
if(prevState.field !== this.state.field){
this.setState({calculatedField:calculate(this.props,this.state)}))
}
}
shouldComponentUpdate(nextProps, nextState) {
return this.state.calculatedField !== nextState.calculatedField
}
You need to check the pieces of state and props that you use in your calculate method and make sure they have changed before updating state again. This will prevent the infinite loop.
It looks like the "state" is the place to store everything (even computed values) you'll need to use on the render function, but usually we have the problem you describe.
Since React 16.3 a new approach for this situation has been given in the way of the static getDerivedStateFromProps (nextProps, prevState) "lifecycle hook".
You should update at least to this version if you haven't, and follow the advice given by the React Team on their blog.
Here is the official documentation for this functionality.
The issue here is that this function is invoked before every render, and being "static" you cannot access the current instance previous props, which is usually needed to decide if the computed value must be generated again or not (I suppose this is your case, as you have stated your computation process is heavy). In this case, the React team suggests to store in the state the values of the related props, so they can be compared with the new ones:
if (nextProps.propToCompute !== prevState.propToComputePrevValue) {
return {
computedValue: Compute(nextProp.propToCompute),
propToComputePrevValue: nextProps.propToCompute
};
}
return null;
Do not include redundant information in your state.
A simplified example is having firstName and lastName in your state. If we want to display the full name in your render method, you would simply do:
render() {
return <span>{`${this.state.firstName} ${this.state.lastName}`}</span>
}
I like this example because it's easy to see that adding a fullName in our state, that just holds ${this.state.firstName} ${this.state.lastName} is unnecessary. We do string concatenation every time our component renders, and we're okay with that because it's a cheap operation.
In your example, your calculation is cheap so you should do it in the render method as well.

ReactJS: How to get state value into container?

I need to get data from DB depending on a search string value. Therefore I'm using an input field. The search string is stored as a state value.
The data for the component comes from a container (using npm meteor/react-meteor-data).
Now my problem is, how do I get the search string into the container to set the parameter for the publication?
container/example.js
export default createContainer((prop) => {
Meteor.subscribe('images', searchString) // How to get searchString?
return { files: Images.find({}).fetch() }
}, Example)
component/example.jsx
class Example extends Component {
constructor(props) {
super(props)
this.state = {
searchString: ''
}
}
searchImage(event) {
const searchString = event.target.value
this.setState({ searchString })
}
render() {
return (<Input onChange={ this.searchImage.bind(this) }/>)
}
}
export default Example
publication
Meteor.publish('images', function(search) {
return Images.find({ title: search }).cursor
})
Maybe you can create two different components: a parent and a child, and you can wrap child component with createContainer HOC like the following
childComponent.js
const Example = (props) => {
return <Input onChange={props.searchImage}/>
}
export default createContainer(({searchString}) => {
Meteor.subscribe('images', searchString)
return { files: Images.find({}).fetch() }
}, Example)
parentComponent.js
class ExampleWrapper extends Component {
constructor(props) {
super(props)
this.state = {
searchString: ''
}
}
searchImage = (event) => {
const searchString = event.target.value
this.setState({ searchString })
} // instead of binding this, you can also use arrow function that
// takes care of binding
render() {
return (<Example searchImage={this.searchImage} searchString={this.state.searchString} {...this.props} />)
}
}
export default ExampleWrapper
The idea is, since createContainer is a higher order component, it doesn't have access to the props of any component wrapped by it.
What we need to do is, passing the value of searchString from a parent component.
The way to do is the following:
ExampleWrapper has a state called searchString and Example component has a prop called searchString. We can set the value of searchString prop to state.searchString.
Since the default export corresponds to createContainer({..some logic…}, Example}), createContainer can make use of prop called searchString.
In order to change the value of state.searchString we also passed searchImage function as a prop to Example component. Whenever there is a change event, onChange triggers searchImage function that updates the value of state.searchString. And eventually, the minute the value of state.searchString changes searchString prop’s value changes thus your subscription result also changes
onChange={ (e)=> {this.setState({ searchString: $(e.target).val() }) } }
This is how we assign values to our internal state properties :)
EDIT: I appear to have misunderstood the question...

Notify react components about value change

Suppose that I have a component class which is responsible to change any number entered into textbox to text:
class NumbersToText extends Component {
onChange(event) {
const { target } = event;
const { value } = target;
if (hasNumbers(value)) {
target.value = numbersToText(value);
// HERE I NEED TO NOTIFY ABOUT CHANGES
}
}
render() {
return (
<span onChange={this.onChange}>
{this.props.children}
</span>
);
}
}
Now the usage would look something like this:
<NumbersToText>
<input onChange={this.saveValue}
</NumbersToText>
Let's say that all works, and the value gets changed to text.
Now the problem is that after I change numbers to text and assign that value to input onChange handlers are not executed again, thus saveValue is not called with updated value.
How should this problem be approached in order to trigger onChange handlers with new value?
I don't know exactly what you mean by numbers to text so I'll just assume you want to modify the value before calling the onChange function in the input, and also reflect that value in the input.
First of all, what you're doing will never work on React, React reflects internal virtual objects into the DOM, meaning you shloud not modify the DOM directly and instead you should modify this internal representantion (via setState, props) to reflect this change into the DOM.
There's also two types of inputs on React, controlled and uncontrolled. I will assume you want to use this on uncontrolled inputs.
The only possible solution I can see, is to transform the input using the React.cloneElement function adding a aditional step before calling the input's onChange callback.
Here's a possible implementation that will make the input uppercase.
class UpperCase extends React.Component {
constructor(props) {
super(props);
}
onChange(e, input, next) {
let value = input.value || '';
value = value.toUpperCase();
input.value = value;
next(value);
}
render() {
let childs = React.Children.map(this.props.children, child => {
let input = null; //Will take advantage of javascript's closures
let onChangeChild = child.props.onChange.bind(child);
return React.cloneElement(child, {
ref: ref => input = ref,
onChange: e => {
this.onChange(e, input, onChangeChild)
}
});
});
return (
<span>
{childs}
</span>
);
}
}
And you can use it like this:
<UpperCase>
<input onChange={(val) => console.log(val)}></input>
<textarea onChange={(val) => console.log(val)}></textarea>
</UpperCase>
Thanks to #tiagohngl I came up with a similar, but maybe a little less cluttered (without cloning elements) way:
class NumbersToText extends Component {
onChange(event) {
const { target } = event;
const { value } = target;
if (hasNumbers(value)) {
target.value = numbersToText(value);
this.childrenOnChange(event);
}
}
childrenOnChange(event) {
const { children } = this.props;
React.Children.forEach(children, child => {
if (child.props.onChange) {
child.props.onChange(event);
}
});
}
render() {
return (
<span onChange={this.onChange}>
{this.props.children}
</span>
);
}
}
export default class NumbersToText extends React.Component {
constructor(props) {
super(props)
this.onChange = this.onChange.bind(this);
}
componentWillMount() {
this.setState({ anyData: [] });
}
onChange(event) {
this.setState({anyData: event.target.value},
()=>{console.log("AnyData: "+this.state.anyData)});
// callback to console.log after setState is done
}
render() {
return (
<input type="text"
value={this.state.anyData}
onChange={this.onChange} />
);
}
}
As you mention that,
onChange is not called after changed value.
There are multiple possibilities.
onChange is not binded.
There are no state change in render method, so it will not re-render
make use of console.log() to trace the problem
I slightly ammend the code for illustration.
Hope it helps.
How react handle State Change (answer I posted before)

Categories

Resources