React Handling Events - javascript

Simple todo list. I want to add a delete function but getting error:
proxyConsole.js:56 Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
I might meesed with the binding as I try to get a grasp of it.
class App extends Component {
constructor(props) {
super(props);
this.onDelete = this.onDelete.bind(this);
this.state = {
todos: ['wash up', 'eat some cheese', 'take a nap'],
};
}
render() {
var todos = this.state.todos;
todos = todos.map(function(item, index){
return(
<TodoItem item={item} key={index} onDelete={this.onDelete}/>
)
}.bind(this));
return (
<div className="App">
<ul>
{todos}
</ul>
</div>
);
}
onDelete(item){
var updatedTodos = this.state.todos.filter(function(val, index){
return item !== val;
});
this.setState({
todos:updatedTodos
});
}
}
class TodoItem extends Component {
constructor(props) {
super(props);
this.handleDelete = this.handleDelete(this);
}
render(){
return(
<li>
<div className="todo-item">
<span className="item-name">{this.props.item}</span>
<span className="item-delete" onClick={this.handleDelete}> x</span>
</div>
</li>
);
}
handleDelete(){
this.props.onDelete(this.props.item);
}
}

I think you are invoking handleDelete handler in child component's constructor. It should be :
this.handleDelete = this.handleDelete.bind(this);
class App extends React.Component {
constructor(props) {
super(props);
this.onDelete = this.onDelete.bind(this);
this.state = {
todos: ["wash up", "eat some cheese", "take a nap"]
};
}
render() {
var todos = this.state.todos;
todos = todos.map(
function(item, index) {
return <TodoItem item={item} key={index} onDelete={this.onDelete} />;
}.bind(this)
);
return (
<div className="App">
<ul>
{todos}
</ul>
</div>
);
}
onDelete(item) {
var updatedTodos = this.state.todos.filter(function(val, index) {
return item !== val;
});
this.setState({
todos: updatedTodos
});
}
}
class TodoItem extends React.Component {
constructor(props) {
super(props);
this.handleDelete = this.handleDelete.bind(this);
}
render() {
return (
<li>
<div className="todo-item">
<span className="item-name">{this.props.item}</span>
<span className="item-delete" onClick={this.handleDelete}> x</span>
</div>
</li>
);
}
handleDelete() {
this.props.onDelete(this.props.item);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"/>

Related

Updating parent state from child components not working in reactjs

I was going through react official documentation when I struck upon an example which updates the parent component through child component callbacks. I was able to understand how the flow works. However, when I tried to optimize the code further it failed to update the component via callbacks.
The Original Code:
https://codepen.io/gaearon/pen/QKzAgB?editors=0010
My code change:
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
this.button = <MyButton message="Login" onClick={this.handleLoginClick} />;
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
if (isLoggedIn) {
this.button = <MyButton message="Logout" onClick={this.handleLogoutClick} />;
} else {
this.button = <MyButton message="Login" onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{this.button}
</div>
);
}
}
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.message=props.message;
this.click=props.onClick;
}
render() {
return (
<button onClick={this.click}>
{this.message}
</button>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
Ok the main problem here is that you are trying to assign to many things to "this".
React does not track changes and re-renders when component's method or properties changes.
try to avoid this pattern and use state and props directly.
Only changes to state or props will cause a component to re-render.
In you situation you can look at this code:
class LoginControl extends React.Component {
state = {isLoggedIn : false}
handleLoginClick = () => {
this.setState({isLoggedIn: true});
}
handleLogoutClick = () => {
this.setState({isLoggedIn: false});
}
button = () => {
const message = this.state.isLoggedIn ? "Logout" : "Login";
const onClick = this.state.isLoggedIn ? this.handleLogoutClick : this.handleLoginClick;
return <MyButton message={message} onClick={onClick} />
}
render() {
return (
<div>
<Greeting isLoggedIn={this.state.isLoggedIn} />
{this.button()}
</div>
);
}
}
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.message=props.message;
this.click=props.onClick;
}
render() {
return (
<button onClick={this.props.onClick}>
{this.props.message}
</button>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);

How can I make a list of integers click on element with corresponding id?

I have a list of ids (integer) and I have multiple components.
After a request to my API, the component receives a list of ids that should already be active.
I want to simulate a click on each element with the same id as the one in my array. I know I can use refs to do that, but I don't undertstand how to make it works with a list of elements.
Here's my code :
import React, { Component } from 'react'
import InterestBox from './InterestBox'
import Axios from 'axios'
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList
import React, { Component } from 'react'
export class InterestBox extends Component {
constructor(props) {
super(props);
this.images = require('../../img/interests/*.svg');
this.state = {activated: false};
this.interest_box_content = React.createRef();
this.interest_text = React.createRef();
this.handleClick = this.handleClick.bind(this);
this.updateDimensions = this.updateDimensions.bind(this);
}
handleClick() {
this.props.handleClick(this.props.id, this.props.title);
this.setState(prevState => ({
activated: !prevState.activated
}))
}
updateDimensions() {
console.log((window.getComputedStyle(this.refs.interest_box_content).width))
this.refs.interest_text = (window.getComputedStyle(this.refs.interest_box_content).width)
}
render() {
return (
<div className="column is-one-fifth-desktop is-half-touch">
<div className="interest-box">
<div className="interest-box-adjuster">
<div ref={"interest_box_content"} className={"interest-box-content " + (this.state.activated == true ? 'interest-box-activated' : '')} onClick={this.handleClick}>
<img className="interest-icon" src={this.images[this.props.icon]} style={{'height': '50%'}}></img>
<i className="activated-icon fas fa-check"></i>
<span ref={"interest_text"} className="interest-text">{this.props.title}</span>
</div>
</div>
</div>
</div>
)
}
}
export default InterestBox
In the InterestList "componentDidUpdate" method, the value of the item is an integer.
I want to use this integer to "click" on the InterestBox with the corresponding "id".
How can I achieve this ?
You can store an array of elements in one ref, like this:
constructor(props) {
super(props);
this.state = {pinterests: []}
this.pinterestRefs = React.createRef()
}
...
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} ref={pinterestRef => this.refs.pinterestRefs.push(pinterestRef)} />
})}
</React.Fragment>
)
}
and then call the click function on each in a componentDidMount function:
componentDidMount() {
if (this.refs.pinterestRefs.length) {
this.refs.pinterestRefs.forEach(pinterestEl => {
pinterestEl.click();
});
}
}
Since this.pinterestRefs is a ref and not an array, the push method is not available. Unfortunately, we do not have a definite length so we can't declare the refs preemptively. However, we can add it to this.refs object and the convert it to an array:
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(Object.values(this.refs)); // Array with all refs
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
{/*I'm assuming each item has a unique id, if not, create one*/}
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} ref={pinterest.id} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList;

React: Remove current instance of component [duplicate]

I have have code that creates <li> elements. I need to delete elements one by one by clicking. For each element I have Delete button. I understand that I need some function to delete items by id. How to do this function to delete elements in ReactJS?
My code:
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {items: [], text: ''};
}
render() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.handleChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
handleChange(e) {
this.setState({text: e.target.value});
}
handleSubmit(e) {
e.preventDefault();
var newItem = {
text: this.props.w +''+this.props.t,
id: Date.now()
};
this.setState((prevState) => ({
items: prevState.items.concat(newItem),
text: ''
}));
}
delete(id){ // How that function knows id of item that need to delete and how to delete item?
this.setState(this.item.id)
}
}
class TodoList extends React.Component {
render() {
return (
<ul>
{this.props.items.map(item => (
<li key={item.id}>{item.text}<button onClick={this.delete.bind(this)}>Delete</button></li>
))}
</ul>
);
}
}
You are managing the data in Parent component and rendering the UI in Child component, so to delete item from child component you need to pass a function along with data, call that function from child and pass any unique identifier of list item, inside parent component delete the item using that unique identifier.
Step1: Pass a function from parent component along with data, like this:
<TodoList items={this.state.items} _handleDelete={this.delete.bind(this)}/>
Step2: Define delete function in parent component like this:
delete(id){
this.setState(prevState => ({
data: prevState.data.filter(el => el != id )
}));
}
Step3: Call that function from child component using this.props._handleDelete():
class TodoList extends React.Component {
_handleDelete(id){
this.props._handleDelete(id);
}
render() {
return (
<ul>
{this.props.items.map(item => (
<li key={item.id}>{item.text}<button onClick={this._handleDelete.bind(this, item.id)}>Delete</button></li>
))}
</ul>
);
}
}
Check this working example:
class App extends React.Component{
constructor(){
super();
this.state = {
data: [1,2,3,4,5]
}
this.delete = this.delete.bind(this);
}
delete(id){
this.setState(prevState => ({
data: prevState.data.filter(el => el != id )
}));
}
render(){
return(
<Child delete={this.delete} data={this.state.data}/>
);
}
}
class Child extends React.Component{
delete(id){
this.props.delete(id);
}
render(){
return(
<div>
{
this.props.data.map(el=>
<p onClick={this.delete.bind(this, el)}>{el}</p>
)
}
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>

Why doesn't react component render when trying to render it in another components click function

I'm trying to render buttons to a page which when clicked render the hard-coded weather data to the page depending on the day that was clicked. The click function works fine and the buttons are rendered just as I expect them to, but when a button is clicked the Day component doesn't render.
I don't understand what I'm doing wrong since my code reaches the console.log in the click handler function. Then the handler function should render the component but for some reason it does not.
Here is my code:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import myData from "./weather.json";
class Day extends React.Component {
constructor(props) {
super(props);
this.state = {
description: null
};
}
render() {
console.log("at last"); //this isn't reached
return (
<div className="dayWeather">
<div className="dayWeather">Humidity {this.props.humidity}</div>
<div className="dayWeather">Temperature {this.props.temperature}</div>
</div>
);
}
}
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null)
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
let daysWeather = myData[day];
console.log("now we've reached this far"); //this console log is reached when a button is clicked.
return (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
class Weather extends React.Component {
render() {
return (
<div className="weather">
<div className="weather-panel">
<DayRow />
</div>
<div className="day" />
</div>
);
}
}
// ========================================
ReactDOM.render(<Weather />, document.getElementById("root"));
Don't return UI elements in click handler as it won't render. Just set a flag inside handler and use it to display the Day component inside your render function.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null),
showDay: false
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler() {
this.setState({
showDay: true,
});
}
renderDayComponent(day) {
let daysWeather = myData[day];
console.log("now we've reached this far"); //this console log is reached when a button is clicked.
return (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler();
}}
>
{day}
</button>
{this.state.showDay && this.renderDayComponent(day)}
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
My guess is that you are trying to render the day in the Weather compoment? To do this, you must keep some sort of state so that React knows what to render. Whenever you change the state, React will call render to re-render your compoment.
Therefore, since the state of wether or not a day is to be shown is local to the Weather component, you need to store the state there:
class Weather extends React.Component {
constructor(props) {
super(props)
this.state = { activeDay: undefined }
}
render() {
// Store the current dayData in a variable if an activeDay is chosen, it will be falsy if no day has been chosen.
const dayData = this.state.activeDay && myData[this.state.activeDay]
return (
<div className="weather">
<div className="weather-panel">
// DayRow will inform Weather when a day has been selected, i.e., clicked
<DayRow onSelected={day => this.setState({ activeDay: day })} />
</div>
<div className="day">
// This is where you are rendering the day, only if a day
// is active. I.e., dayData is truthy
{ dayData && <Day humitidy={dayData.humitidy} temperature={dayData.temperature} /> }
</div>
</div>
);
}
}
Your DayRow would simple communicate with Weather by saying which day is selected.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.renderDay = this.renderDay.bind(this)
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.props.onSelected(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
Returning JSX from your event handler will not render it. You have to do all rendering in your component's render method.
You could instead have an additional object in your state that keep track of if the day has been clicked or not, and use that state in your rendering.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null),
showDays: {}
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
this.setState(previousState => {
const showDays = { ...previousState.showDays };
showDays[day] = !showDays[day];
return { showDays };
});
}
renderDay(day) {
let daysWeather = myData[day];
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
{this.state.showDays[day] && (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
)}
</div>
);
}
// ...
}
The returned component from handler function is being passed to onClick event. Its not getting into the DOM tree.
You can change the code as shown below.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import myData from './weather.json';
const myData =
class Day extends React.Component {
constructor(props) {
super(props);
this.state = {
description: null
};
}
render() {
console.log('at last'); //this isn't reached
return (
<div className="dayWeather">
<div className="dayWeather">Humidity {this.props.humidity}</div>
<div className="dayWeather">Temperature {this.props.temperature}</div>
</div>
);
}
}
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null)
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
let daysWeather = myData[day];
console.log('now we\'ve reached this far'); //this console log is reached when a button is clicked.
this.props.handler(daysWeather);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay('Monday')}
{this.renderDay('Tuesday')}
{this.renderDay('Wednesday')}
{this.renderDay('Thursday')}
{this.renderDay('Friday')}
{this.renderDay('Saturday')}
{this.renderDay('Sunday')}
</div>
</div>
);
}
}
class Weather extends React.Component {
constructor(props){
super(props);
this.state = {
selectedDay: {}
};
this.handler = this.handler.bind(this);
}
handler(day){
this.setState({
selectedDay: day
});
}
render() {
let day = null;
if(Object.keys(this.state.selectedDay) > 0){
day = <Day
humidity={selectedDay.humidity}
temperature={selectedDay.temperature}
/>;
}
return (
<div className="weather">
<div className="weather-panel">
<DayRow onDayChange={this.handler}/>
</div>
<div className="day">
{day}
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(<Weather />, document.getElementById('root'));

calling grandparent method from grandchild functional component in react

I'm trying to call a simple method from the grandparent component in my child component but from some reason I can't , I tried every possible way but I think I'm missing something
here's the full code :
import React, { Component } from 'react';
import './App.css';
var todos = [
{
title: "Example2",
completed: true
}
]
const TodoItem = (props) => {
return (
<li
className={props.completed ? "completed" : "uncompleted"}
key={props.index} onClick={props.handleChangeStatus}
>
{props.title}
</li>
);
}
class TodoList extends Component {
constructor(props) {
super(props);
}
render () {
return (
<ul>
{this.props.todosItems.map((item , index) => (
<TodoItem key={index} {...item} {...this.props} handleChangeStatus={this.props.handleChangeStatus} />
))}
</ul>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos ,
text :""
}
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangeStatus = this.handleChangeStatus(this);
}
handleTextChange(e) {
this.setState({
text: e.target.value
});
}
handleChangeStatus(){
console.log("hello");
}
handleSubmit(e) {
e.preventDefault();
const newItem = {
title : this.state.text ,
completed : false
}
this.setState((prevState) => ({
todos : prevState.todos.concat(newItem),
text : ""
}))
}
render() {
return (
<div className="App">
<h1>Todos </h1>
<div>
<form onSubmit={this.handleSubmit}>
< input type="text" onChange={this.handleTextChange} value={this.state.text}/>
</form>
</div>
<div>
<TodoList handleChangeStatus={this.handleChangeStatus} todosItems={this.state.todos} />
</div>
<button type="button">asdsadas</button>
</div>
);
}
}
export default App;
The method im trying to use is handleChangeStatus() from the App component in the TodoItem component
Thank you all for your help
This line is wrong:
this.handleChangeStatus = this.handleChangeStatus(this);
//Change to this and it works
this.handleChangeStatus = this.handleChangeStatus.bind(this);

Categories

Resources