In my specific case, I want to be able to pass this month_week variable into one of my components.
import { PERIOD_MONTH, PERIOD_WEEK, PERIOD_COUNT } from "../../utils/utility";
export const mainFilterLookup = (props) => {
let month_week = "";
if (period.includes(PERIOD_WEEK.toLowerCase())) {
period = PERIOD_WEEK.toLowerCase();
month_week = props.presentState.week;
} else if (period.includes(PERIOD_MONTH.toLowerCase())) {
period = PERIOD_MONTH.toLowerCase();
month_week = props.presentState.month;
}
let periodCount = PERIOD_COUNT;
let mounted = true;
console.log(
month_week,
period,
login,
year,
periodCount,
);
return {
month_week,
period,
login,
year,
periodCount,
mounted,
};
};
Now this is probably because I don't understand state or props too well but whenever I import it into my other component, it pulls and undefined whenever I call the variable that variable specifically in this case as I need that value from the variable to do this conditional render as seen here.
import React, { useState, useEffect } from "react";
import month_week from "../components/Common/MainFilters.js"
return (
{month_week > 0 &&
<Button
disabled={true}
>
Create Award
</Button>
}
<Button
disabled={false}
>
Create Award
</Button>
)
I feel like this is a state issue, just can't wrap my head around how state is working in this case
Related
import React, {useState, useEffect} from 'react';
const Test = ( {numar}) => {
const [likeStatus, setLikeStatus] = useState(true);
const [likeNumber, setLikeNumber] = useState(100);
const onLikeHandler = () => {
setLikeStatus(prevState => !prevState);
if(likeStatus){
setLikeNumber(prevState=> prevState +1)
} else {
setLikeNumber(prevState=>prevState-1);
}
}
console.log(likeStatus);
console.log(likeNumber);
return <button className={`like ${likeStatus ? 'liked' : ""}`} onClick={onLikeHandler}>{`Like | ${ likeNumber}`}</button>
}
export default Test;
I am trying to make a like button that likes/unlikes based on the click.
How can I make the second change state function wait for my first state function to finish? I tried using an use effect hook, and I am using the likeStatus in the dependecy array, but for some reason "the unlike" function triggers twice upon refresh"
State update is async, and state is being accessed from closure which recreates only after rerender - meaning you can not set state and then in next line to expect that you will get that new state immediately, you will get new state but only in next function call(after element rerenders). Solution is to preserve new state in temp variable.
Rewrite to this:
const onLikeHandler = () => {
const newLikeStatus = !likeStatus; // Here you are preserving that new state in temp variable
setLikeStatus(newLikeStatus);
if(newLikeStatus){
setLikeNumber(prevState=> prevState +1)
} else {
setLikeNumber(prevState=>prevState-1);
}
}
Here is a parent component (Cart) and a child component (CartItem).
in the parent, there is a button that counts on the state that changes by the checkAvailability function which passed to the child via props,
import {useState} from "react";
import CartItem from "./CartItem";
const Cart = ({cart}) => {
const [available, setAvailable] = useState(true);
const checkAvailability = (check) => {
setAvailable(check)
}
return (
<>
{cart.items.map((item) => (
item.is_valid &&
<CartItem
key={item.id}
checkAvailability={checkAvailability}
/>
))}
<button disabled={available} >Click Me!</button>
</>
)
}
export default Cart;
in the child component, an API call returns true or false called by useEffect.
import {useState, useEffect} from "react";
const CartItem = ({checkAvailability}) => {
const [newData, setNewData] = useState(null);
const handleCheck = async () => {
const data = await api.call();
setNewData(data)
if(newData.available === false) {
checkAvailability(false)
} else if(newData.available === true) {
checkAvailability(true)
}
};
useEffect(() => {
handleCheck();
}, []);
return (
<div> Item </div>
)
};
export default CartItem;
issue:
every time the components mount, the API call in the child returns a value, that value gets passed to the parent by the checkAvailability function as a prop, which changes the state in the parent, when the state changes a re-render happen which restarts the circle infinitely.
the main thing is the button gets disabled when the API call returns a {false} value. if this way won't do the job, is there another way of doing it?.
what is the solution?.
Even if you resolved the current issue of infinite re-renders, you'll still have multiple API requests if there are multiple <CartItem/> components.
A better approach will be to move the API call to the parent. This will ensure it's called once regardless of the number of cart items it has. If the cart items need to know the value of available, then pass it to them.
const CartItem = ({ available }) => {
return <div> Item </div>
}
I'm making a React app that requests data from an API and renders the results as cards. The issue I'm having is that I added a 'favorite' star icon to each card, that when clicked, will become highlighted gold, and an additional click will revert it to grey. This is controlled by a Boolean value stored in a React Hook for the local component. The color change works properly, however when I want to access the boolean state variable to continue the rest of the operation, the local state value that I keep accessing seems to be the 'previous' state. I can't figure out how to fix this.
Reading up on the documentation leads me to think that I'm accessing stale state data or stale closures. I'm still having trouble fixing this so I would appreciate some assistance, here's what I have.
import React, { useState } from 'react'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faStar} from '#fortawesome/free-solid-svg-icons'
export const Card = ({ item, addOn, onDelete }) => {
//flags if the star was clicked or not
const [favored, setFavored] = useState(false);
//update favorite component
const updateFavorites = (item) => {
//here thet favored state is toggled
setFavored(!favored);
//further attempts to access favored results in the wrong favored being accessed
if(favored){
console.log(`value of favored is: ${favored}, the start should be gold`);
}else{
console.log(`value of favored is: ${favored}, the star should be grey`);
}
}
return (
<div className = 'card' key = {item.id} >
<FontAwesomeIcon
icon = {faStar}
className = {favored ? 'star favored' : 'star'}
onClick = {(e) => updateFavorites(item)}
/>
<div className = 'card-info'>
<img
src = {item.image_url}
alt = ''
style = {{height: '15rem', width: '5rem'}}
/>
<div>
<h3>{item.name}</h3>
<p className = "description">{item.description}</p>
</div>
</div>
</div>
)
}
Edit: I solved my issue by doing the following:
const updateFavorites = (item) => {
//here thet favored state is toggled
setFavored(favored => {
favored = !favored;
if(favored){
console.log('adding a favorite');
addOn(item);
}else{
console.log('removing a favorite');
onDelete(item);
};
return favored;
});
}
However, I replaced it by using useEffect as per the answers.
Fact is that state values are used by functions based on their current closures, and state updates will reflect in the next re-render by which the existing closures are not affected, but new ones are created. Now in the current state, the values are obtained by existing closures, and when a re-render happens, the closures are updated based on whether the function is recreated again or not.
You can use following solutions.
useEffect(() => {
if (favored) {
console.log(`value of favored is: ${favored}, the start should be gold`);
} else {
console.log(`value of favored is: ${favored}, the star should be grey`);
}
}, [favored,item]);
You should import useEffect and move the logic from the event handler to the useEffect hook:
import React, { useState, useEffect } from 'react'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faStar} from '#fortawesome/free-solid-svg-icons'
export const Card = ({ item, addOn, onDelete }) => {
const [favored, setFavored] = useState(false);
const updateFavorites = () => {
setFavored(!favored);
}
// Move the desired logic here and add 'favored' and 'item' to your dependency array:
useEffect(() => {
if(favored){
console.log(`value of favored is: ${favored}, the start should be gold`);
}else{
console.log(`value of favored is: ${favored}, the star should be grey`);
}
}, [favored, item])
return (
<div className = 'card' key = {item.id} >
<FontAwesomeIcon
icon = {faStar}
className = {favored ? 'star favored' : 'star'}
onClick = {() => updateFavorites()}
/>
<div className = 'card-info'>
<img
src = {item.image_url}
alt = ''
style = {{height: '15rem', width: '5rem'}}
/>
<div>
<h3>{item.name}</h3>
<p className = "description">{item.description}</p>
</div>
</div>
</div>
)
}
I'm trying to use react Hooks to update my react component when a change to the state holding the api information occurs. I'm currently creating a quiz application where the user can create a quiz , by entering data such as title and answers, which is then sent to a mongodb. When the user is not editing the quiz, I want to display the updated values from the database .
For example:
parent component
import React,{useState} from 'react';
export default function Context() {
const [data,setData] = useState(null);
const [selected,setSelected] = useState(id); // id is taken from url
useEffect(() => {
getKahootQuestion();
}, [data])
async function getKahootQuestion(slide) {
if(selected !== undefined) {
let getKahootQuestion = await withData(`quizQuestion/single/${selected}`,'GET');
if(getKahootQuestion) {
setData(getKahootQuestion.data);
console.log(getKahootQuestion);
}
}
}
}
export default function MainContent() {
// I want to be able to add items to Database and re-render components to reflect the changes
let context = useContext(Context);
let title = context.data[0].title;
async function submitTitle(e) {
e.preventDefault();
let form = e.currentTarget;
let title = form['title'].value;
setEditable(false);
let updateKahootQuestion = await withData(`quizQuestion/${context.selected}`,'PUT',{title});
}
return (
<>
<form>
<input name='title' />
</form>
<p>{title}</p>
</>
)
}
I am not sure if your intention is to do this strictly through the context, but we will need more of it there to know what properties and functions are exposed from it.
Regardless, to trigger a re-render on your MainContent component you could also simply add in a local useState and update as needed. It should look something like this:
export default function MainContent() {
let context = useContext(Context);
// Instead of just declaring a title variable, declare a title state using that same information.
const [title, setTitle] = useState(context.data[0].title)
async function submitTitle(e) {
e.preventDefault();
let form = e.currentTarget;
let title = form['title'].value;
setEditable(false);
let updateKahootQuestion = await withData(`quizQuestion/${context.selected}`,'PUT',{title});
// Not sure what your updateKahootQuestion will look like, so for the sake of this example, will just use title.
setTitle(title) // will trigger a re-render
}
return (
<>
<form>
<input name='title' />
</form>
<p>{title}</p>
</>
)
}
I was playing around with react-dev-tools chrome extension and found out that all my components are re-rendering.
App.js
import React from 'react';
import './App.css';
import Header from './components/molecules/Header/Header';
// import { colorListGenerator } from './core-utils/helpers';
import ColorPalette from './components/organisms/ColorPalette/ColorPalette';
export const colorListGenerator = (n) => {
let colorArray = []
for(let i=0; i<n; i++) {
let randomColor = '#'+Math.floor(Math.random()*16777215).toString(16);
let id="id" + Math.random().toString(16).slice(2)
console.log(typeof(id), id)
let color = {
id: id,
hex: randomColor
}
colorArray.push(color);
}
return colorArray
}
const App = () => {
const colors=colorListGenerator(10);
return (
<div className="App">
<Header/>
<ColorPalette colorPalette={colors} />
</div>
);
}
export default App;
ColorPalette.js
/* eslint-disable eqeqeq */
import React from 'react';
import Color from '../../atoms/Color';
import './ColorPalette.css';
const ColorPalette = ({ colorPalette }) => {
const [colors, setColors] = React.useState(colorPalette);
// const handleColorClick = (event) => {
// const id = event.currentTarget.getAttribute('id')
// const index = colors.findIndex(item => item.id == id);
// setColors(colors.filter(item => item.id != id))
// }
const deleteItem = (id) => {
setColors(colors.filter(item => item.id != id))
}
return (
<div className={'colorPalette'}>
{colors && colors.map((color, index) => {
// const key = index
const key = color.id
return <Color
key={key}
color={color.hex}
colorKey={key}
handleColorClick = {() => {deleteItem(color.id)}}
/> })}
</div>
)
}
// export default React.memo(ColorPalette);
export default ColorPalette;
Color.js
import React from 'react';
import './Color.css';
import deleteIcon from '../../../delete-icon.png'
const Color = ({ color, colorKey, handleColorClick }) => {
return (
<div className="color"
style={{ backgroundColor: color }}
// no need to mention key here
// key={colorKey}
id={colorKey}
onClick={handleColorClick} >
<p> {colorKey} </p>
<img src={deleteIcon}
alt={'delete'}
className="delete"
/>
</div>
)
}
// export default React.memo(Color);
export default Color;
When I use the profiler to check why all my 'Color' components have re-rendered after deleting a single item, it complains that handleColorClick prop has changed. I changed the deleteItem to handleColorClick which isn't an arrow function, but the result is the same. I'm also passing unique ids. Interestingly, when I pass const key = Math.random() instead of const key = color.id my Color components are not rerendering. So it has something to do with the keys. I want to understand why my components are rerendering when I pass unique ids as keys.
The only way a React functional component will be prevented from rerendering is by using React.memo to memoize the component. Memoization here means that if the component's props do not change - they are strictly equivalent to each other using the === operator - then the component's last render output will be re-used instead of rerendering the entire component.
However, React.memo itself gets tricky when you're talking about props that are object or functions - values for which the strict === comparison checks referential equality. That means that for functions like deleteItem need to use something like React.useCallback to memoize the references themselves so that they themselves do not change between renders, which will trip up React.memo and lead to rerenders in situations where intuitively it seems like it shouldn't.
As you can see, it quickly starts to get quite complicated, as you try to keep track of memoizing your functions, your objects, your components, etc.
And really, what's the point?
The performance gains you get from memoization - if they even materialize - are miniscule. This is a classic case of premature optimization, sometimes called the "root of all evil" because of what an unnecessary time sink it is, for little to no gain, and the cost of added complexity.
React itself in its optimized production build is insanely fast, good at resolving diffs, and in most cases could rerender your entire app dozens of times per second without any perceivable slowdown. You should ONLY start optimizing your app with things like memoization when you have ACTUAL, MEASURABLE impacts to performance that you need to address.
In short, you do not need to worry about "unnecessary" rerenders.
I'll say it again for emphasis:
DO NOT WORRY ABOUT "UNNECESSARY" RERENDERS.
Seriously.
PS: The reason using a random value for key makes it seem like unnecessary rerenders are eliminated is because every time a component renders it is literally a brand new instance of that component, not the same component being rerendered. React uses the key prop under the hood to track which component is which between renders. If that value is unreliable, it means that React is literally rendering NEW components every time. You're basically destroying all the old components and recreating them from scratch, albeit with the same props or whatever, but make no mistake, they are NOT the same components between renders. (Even their internal state including hooks will be erased)
As per what you said handleColorClick prop has changed, which is why the components are getting re-rendered. Since you are using functional component and hooks in the component, when the component is getting re-rendered the function handleColorClick is redefined again and the reference is getting changed. That's the reason why the components are getting re-rendered even though you pass unique ids as keys.
In order to avoid that you can use useCallback hook which will help you not to get a new function reference unless there's a change in the dependencies provided to the useCallback hook
/* eslint-disable eqeqeq */
import React, {useCallback} from 'react';
import Color from '../../atoms/Color';
import './ColorPalette.css';
const ColorPalette = ({ colorPalette }) => {
const [colors, setColors] = React.useState(colorPalette);
// const handleColorClick = (event) => {
// const id = event.currentTarget.getAttribute('id')
// const index = colors.findIndex(item => item.id == id);
// setColors(colors.filter(item => item.id != id))
// }
const deleteItem = useCallback((id) => {
setColors(colors.filter(item => item.id != id))
}, [])
return (
<div className={'colorPalette'}>
{colors && colors.map((color, index) => {
// const key = index
const key = color.id
return <Color
key={key}
color={color.hex}
colorKey={key}
handleColorClick = {() => {deleteItem(color.id)}}
/> })}
</div>
)
}
// export default React.memo(ColorPalette);
export default ColorPalette;