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 });
}
Related
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;
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
i'm facing an issue with react's method (setState), hope you can help.
I have a handleChange method using dynamic keys to 'persist' data in the state.. i looks like this:
handleChange = (event, group) => {
event.persist(); //Used to avoid event recycling.
this.setState(
prevState => ({
...prevState,
[group]: { ...prevState[group], [event.target.name]: event.target.value }
}),
() => console.log("state", this.state)
);
};
this method works pretty well when theres just one 'instance' of my custom component using the mentioned handleChange method. The problem began when i wanted to have several components using that method, because when called, its overriding the prevState value. For example:
Initial state: {mockedValue:'Im here to stay'}
then i call handleChange for group 'alpha', to add to this values {name:a},
Next state: {alpha:{name:a},mockedValue:'Im here to stay'}
then i call handleChange for group 'beta', to add to this values {otherName:b},
expected state: {alpha:{name:a}, beta:{otherName:b},mockedValue:'Im here to stay'}
Next state : beta:{otherName:b},mockedValue:'Im here to stay'}
Not sure why this is happening, perhaps i'm misunderstanding some concept, the fact is that i don't have idea why this is not working as expect, (perhaps it's because computed name value, but not sure..) Do you have any idea how to solve this?
Thanks for reading! :)
Update
Code in sandbox: https://codesandbox.io/s/v3ok1jx175
Update2: SOLVED
Thanks for your support Thollen and DevSerkan, i really appreciate it.
The problem was that i had the handleChange event at the wrong level... it means that i was defining the handleChange method inside the child Componet, for instance:
class Parent extends React.Component {
render(){
return(
<div>
<Child/>
<Child/>
<Child/>
</div>
);
}
}
so there was just one 'instance' of handleChange method shared by all the 'instances' , it's a wrong approach. to solve this, i modified Parent like this:
class Parent extends React.Component {
handleChange(){
//updateState Operations here...
}
render(){
return(
<div>
<Child handleChange = {this.handleChange}/>
<Child handleChange = {this.handleChange}/>
<Child handleChange = {this.handleChange}/>
</div>
);
}
}
in this way, i removed the responsibility of handling change from the 'top level child' to the parent, where the child components were being used.
I am fairly new to JS and I am having a bit trouble in understanding how to properly implement the callback passed to setState in React, for a controlled input. The following code is what I have so far:
class App extends React.Component {
...
this.state = {
properties: {
width: '',
height: ''
}
this.handleChange = this.handleChange.bind(this); //edit 1
}
handleChange(e){
this.setState(() => ({ properties[e.target.name]: e.target.value })) //edit 2
}
render(){
return(
<input
type="text"
value={this.state.properties.width}
name="width"
onChange={this.handleChange} />
...
)
}
}
https://codepen.io/anon/pen/YYQgNv?editors=0010
You need to change handleChange declaration:
class App extends React.Component {
...
handleChange = (e) => {
this.setState({ properties[e.target.name]: e.target.value })
}
...
}
When you write handleChange = (e) => {...} it will bind this pointer of the function to your component so that you will be able to access setState as pointed out by #Li357, it doesn't not bind at all, on the contrary it creates a property of the class that is an arrow function that doesn't bind this, capturing the this value of the surrounding scope, the class.
Update:
It has been pointed out that using arrow functions as class properties is an experimental feature, so it is safer to use this.handleChange = this.handleChange.bind(this) in constructor of the component.
I got the example working with this code:
handleChange(event) {
const target = event.target;
this.setState((prevState) => ({
properties: {...prevState.properties, ...{ [target.name]: target.value } }
})
);
}
I am not entirely sure why it behaves the way it does, I am guessing it has to do with the fact that setState is async and react wraps events in its own SyntheticEvent which will be reused and all properties will be nullified after the event callback has been invoked (see react docs). So if you store the original reference to target outside of setState it will get scoped and used inside setState.
Here is a working example on codesandbox.
Update 2:
According to react docs, one can't access react SyntheticEvent in an asynchronous way. One way of dealing with this would to be call event.persist() which will remove the wrapper, but this might not be a good idea since SyntheticEvent is a cross-browser wrapper around the browser’s native event which makes sure the events work identically across all browsers.
I have some problem with splice() method in my React.js app.
So, this is an example app. Deletion not works now. What's wrong here? Part of code:
class CardList extends React.Component {
static propTypes = {
students: React.PropTypes.array.isRequired
};
// ADD DELETE FUNCTION
deletePerson(person) {
this.props.students.splice(this.props.students.indexOf(person), 1)
this.setState()
}
render() {
let that = this
return <div id='list'>
{this.props.students.map((person) => {
return <Card
onClick={that.deletePerson.bind(null, person)}
name={person.name}>
</Card>
})}
</div>
}
}
class Card extends React.Component {
render() {
return <div className='card'>
<p>{this.props.name}</p>
{/* ADD DELETE BUTTON */}
<button onClick={this.props.onClick}>Delete</button>
</div>
}
}
http://codepen.io/azat-io/pen/Vaxyjv
Your problem is that when you call
onClick={that.deletePerson.bind(null, person)}
You bind this value to null. So inside of your deletePerson function this is null instead of actual component. You should change it to
onClick={that.deletePerson.bind(this, person)}
And everything would work as expected =)
Changing the bind value to this will definitely cause the call to this.setState() to work, thus triggering the re-render, however I strongly recommend against the approach you've taken.
Props are supposed to be immutable. Instead use internal state and replace with new values rather than mutate them. To do this, set the state of your component in the constructor by doing something like:
constructor(props) {
super(props)
this.state = {
students: ...this.props.students
}
}
And now when you need to delete a person:
deletePerson(person) {
// notice the use of slice vs splice
var newStudents = this.props.students.slice(this.props.students.indexOf(person), 1)
this.setState({ students: newStudents })
}
And finally use this.state.students in your render method instead.
The reasoning behind this is that props are passed directly from the parent container component so modifying them wouldn't really make sense. To make more sense of my own code, I tend to pass in the prop named initialStudents and set my state to students: ...initialStudents to ensure I make the distinction between my prop variable and my state variable.