Updating parent state from child components not working in reactjs - javascript

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')
);

Related

React: How to trigger event in parent component when onClick event happened in child component

I have the following parent and child components. I want to run the changeBackground function every time the button in the child component (<NewQuoteButton />) is clicked but I don't know how to pass this information to the parent component. Can anyone please help?
//parent component
class App extends React.Component {
constructor(props) {
super(props)
this.state ={
primary: '',
prmaryDark: ''
}
}
changeBackground = () => {
let randomNum = Math.floor(Math.random()*backgroundColors.length)
this.setState = {
primary: backgroundColors[randomNum].main,
prmaryDark: backgroundColors[randomNum].dark
}
}
render() {
return (
<div className="App">
<QuoteBox />
</div>
);
}
}
//child component
export default function QuoteBox() {
const [newQuote, setNewQuote] = useState('');
const [author, setAuthor] = useState('');
const fetchRandomQuote = () => {
fetch(url)
// some method
}
return (
<div className="QuoteBox" >
<Text quoteText={newQuote} author={author} />
<Banner>
<Links />
<NewQuoteButton onClick={fetchRandomQuote} className="NewQuoteButton Button" />
</Banner>
</div>
);
}
//parent component
class App extends React.Component {
constructor(props) {
super(props)
this.state ={
primary: '',
prmaryDark: ''
}
}
changeBackground = () => {
let randomNum = Math.floor(Math.random()*backgroundColors.length)
this.setState = {
primary: backgroundColors[randomNum].main,
prmaryDark: backgroundColors[randomNum].dark
}
}
render() {
return (
<div className="App">
<QuoteBox handleColorChange={this.changeBackground} />
</div>
);
}
}
//child component
export default function QuoteBox(props) {
const [newQuote, setNewQuote] = useState('');
const [author, setAuthor] = useState('');
const fetchRandomQuote = () => {
props.handleColorChange();
fetch(url)
// some method
}
return (
<div className="QuoteBox" >
<Text quoteText={newQuote} author={author} />
<Banner>
<Links />
<NewQuoteButton onClick={fetchRandomQuote} className="NewQuoteButton Button" />
</Banner>
</div>
);
}
If you just want to run your changeBackground function when the button is clicked then call the function in your fetchRandomQuote function.

React onClick event in array.map()

Edit: I have included the full code for better clarity
I am not sure if this is possible. I am trying to pass an onClick event via props but the click event does not work.
The parent component looks like this:
import React from 'react'
import { getProductsById } from '../api/get'
import Product from './Product'
import { instanceOf } from 'prop-types'
import { withCookies, Cookies } from 'react-cookie'
class Cart extends React.Component {
static propTypes = {
cookies: instanceOf(Cookies).isRequired
}
constructor(props) {
super(props)
const { cookies } = props;
this.state = {
prods: cookies.get('uircartprods') || '',
collapsed: true,
total: 0,
}
this.expand = this.expand.bind(this)
this.p = [];
}
componentDidMount() {
this.getCartProducts()
}
handleClick = (o) => {
this.p.push(o.id)
const { cookies } = this.props
cookies.set('uircartprods', this.p.toString(), { path: '/' , sameSite: true})
this.setState({prods: this.p })
console.log('click')
}
getCartProducts = async () => {
let products = []
if (this.state.prods !== '') {
const ts = this.state.prods.split(',')
for (var x = 0; x < ts.length; x++) {
var p = await getProductsById(ts[x])
var importedProducts = JSON.parse(p)
importedProducts.map(product => {
const prod = <Product key={product.id} product={product} handler={() => this.handleClick(product)} />
products.push(prod)
})
}
this.setState({products: products})
}
}
expand(event) {
this.setState({collapsed: !this.state.collapsed})
}
handleCheckout() {
console.log('checkout clicked')
}
render() {
return (
<div className={this.state.collapsed ? 'collapsed' : ''}>
<h6>Your cart</h6>
<p className={this.state.prods.length ? 'hidden' : ''}>Your cart is empty</p>
{this.state.products}
<h6>Total: {this.props.total}</h6>
<button onClick={this.handleCheckout} className={this.state.prods.length ? '' : 'hidden' }>Checkout</button>
<img src="./images/paypal.png" className="paypal" alt="Paypal" />
<a className="minify" onClick={this.expand} alt="My cart"></a>
<span className={this.state.prods.length ? 'pulse' : 'hidden'}>{this.state.prods.length}</span>
</div>
)
}
}
export default withCookies(Cart)
The Product component:
import React from 'react';
class Product extends React.Component {
constructor(props) {
super(props);
this.state = {
showDetails: false,
showModal: false,
cart: []
}
this.imgPath = './images/catalog/'
}
render() {
return (
<div className="Product">
<section>
<img src={this.imgPath + this.props.product.image} />
</section>
<section>
<div>
<h2>{this.props.product.title}</h2>
<h3>{this.props.product.artist}</h3>
<p>Product: {this.props.product.product_type}</p>
<h4>${this.props.product.price}</h4>
<button className="button"
id={this.props.product.id} onClick={this.props.handler}>Add to cart</button>
</div>
</section>
</div>
)
}
}
export default Product
If I log this.props.handler I get undefined. Everything works apart from the click handler, I was wondering if it might have something to with the async function. I am very new to React, there are still some concepts I'm not sure about, so any help is appreciated.
Okay, I see a few issues here.
First, there is no need to call this.handleClick = this.handleClick.bind(this) in the constructor, because you are using an arrow function. Arrow functions do not have a this context, and instead, accessing this inside your function will use the parent this found in the Class.
Secondly, it is wrong to store components in state. Instead, map your importedProducts inside the render function.
Thirdly, the issue with your handler is that this.props.handler doesn't actually call handleClick. You will notice in the definition handler={(product) => this.handleClick} it is returning the function to the caller, but not actually calling the function.
Try this instead.
class Product extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<button className="button" id={this.props.product.id} onClick={this.props.handler}>
Add to cart
</button>
</div>
);
}
}
export default Product;
import Product from './Product'
class Cart extends React.Component {
constructor(props) {
super(props);
}
handleClick = (o) => {
console.log('click');
};
render() {
return (
<div>
{importedProducts.map((product) => {
return <Product key={product.id} product={product} handler={() => this.handleClick(product)} />;
})}
</div>
);
}
}
export default Cart;

Function is not being called as it should in React onClick()

Basically, i created a button to call a function when the button is clicked, it's working, the console.log() shows the message "Working", but the script inside it, it's not being shown on the browser. I have no idea what's wrong, could someone help me? I'm very new in Software Engineer and started learning React a few days ago, so i dont know too much about it.
This is the entire code:
class QuizGame extends React.Component {
constructor() {
super();
this.state = {
questions: Questions
}
}
Game() {
const { questions } = this.state;
const item = questions[Math.floor(Math.random() * questions.length)];
const items = [item.a, item.b, item.c, item.d];
console.log('Working');
return (
<div>
<GameParagraph value={item.ques} />
{
items.map(quest => (
<div key={quest}>
<div>
<GameButton
value={quest}
onClick={() => {
if(item.ans === quest) {
return console.log('Working');
} else {
return console.log('not working')
}
}}
/>
</div>
</div>
))
}
</div>
)
}
render() {
return (
<StartGame
onClick={() => this.Game()}
/>
);
}
}
And that's the button to call the Game():
class StartGame extends React.Component {
render() {
return (
<button onClick={this.props.onClick}>Start</button>
);
}
}
You can try like this:
class QuizGame extends React.Component {
constructor() {
super();
this.state = {
questions: Questions,
hasGameStarted: false
}
}
Game() {
const { questions } = this.state;
const item = questions[Math.floor(Math.random() * questions.length)];
const items = [item.a, item.b, item.c, item.d];
console.log('Working');
return (
<div>
<GameParagraph value={item.ques} />
{
items.map(quest => (
<div key={quest}>
<div>
<GameButton
value={quest}
onClick={() => {
if(item.ans === quest) {
return console.log('Working');
} else {
return console.log('not working')
}
}}
/>
</div>
</div>
))
}
</div>
)
}
startGameClicked() {
this.setState({hasGameStarted: true});
}
render() {
return (
<StartGame
onClick={this.startGameClicked}
/>
{this.state.hasGameStarted ? this.Game() : null}
);
}
Make sure you are binding the onClick event in StartGame component properly.
You need to refactor your code. Returning JSX from an event handler (which is what your function Game is) won't cause your component to render.
Instead, the "React" way to handle this is to provide some state variable that can be updated by the super class to cause a render. i.e.
class QuizGame extends React.Component {
constructor() {
super();
this.state = {
started: false,
questions: Questions
}
}
startGame() { // JavaScript functions should be camelCase
this.setState({started: true});
}
render() {
if (this.state.started) {
return const { questions } = this.state;
const item = questions[Math.floor(Math.random() * questions.length)];
const items = [item.a, item.b, item.c, item.d];
return (
<div>
<GameParagraph value={item.ques} />
{items.map(quest => (
<div key={quest}>
<div>
<GameButton
value={quest}
onClick={() => {
if(item.ans === quest) {
return console.log('Working');
} else {
return console.log('not working')
}
}}/>
</div>
</div>
))}
</div>);
} else {
return <StartGame onClick={() => this.startGame()} />;
}
}
}

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