I'm just starting out with React, adapting the tic tac toe tutorial for my case.
I'm trying to click on the grandchild component to change the state of the grandparent component . Code is as follows:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
fields: [
{
id: 1,
show: false
},
{
id: 2,
show: false
}
]
}
}
handleClick(i) {
const fields = this.state.fields.slice();
fields[i].show = true;
this.setState({fields: fields});
}
render() {return <Preview />}
}
const Preview = (props) => {
return (
<div className="preview">
{props.fields.map((field) => (
<Field data={field} key={field.id} onClick={ props.onClick(field.id) }/>
))}
</div>
);
};
const Field = props => {
return (
<div className="field" onClick={ props.onClick } />
);
};
I get a TypeError: Cannot read property 'state' of undefined from this line:
handleClick(i) {
const fields = this.state.fields.slice();
Issues
this of the App class isn't bound to the handleClick function. This is cause of TypeError: Cannot read property 'state' of undefined error.
You are mutating your state object. Slicing the array creates a new array reference, but fields[i].show = true; mutates the object reference in state.
You don't pass fields or onClick props to Preview.
The onClick callback isn't called correctly in Preview.
Solution
Bind this to the handler or convert to arrow function so it is automatically bound.
constructor(props){
...
this.handleClick = this.handleClick.bind(this);
}
or
handleClick = (i) => { ..... };
DON'T MUTATE STATE. Shallow copy state then update properties.
handleClick = (id) => {
this.setState(prevState => ({
fields: prevState.fields.map((field) => {
return field.id === id ? {
...field,
show: true,
} : field;
}),
}));
};
Pass fields and handleClick as onClick to Preview.
render() {
return (
<Preview
fields={this.state.fields}
onClick={this.handleClick}
/>
);
}
Call props.onClick correctly with the id.
{props.fields.map((field) => (
<Field
data={field}
key={field.id}
onClick={() => props.onClick(field.id)}
/>
))}
I've added some explanations, check the comments
// [...]
render() {
// Here you need to pass "fields" and "handleClick" as props:
return <Preview fields={this.state.fields} onClickField={this.handleClick} />
}
}
const Preview = (props) => {
// Here you get the props:
const { fields, onClickField } = props;
// Your onclick was a function call instead of just a function
return (
<div className="preview">
{fields.map((field) => (
<Field
data={field}
key={field.id}
onClick={() => onClickField(field.id) }
/>
))}
</div>
);
};
const Field = props => {
return (
<div className="field" onClick={ props.onClick } />
);
};
Related
Hello [from react beginner].
Trying to pass child's input value to parent state.
So, App has an array:
export default class App extends React.Component {
state = {
data: [
{id: 1, name: 'john'},
{id: 2, name: 'doe'},
]
}
render() {
return (
<List data={this.state.data}/>
)
}
}
Then List takes prop.data as state.data and returns children in map:
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
data: this.props.data
};
this.parentChange = this.parentChange.bind(this);
}
renderList() {
const data = this.state.data;
let list = null;
if (data.length) {
list = data.map(function(item, index){
return (<Item key={item.id} data={item} onChange={(e, index) => this.parentChange(e, index)} />)
});
} else {
list = <p>nothing here</p>
}
return list;
}
parentChange(value, index) {
// pls give me anything
console.log('--- value: ', value);
console.log('--- index: ', index);
}
render() {
return (
<div>{this.renderList()}</div>
)
}
}
And Item child:
class Item extends React.Component {
render() {
const {id, name} = this.props.data;
return (
<div>
<input id={id} value={name} onChange={(e) => this.props.onChange(e, id)} />
</div>
)
}
}
But if I change any input's value there is an error as result
Cannot read property 'parentChange' of undefined
Thanks for any help (code, links etc)
You are declaring a function with function keyword:
if (data.length) {
list = data.map(function(item, index){
return (<Item key={item.id} data={item} onChange={(e, index) =>
this.parentChange(e, index)} />)
});
}
Declaring a function with the function keyword will create another context inside itself, so your this (context) will no longer be the class context.
The IDE might not warn you but when it runs, JS create another context inside your function result in an undefined error.
So it will need to change to:
if (data.length) {
list = data.map((item, index) => {
return (<Item key={item.id} data={item} onChange={(e, index) =>
this.parentChange(e, index)} />)
});
}
I got an error when trying to call a function declared inside a component. here is the error message: Uncaught TypeError: Cannot read property 'handleClick' of undefined
class LanguageDropDown extends Component {
constructor(props) {
super(props);
this.state = {languageValue: ""};
this.handleLanguageClick = this.handleLanguageClick.bind(this);
}
handleLanguageClick = (languageKey) => {
this.setState({ languageValue: languageKey});
}
render() {
const {intl, value, onChange, onFocus, onBlur, onClick} = this.props;
return (
<React.Fragment>
{
map(languageWhitelist, function(Key) {
return (
<option onClick={this.handleLanguageClick(Key)}
key={Key} value={Key}></option>
);
}, this)
}
</React.Fragment>
);
}
}
This should work
class LanguageDropDown extends Component {
state = { languageValue: '' };
_handleLanguageClick(languageValue) {
this.setState({ languageValue });
};
render() {
const { intl, value, onChange, onFocus, onBlur, onClick } = this.props;
return (
<React.Fragment>
{languageWhitelist.map(language => (
<option
onClick={() => this._handleLanguageClick(language)}
key={language}
>
{language}
</option>
))}
</React.Fragment>
);
}
}
By using arrow functions inside a class,it becomes automatically bound to the class,
so just remove the binding statement from the constructor.
And also set the onClick props to a callback function.
class LanguageDropDown extends Component {
constructor(props) {
super(props);
this.state = { languageValue: '' };
}
_handleLanguageClick = languageValue => {
this.setState({ languageValue });
};
render() {
const { intl, value, onChange, onFocus, onBlur, onClick } = this.props;
return (
<React.Fragment>
{languageWhitelist.map(language => (
<option
onClick={() => this._handleLanguageClick(language)}
key={language}
>
{language}
</option>
))}
</React.Fragment>
);
}
}
I'm making a React app using openweathermap API. Right now I receive the list of weather data. I'm trying to highlight the weather if I click it.
To make this happen, I wrote on App.js to pass a prop to WeatherDetail.js, but so far seems like WeatherDetail.js doesn't recognize props from its parent.
class App extends React.Component {
constructor(props) {
super(props);
}
state = { forecasts: [], selectedWeather: null }
getWeather = async city => {
const response = await weather.get('/forecast', {
params: {
q: city
}
});
this.setState ({
forecasts: response.data.list,
city: response.data.city.name,
selectedWeather: response.data.list[0]
})
}
}
onWeatherSelectFunction = (item) => {
this.setState({ selectedWeather: item });
};
render() {
return (
<div>
<Form loadWeather={this.getWeather} />
<WeatherDetail itemToChild={this.state.selectedWeather} />
<WeatherList
onWeatherSelect={this.onWeatherSelectFunction}
weathers={this.state.forecasts}
city={this.state.city}
/>
</div>
);
}
}
export default App;
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
const WeatherItem = ({item, onWeatherSelectFromList, humidity, city, temp }) => {
return (
<div>
<div onClick={() => onWeatherSelectFromList(item)} >
{city}<br /> <-- Appears on screen
{humidity}<br /> <-- Appears on screen
</div>
</div>
);
};
const WeatherList = ({weathers, onWeatherSelect, city}) => {
const renderedList = weathers.map((item) => {
return (
<div>
<WeatherItem
city={city}
temp={item.main.temp}
humidity={item.main.humidity}
temperature={item.weather.icon}
onWeatherSelectFromList={onWeatherSelect}
/>
</div>
);
});
return (
<div className="flex">
{renderedList}
</div>
);
}
class Form extends React.Component {
state = { term: '' };
onFormSubmit = (event) => {
event.preventDefault();
this.props.loadWeather(this.state.term);
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<input
ref="textInput"
type="text"
value={this.state.term}
onChange={event => this.setState({term: event.target.value})}
/>
<button>Get Weather</button>
</form>
</div>
);
}
}
How do I connect App.js and WeatherDetail.js using props?
In your App.js file you are passing only one props called itemToChild
<WeatherDetail itemToChild={this.state.selectedWeather} />
In your WeatherDetail file from where you're getting forecasts? do you get forecasts from redux store?
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
change your code with this.
const WeatherDetail = (props) => {
console.log("props.itemToChild", props.itemToChild) // check this console that do you get data as you want.
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
You have already destructured the props so there is no need to mention props in WeatherDetail component
and also there is an extra parenthesis after the return statement you should remove that also...
Old:
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
New:
const WeatherDetail = ({ forecasts, itemToChild }) => {
const weather = itemToChild;
if (!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div>;
};
I'm having trouble figuring out how to pass event.target.value into my setState below.
I was thinking that the first event (= event =) would get passed in closure and be available to the return, but I get undefined when I debug that.
What is the right away to get to event.target.value?
class InputElement1CC extends React.Component {
state = {
inputText: "",
historyList: []
};
handleChange = event => {
this.setState(previousState => {
return {
inputText: event.target.value,
historyList: [...previousState.historyList, event.target.value]
};
});
};
render() {
return (
<div>
home
<h1>InputElement1CC - Class Component</h1>
<input placeholder="Enter Some Text" onChange={this.handleChange} />
<br />
{this.state.inputText}
<hr />
<br />
<ul>
{this.state.historyList.map(rec => {
return <div>{rec}</div>;
})}
</ul>
</div>
);
}
}
export default InputElement1CC;
Set the value to a variable and use that variable. Something like this:
handleChange = event => {
const value = event.target.value;
this.setState(
state => ({
inputText: value,
historyList: [...state.historyList, value]
}),
() => console.log("state", this.state)
);
};
The problem is any item button click will delete the 1st index item in the array.
I looked at these resources on handling deleting an item in an array in react.
How to remove item in todo list using React
Removing element from array in component state
React Binding Patterns
I've tried changing how my handler is called in TodoList and TodoItemLIst and that causes the handler not to fire on click. I've tried different methods of binding the handler - adding a param has no effect on it -bind(this) breaks it & isn't necessary because I'm using a function.
I've tried setting state different ways using a filter method. No change happens...
this.setState((prevState) => ({
todoItems: prevState.todoItems.filter(i => i !== index)
}));
I'm not understanding where/what the problem is.
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
listItemValue: props.value || '',
todoItems: [
{id: _.uniqueId(), item: 'Learn React.'},
{id: _.uniqueId(), item: 'Improve JS skills.'},
{id: _.uniqueId(), item: 'Play with kittens.'}
]
};
}
handleChange = (event) => {
let value = event.target.value;
this.setState({
value: this.state.value,
listItemValue: value
});
}
handleSubmit = (event) =>{
event.preventDefault();
this.setState({
value: '',
listItemValue: ''
});
}
addTodoItem = () => {
let todoItems = this.state.todoItems.slice(0);
todoItems.push({
id: _.uniqueId(),
item: this.state.listItemValue
});
this.setState(prevState => ({
todoItems: [
...prevState.todoItems,
{
id: _.uniqueId(),
item: this.state.listItemValue
}]
}))
};
deleteTodoItem = (index) => {
let todoItems = this.state.todoItems.slice();
todoItems.splice(index, 1);
this.setState({
todoItems
});
}
render() {
return (
<div className="App">
<h1>Todo List</h1>
<TodoListForm name="todo"
onClick={ ()=>this.addTodoItem() }
onSubmit={ this.handleSubmit }
handleChange={ this.handleChange }
value={ this.state.listItemValue } />
<TodoList onClick={ ()=>this.deleteTodoItem() }
todoItems={ this.state.todoItems }/>
</div>
);
}
}
const TodoList = (props) => {
const todoItem = props.todoItems.map((todo) => {
return (
<TodoListItem onClick={ props.onClick }
key={ todo.id }
id={ todo.id }
item={ todo.item }/>
);
});
return (
<ul className="TodoList">
{todoItem}
</ul>
);
}
const TodoListItem = (todo, props) => {
return (
<li className="TodoListItem">
<div className="TodoListItem__Item">{todo.item}
<span className="TodoListItem__Icon"></span>
<button onClick={ todo.onClick }
type="button"
className="TodoListItem__Btn">×</button>
</div>
</li>
)
};
In the deleteTodoItem method, try just
let todoItems = this.state.todoItems.slice(0, -1);
and remove the call to splice().