callback in nested state object - javascript

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.

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 with dynamic key and object value not working as expected

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.

How to remove ReactJS warning related to using a `value` prop to a form field without an `onChange` handler when the handler is actually defined?

So have an input field:
<input
id={itemId}
onChange={handleChange}
type="number"
value={packed}
/>
And here is my onChange function:
handleChange(e) {
const { items, onUpdateQuantity } = this.props;
const updateItem = items.filter((item) =>
item.itemId === e.target.id,
);
const itemQuantity = toNumber(e.target.value);
updateItem.total += itemQuantity;
onUpdateQuantity(e.target.id, itemQuantity);
}
So why is React still complaining about an onChange handler not being defined when it already is? I don't want to add a defaultValue prop, as that causes bugs in my app. Any ideas?
That is coming because your value is not changing anywhere. As you can see from docs for controlled components, the value of the input is this.state.value and the onChange method changes the value inside the input by changing this.state.value.
As far as I can see, when you input a value inside the input (<input/>) element, the value of that element is not changing. It is always whatever the value of packed is. That is why you're getting the error.
Make sure you bind you function in the constructor
class bla extends Component {
constructor(props){
super(props);
this.handleChange = this.handleChange.bind(this);
}
}
or if using stage 0 to have you have your function in this way.
handleChange = () => {}
and if not
handleChange () {
return (e) => {};
}
Also, if your using a class component you should call your handleChange with this.handleChange if your passing it to functional component then what you have should be fine
https://reactjs.org/docs/handling-events.html
You have to be careful about the meaning of this in JSX callbacks. In JavaScript, class methods are not bound by default. If you forget to bind this.handleClick and pass it to onClick, this will be undefined when the function is actually called.
This is not React-specific behavior; it is a part of how functions work in JavaScript. Generally, if you refer to a method without () after it, such as onClick={this.handleClick}, you should bind that method.
Don't forget to use this.handleChange in you JSX
Example:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);

Why this.state is undefined in react native?

I am a complete newbie in react native, react.js, and javascript. I am Android developer so would like to give RN a try.
Basically, the difference is in onPress;
This code shows 'undefined' when toggle() runs:
class LoaderBtn extends Component {
constructor(props) {
super(props);
this.state = { loading: false };
}
toggle() {
console.log(this.state);
// let state = this.state.loading;
console.log("Clicked!")
// this.setState({ loading: !state })
}
render() {
return (
<Button style={{ backgroundColor: '#468938' }} onPress={this.toggle}>
<Text>{this.props.text}</Text>
</Button>
);
}
}
but this code works:
class LoaderBtn extends Component {
constructor(props) {
super(props);
this.state = { loading: false };
}
toggle() {
console.log(this.state);
// let state = this.state.loading;
console.log("Clicked!")
// this.setState({ loading: !state })
}
render() {
return (
<Button style={{ backgroundColor: '#468938' }} onPress={() => {this.toggle()}}>
<Text>{this.props.text}</Text>
</Button>
);
}
}
Can you explain me the difference, please?
In Java / Kotlin we have method references, basically it passes the function if signatures are the same, like onPress = () => {} and toggle = () => {}
But in JS it doesn't work :(
The issue is that in the first example toggle() is not bound to the correct this.
You can either bind it in the constructor:
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
...
Or use an instance function (OK under some circumstances):
toggle = () => {
...
}
This approach requires build changes via stage-2 or transform-class-properties.
The caveat with instance property functions is that there's a function created per-component. This is okay if there aren't many of them on the page, but it's something to keep in mind. Some mocking libraries also don't deal with arrow functions particularly well (i.e., arrow functions aren't on the prototype, but on the instance).
This is basic JS; this article regarding React Binding Patterns may help.
I think what is happening is a matter of scope. When you use onPress={this.toggle} this is not what you are expecting in your toggle function. However, arrow functions exhibit different behavior and automatically bind to this. You can also use onPress={this.toggle.bind(this)}.
Further reading -
ES6 Arrow Functions
.bind()
What is happening in this first example is that you have lost scope of "this". Generally what I do is to define all my functions in the constructor like so:
constructor(props) {
super(props);
this.state = { loading: false };
this.toggle = this.toggle.bind(this);
}
In the second example, you are using ES6 syntax which will automatically bind this (which is why this works).
Then inside of you onPress function, you need to call the function that you built. So it would look something like this,
onPress={this.toggle}

Why is my onClick being called on render? - React.js

I have a component that I have created:
class Create extends Component {
constructor(props) {
super(props);
}
render() {
var playlistDOM = this.renderPlaylists(this.props.playlists);
return (
<div>
{playlistDOM}
</div>
)
}
activatePlaylist(playlistId) {
debugger;
}
renderPlaylists(playlists) {
return playlists.map(playlist => {
return <div key={playlist.playlist_id} onClick={this.activatePlaylist(playlist.playlist_id)}>{playlist.playlist_name}</div>
});
}
}
function mapStateToProps(state) {
return {
playlists: state.playlists
}
}
export default connect(mapStateToProps)(Create);
When I render this page, activatePlaylist is called for each playlist in my map. If I bind activatePlaylist like:
activatePlaylist.bind(this, playlist.playlist_id)
I can also use an anonymous function:
onClick={() => this.activatePlaylist(playlist.playlist_id)}
then it works as expected. Why does this happen?
You need pass to onClick reference to function, when you do like this activatePlaylist( .. ) you call function and pass to onClick value that returned from activatePlaylist. You can use one of these three options:
1. using .bind
activatePlaylist.bind(this, playlist.playlist_id)
2. using arrow function
onClick={ () => this.activatePlaylist(playlist.playlist_id) }
3. or return function from activatePlaylist
activatePlaylist(playlistId) {
return function () {
// you code
}
}
I know this post is a few years old already, but just to reference the latest React tutorial/documentation about this common mistake (I made it too) from https://reactjs.org/tutorial/tutorial.html:
Note
To save typing and avoid the confusing behavior of this, we will use
the arrow function syntax for event handlers here and further below:
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => alert('click')}>
{this.props.value}
</button>
);
}
}
Notice how with onClick={() => alert('click')}, we’re passing a
function as the onClick prop. React will only call this function after
a click. Forgetting () => and writing onClick={alert('click')} is a
common mistake, and would fire the alert every time the component
re-renders.
This behaviour was documented when React announced the release of class based components.
https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html
Autobinding
React.createClass has a built-in magic feature that bound all methods to this automatically for you. This can be a little confusing for JavaScript developers that are not used to this feature in other classes, or it can be confusing when they move from React to other classes.
Therefore we decided not to have this built-in into React's class model. You can still explicitly prebind methods in your constructor if you want.
import React from 'react';
import { Page ,Navbar, Popup} from 'framework7-react';
class AssignmentDashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
}
onSelectList=(ProjectId)=>{
return(
console.log(ProjectId,"projectid")
)
}
render() {
return (
<li key={index} onClick={()=> this.onSelectList(item.ProjectId)}></li>
)}
The way you passing the method this.activatePlaylist(playlist.playlist_id), will call the method immediately. You should pass the reference of the method to the onClick event. Follow one of the below-mentioned implementation to resolve your problem.
1.
onClick={this.activatePlaylist.bind(this,playlist.playlist_id)}
Here bind property is used to create a reference of the this.activatePlaylist method by passing this context and argument playlist.playlist_id
2.
onClick={ (event) => { this.activatePlaylist.(playlist.playlist_id)}}
This will attach a function to the onClick event which will get triggered on user click action only. When this code exectues the this.activatePlaylist method will be called.

Categories

Resources