REACT onChange bind this error - javascript

So i have this REACT code which should work :
App.jsx:
class App extends Component {
constructor(props){
super(props);
this.state = {count: props.initialCount};
this.onChange = this.onChange.bind(this);
this.state = {
questions:[
{
id: 1,
text: 'What is your name ?',
choices: [
{
id: 'a',
text: 'Michael'
},
{
id: 'b',
text: 'Brad'
},
{
id: 'c',
text: 'Steven'
}
],
correct: 'b'
},
{
id: 2,
text: 'What is your mothers name ?',
choices: [
{
id: 'a',
text: 'Sara'
},
{
id: 'b',
text: 'Sue'
},
{
id: 'c',
text: 'Donna'
}
],
correct: 'c'
},
{
id: 3,
text: 'What is your father name ?',
choices: [
{
id: 'a',
text: 'Bobby'
},
{
id: 'b',
text: 'Harry'
},
{
id: 'c',
text: 'Wayne'
}
],
correct: 'a'
},
{
id: 4,
text: 'What is your friend name ?',
choices: [
{
id: 'a',
text: 'John'
},
{
id: 'b',
text: 'Paul'
},
{
id: 'c',
text: 'Dan'
}
],
correct: 'c'
}
],
score: 0,
current: 1
}
}
render(){
return(
<div onClick={this.onChange}>
<QuestionList {...this.state} />
</div>
)
}
}
export default App
QuestionList.jsx :
class QuestionList extends Component {
render(){
return(
<div className="questions">
{
this.props.questions.map(question => {
return <Question question={question} key={question.id} {...this.props} />
})
}
</div>
)
}
}
export default QuestionList
Question.jsx :
class Question extends Component {
render(){
const {question} = this.props;
return(
<div className="well">
<h3 >{question.text}</h3>
<hr />
<ul className="list-group">
{
this.props.question.choices.map(choice => {
return(
<li className="list-group-item">
{choice.id} <input type="radio" name={question.id} value={choice.id} /> {choice.text}
</li>
)
})
}
</ul>
</div>
)
}
}
export default Question
except it throws this error in console : TypeError: _this.onChange is undefined
I google it and found out that :
Make sure you do this.onChange = this.onChange.bind(this) in your constructor
But when I add that on my controller i get this error : TypeError: this.onChange is undefined

class Question extends Component {
onChange(e) {
//Your change code here!!
}
render(){
const {question} = this.props;
return(
<div className="well">
<h3 >{question.text}</h3>
<hr />
<ul className="list-group">
{
this.props.question.choices.map(choice => {
return(
<li className="list-group-item">
{choice.id} <input type="radio" onChange={this.onChange.bind(this)} name={question.id} value={choice.id} /> {choice.text}
</li>
)
})
}
</ul>
</div>
)
}
}
export default Question
You should have a onChange method in your class.

If you want to put it in the constructor you need to do it like this:
Note: this is an example from the react docs, which are linked below
export class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: props.initialCount};
this.onChange = this.onChange.bind(this);
}
onChange() {} // add this
render() {
return (
<div onClick={this.onChange}>
Clicks: {this.state.count}
</div>
);
}
}
docs: https://facebook.github.io/react/docs/reusable-components.html#es6-classes

To avoid an error with binding this, you have two options:
Using this.onClick = this.onClick.bind(this); (inside constructor)
Using arrow function for onClick function
I hope this trick helps you.

Related

Mapping objects in objects [duplicate]

This question already has answers here:
map function for objects (instead of arrays)
(39 answers)
Closed 2 years ago.
I am trying to map and object in React and keep getting the following error
"TypeError: Cannot read property 'map' of undefined"
My Expected Outcome
task-1
task-2
task-3
task-4
Code
import React, { Component } from 'react';
class MapEx extends Component {
constructor(props) {
super(props);
this.state = {
tasks: {
'task-1': { id: 'task-1', content: 'clean house' },
'task-2': { id: 'task-2', content: 'walk dog' },
'task-3': { id: 'task-3', content: 'Do pushups' },
'task-4': { id: 'task-4', content: 'have a drink' }
}
};
}
render() {
const tasks = this.state.tasks
console.log(tasks)
return (
<div>
<h1>Hello</h1>
<p> {this.tasks.map((task) =>
task.id)}</p>
</div>
);
}
}
export default MapEx;
Two issues:
You reference this.tasks instead of this.state.tasks.
You are using map on an object instead of an array.
Try something like this:
return (
<div>
<h1>Hello</h1>
{Object.values(this.state.tasks).map(task => <p>{task.id}</p>)}
</div>
);
map can only be used on arrays. To begin with, convert your data to array DS and proceed as below.
import React, { Component } from 'react';
class MapEx extends Component {
constructor(props) {
super(props);
this.state = {
tasks: {
'task-1': { id: 'task-1', content: 'clean house' },
'task-2': { id: 'task-2', content: 'walk dog' },
'task-3': { id: 'task-3', content: 'Do pushups' },
'task-4': { id: 'task-4', content: 'have a drink' }
}
};
}
render() {
const tasks = this.state.tasks
console.log(tasks)
return (
<div>
<h1>Hello</h1>
{Object.values(tasks).map(task => (<p>{task.id}</p>))}
</div>
);
}
}
export default MapEx;
You can do something like this:
Destructuring state
As tasks is an object you can't map over it, you need to use object.keys
import React, { Component } from 'react';
class MapEx extends Component {
constructor(props) {
super(props);
this.state = {
tasks: {
'task-1': { id: 'task-1', content: 'clean house' },
'task-2': { id: 'task-2', content: 'walk dog' },
'task-3': { id: 'task-3', content: 'Do pushups' },
'task-4': { id: 'task-4', content: 'have a drink' }
}
};
}
render() {
const {tasks} = this.state
console.log(tasks)
return (
<div>
<h1>My tasks</h1>
{!!tasks ? Object.values(tasks).map(task => (<p>{task.id}</p>)) : null}
</div>
);
}
}
export default MapEx;
Working example on https://codesandbox.io/s/react-boilerplate-r68kh
I suggest you to read the docs of map.
It works with arrays and not objects.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
tasks is an object, you need to convert it into an array to make this work.
this.state = {
tasks: [
{ id: 'task-1', content: 'clean house' },
{ id: 'task-2', content: 'walk dog' },
{ id: 'task-3', content: 'Do pushups' },
{ id: 'task-4', content: 'have a drink' }
]
};
You can map over an array and here tasks is an object

Filling a model with a list from Textarea

Now I do not really understand you. Sorry, I just started this whole study not so long ago. I’ll try to explain again what I can’t do.
I have an empty object and an object with data with the same structure.
data: [
{id: 1, title: "title1"},
{id: 2, title: "title1"},
{id: 3, title: "title3"},
{id: 4, title: "title4"},
{id: 5, title: "title3"}
],
item: [
{
itemId: "",
itemname: ""
}
]
And I have select and textarear. Select have data, textarear empty. Textarear displays title.
I want to press a button. Selected item from select. copied to textarear (title only), and also itemId - this selected element id: 5 and itemname - the same title: "title3" element, was recorded in item [].
https://codesandbox.io/s/priceless-hermann-g9flw
Please do check now
import React from "react";
class App extends React.Component {
constructor() {
super();
this.state = {
id: null,
title: "",
filmItem: "",
listFilms: [],
data: [
{ id: 1, title: "title1" },
{ id: 2, title: "title2" },
{ id: 3, title: "title3" },
{ id: 4, title: "title4" }
],
item: []
};
this.onChange = this.onChange.bind(this);
this.onChangeArea = this.onChangeArea.bind(this);
this.addFilm = this.addFilm.bind(this);
this.choice = this.choice.bind(this);
}
addFilm(film) {
const selectedData = this.state.data.find(item => item.id == film);
console.log(selectedData);
this.setState({
listFilms: [...this.state.listFilms, selectedData.title],
item: [
...this.state.item,
{ itemId: selectedData.id, itemname: selectedData.title }
]
});
}
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
onChangeArea = e => {
this.setState({ [e.target.name]: e.target.value.split("\n") });
};
choice(title) {
this.setState({ filmItem: title });
}
render() {
return (
<div className="App">
<div className="row App-main">
<div>
<select name="filmItem" size="4" onChange={e => this.onChange(e)}>
{this.state.data.map(film => (
<option key={film.title} value={film.id}>
{film.title}
</option>
))}
</select>
</div>
<div>
<button
className="editButton"
onClick={() => this.addFilm(this.state.filmItem)}
>
button
</button>
</div>
<div>
<textarea
name="films"
onChange={this.onChangeArea}
value={this.state.listFilms.map(r => r).join("\n")}
/>
</div>
<div>
<input type="text" name="text-input" onChange={this.onChange} />
</div>
</div>
<pre style={{ whiteSpace: "pre-wrap" }}>
{JSON.stringify(this.state)}
</pre>
</div>
);
}
}
export default App;

ReactJS two way binding not working, somewhere wrong with binding

I'm new to reactJs, I'm not sure where it went wrong.
I suppose there is something wrong with binding input. I suppose, cant change input because of value={detail.name}. However, even though I have deleted value={detail.name}, Name: {detail.name} still keeps the original value.
Could somebody give me a hint?
import React, { Component } from "react";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
]
};
}
changeName(event) {
this.setState({
name: event.target.value
});
}
onDelete() {}
render() {
return (
<div>
<ul>
{this.state.details.map((detail, index) => (
<li key={index}>
Name: {detail.name} | age: {detail.age}
<input
style={{ marginLeft: "10px" }}
type="text"
onChange={this.changeName.bind(this)}
value={detail.name}
/>
</li>
))}
</ul>
</div>
);
}
}
export default App;
I updated the code a bit.
First of all, I moved the binding of the callback to the constructor (to have ONE callback instead of one per item*render)
I also changed the key used in the map to be the id, rather than the index of the current item.
Try, it, I hope it works for you.
import React, { Component } from "react";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
]
};
this.changeName = this.changeName.bind(this);
}
changeName(event) {
const {target} = event;
const id = Number(target.dataset.id);
const { details } = this.state;
this.setState({
details: details.map((detail) => {
if (detail.id === id) {
return {
...detail,
name: target.value,
}
}
return detail;
}),
});
}
onDelete() {}
render() {
return (
<div>
<ul>
{this.state.details.map(({ id, age, name }) => (
<li key={id}>
Name: {name} | age: {age}
<input
style={{ marginLeft: "10px" }}
type="text"
onChange={this.changeName}
data-id={id}
value={name}
/>
</li>
))}
</ul>
</div>
);
}
}
export default App;
Your code works fine, nothing wrong with the input data binding. The problem is you're setting the name property directly to the state object. That would make it go from this:
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
]
}
To this:
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
],
name: "Bob"
}
Which has no effect on how the component gets rendered. To properly change the name of one of the details, which is what I assume you want, you also need to do a find that detail object to modify. Like this:
changeName(e, target_detail) {
this.setState({
// always update the WHOLE STATE OBJECT! using a map
details: this.state.details.map(detail => {
// the detail we want to modify has the same ID
if(detail.id === target_detail.id) {
// modify the name value of only that
target_detail.name = e.target.value
}
})
});
}
render method:
render() {
return (
<div>
<ul>
{this.state.details.map((detail, index) => (
<li key={index}>
Name: {detail.name} | age: {detail.age}
<input
style={{ marginLeft: "10px" }}
type="text"
// arrow functions implicitly "bind" the current this context:
onChange={e => this.changeName(e, detail)}
value={detail.name}
/>
</li>
))}
</ul>
</div>
);
}

Assigning onClick button to the proper feature. Map function ReactJS

I created this mockup and I am trying to put this into real life
component mockup
I started from something simple so I created 3 buttons and an array
but what happens is that when I click on any button I see all of the features, and my goal was to when I click on SMS I see sms.features etc. But for now I see this that way current result
import React, { Component } from "react";
export default class Test extends Component {
constructor(props) {
super(props);
this.state = {
isHidden: true,
features: [
{
name: "sms.features",
key: "1",
icon: "sms icon"
},
{
name: "pricing.features",
key: "2",
icon: "pricing icon"
},
{
name: "api.features",
key: "3",
icon: "api icon"
}
],
buttons: [
{
name: "sms",
key: 1
},
{
name: "pricing",
key: 2
},
{
name: "api",
key: 3
}
]
};
this.toggleHidden = this.toggleHidden.bind(this);
}
toggleHidden() {
this.setState({
isHidden: !this.state.isHidden
});
}
render() {
return (
<div style={{ marginLeft: "20%" }}>
<div className="features__details__grid">
{!this.state.isHidden &&
this.state.features.map((object, key) => (
<div key={key}>{object.name}</div>
))}
</div>
<div className="buttons">
{this.state.buttons.map((button, key) => (
<div key={key}>
<button onClick={this.toggleHidden}>{button.name}</button>
</div>
))}
</div>
</div>
);
}
}
So, since you want to show only one, the isHidden should really be a pointer to which feature should be visible (by targeting the key property)
import React, { Component } from "react";
export default class Test extends Component {
constructor(props) {
super(props);
this.state = {
visibleFeature: "0",
features: [
{
name: "sms.features",
key: "1",
icon: "sms icon"
},
{
name: "pricing.features",
key: "2",
icon: "pricing icon"
},
{
name: "api.features",
key: "3",
icon: "api icon"
}
],
buttons: [
{
name: "sms",
key: "1"
},
{
name: "pricing",
key: "2"
},
{
name: "api",
key: "3"
}
]
};
this.toggleHidden = this.toggleHidden.bind(this);
}
toggleHidden(key) {
this.setState(state=>{
if (state.visibleFeature === key) return {visibleFeature: 0}
return {visibleFeature: key}
});
}
render() {
const feature = this.state.visibleFeature;
return (
<div style={{ marginLeft: "20%" }}>
<div className="features__details__grid">
{this.state.features.map((object) => (
feature === object.key && <div key={object.key}>{object.name}</div>
))}
</div>
<div className="buttons">
{this.state.buttons.map((button) => (
<div key={button.key}>
<button onClick={()=>this.toggleHidden(button.key)}>{button.name}</button>
</div>
))}
</div>
</div>
);
}
}
Demo at https://codesandbox.io/s/8y5q120wxj

How to propagate state to the root of nested tree from a deep node without using Flux?

I am rendering a nested comment tree, I am not able to figure how to update the tree data present in the comment container from one of the deeply nested comments. I have created a basic fiddle representing the scenario, check it out HERE. The button in each node is supposed to invert the value of "likes" property, which should be coming from the state in parent container. Looking for a non redux solution.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
tree: [
{
id: "cake",
likes: true,
kids: [
{
id: "banana",
likes: false,
kids: []
},
{
id: "rocket",
likes: false,
kids: [
{
id: "homework",
likes: true,
kids: [
{
id: "something",
likes: true,
kids: []
}
]
}
]
}
]
}
]
};
}
render() {
return (
<div>
{this.state.tree.map(value => <Kid value={value} />)}
</div>
);
}
}
class Kid extends React.Component {
render() {
return (
<div style={{margin: '20px', border: '5px dashed #DCEDC8'}}>
<span>id: {this.props.value.id}</span><br />
<span>likes: {this.props.value.likes ? 'yes' : 'no'}</span><br />
<button>invert</button>
{this.props.value.kids.length
? this.props.value.kids.map(value => <Kid value={value} />)
: null}
</div>
);
}
}
React.render(<Parent />, document.getElementById("container"));
<script src="https://facebook.github.io/react/js/jsfiddle-integration.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
The recommendation for Redux state is to keep it normalized, something like this:
this.state = {
normalized: [
cake: {likes: true, kids: ["banana", "rocket"]}
banana: {likes: false, kids: []},
rocket: {likes: false, kids: ["homework"]},
homework: {likes: true, kids: ["something"]},
something: {likes: true, kids: []},
],
root: "cake",
};
Then, if you have an id, you would refer to an item with this.state.normalized[id]
For example, to traverse the tree and apply a function to every node, you would do:
function traverse(node, func) {
func(node);
for (let i = 0, len = node.kids.length; i < len; i++) {
traverse(this.state.normalized[node.kids[i]]);
}
}
traverse(this.state.normalized[this.state.root]);
Normalizr can be useful to normalize nested API responses if you don't want to code your own solution.
If you really want to keep an immutable tree in your state, Immutable.js is good at letting your performantly alter a tree in a single line without mutating it.
Here is the final code that does what you wanted.
There are a lot of things here that you need to pay attention to be able to understand how it works.
While rendering each of the kid, the parent path and kid's id becomes a kid's path. When the button is clicked this path gets passed to the Parent component where the state gets manipulated using this path.
This assumes that id will be unique for siblings.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
tree: [
{
id: "cake",
likes: true,
kids: [
{
id: "banana",
likes: false,
kids: []
},
{
id: "rocket",
likes: false,
kids: [
{
id: "homework",
likes: true,
kids: [
{
id: "something",
likes: true,
kids: []
}
]
}
]
}
]
}
]
};
this.onInvertHandler = this.onInvertHandler.bind(this);
}
onInvertHandler(path) {
this.setState((prevState) => {
const pathKeys = path.split(':');
let obj = prevState['tree'];
for(let i=0;i<pathKeys.length;i++) {
obj = i >0 ? obj.kids : obj;
obj = obj.find((el)=>{ return el.id == pathKeys[i] });
}
obj['likes'] = !obj['likes'];
return prevState;
})
};
render() {
return (
<div>
{this.state.tree.map(value => <Kid value={value} path = {value.id} onInvertHandler={this.onInvertHandler}/>)}
</div>
);
}
}
class Kid extends React.Component {
constructor(props){
super(props);
this.onInvertHandler = this.onInvertHandler.bind(this);
}
onInvertHandler(p) {
let params = typeof p === 'string' ? p : this.props.path;
this.props.onInvertHandler(params);
};
render() {
return (
<div style={{margin: '20px', border: '5px dashed #DCEDC8'}}>
<span>id: {this.props.value.id}</span><br />
<span>likes: {this.props.value.likes ? 'yes' : 'no'}</span><br />
<button onClick = {this.onInvertHandler}>invert</button>
{this.props.value.kids.length
? this.props.value.kids.map(value => <Kid value={value} path = {this.props.path + ':'+value.id} onInvertHandler={this.onInvertHandler}/>)
: null}
</div>
);
}
}
React.render(<Parent />, document.getElementById("container"));

Categories

Resources