The useState set method not rendring the specific component - javascript

I want to update my TaskDetail (at Right Side) component with new card data but when I change the value of card from handleCardClick callback it does not reflect the change to that specific component. If you look at attached gif you will see that useEffect is getting the updated values of card and the page variable in the same call back showing updated value on view.
Here is the minimum code that depict my problem.
const [selectedCard, setSelectCard] = useState(null);
const [selectedColumn, setSelectedColumn] = useState(null);
// Page Updater
const [page, setPage] = useState(0);
const handleCardClick = (e, card, columnId) => {
setSelectedColumn(columnId);
setSelectCard(card);
setPage(page + 1);
}
useEffect(() => {
// action on update of movies
console.log(selectedCard);
console.log(selectedColumn);
}, [selectedCard]);
return (
<Container>
<DragDropContext onDragEnd={handleDragEnd}>
{boardData.map((list) => (
<List key={list.title} column={list} handleAddTask={handleAddTask} handleCardClick={handleCardClick} />
))}
</DragDropContext>
<p>{page}</p>
<> {(page !== 0) && <TaskDetail card={selectedCard} columnId={selectedColumn} />}</>
</Container>
);
This is the most relevant to my question The useState set method is not reflecting a change immediately but I did't find the solution from it.

I would try 2 things:
Check the TaskDetail code to see if it has state management of it's own. If it does, you need to make sure it triggers re-render when the relevant props change (trigger it by calling setState inside the component for example)
Add a unique key to the TaskDetail component to force it to re-render when the key changes.
For example:
<TaskDetail key={selectedCard.id} card={selectedCard} columnId={selectedColumn} />

Related

State inside parent component not setting initial value. Child component is preventing it from working

Child component is passing data as prop via function to Parent. Inside Parent I have a variable with a string value. I also have a useState. The initial value of that useState should be the string value.
But for some reason setting the initial value to the variable doesn't render the state initial value.
const header = () => {
//some data I fetched
const [address, setAddress] = useState("");
//take the string I need
let city = address.split(", ")[0];
//initiate the useState with the string above
const [updateAddress, setUpdateAddress] = useState(city);
//accept new location from SearchLocation component and pass it to the address
const handleChangeLocation = (userInput) => {
setUpdateAddress(userInput);
};
//won't render the value of updateAddress which is the city I passed
return (<div className={styles.city}>{updateAddress}</div>
<SearchLocation changeLocation={handleChangeLocation} />)
}
When I add a primitive value to the city variable or the useState for testing purposes it won't render either.
If I go to the child component(SearchLocation) that transfers the data I need to the parent component and, I remove the prop the city variable in the parent component, the city value will get through fine.
I would appreciate some explanation. Thank you
--
Below is how I did it based on what you said you needed.
You update a state directly in the root of the component since it's asynchronous, you have to update them inside a useEffect hook. Especially if the initial data is fetched from an API.
Your code below wouldn't work.
....
//take the string I need
let city = address.split(", ")[0];
//initiate the useState with the string above
const [updateAddress, setUpdateAddress] = useState(city);
...
Anyway here's what I did, so based from your code an address is in the format of city, xxxx, xxxx, so you need to get the [0] index.
import React, { useEffect, useState } from 'react'
import ReactDOM from 'react-dom'
// make sure your component starts with a capital letter
const Header = () => {
// assign the search keyword to a different state
const [searchKeyword, setSearchKeyword] = useState('')
// then the specific data you want
const [addressCity, setAddressCity] = useState('')
// Setting the initial address from the API
// You need to put updating of the state inside a useEffect or what because it is asynchronous
useEffect(() => {
// fetch from the api
// assign the fetched data to the address state
const addressFromAPI = 'Test, address'
setAddressCity(addressFromAPI.split(', ')[0])
}, [])
// Add a onChange handler for the search input
const handleSearch = (e) => {
// assign the input of the user to the search keyword state
const userInput = e.target.value
setSearchKeyword(userInput)
// now get what you wanted, the city
const city = userInput.split(', ')[0]
console.log(city)
setAddressCity(city)
}
// now everything works
return <>
<div>Search keyword: {searchKeyword}</div>
<div>City: {addressCity}</div>
<input onChange={handleSearch} value={searchKeyword} />
</>
}
ReactDOM.render(
<Header />,
document.getElementById('root'),
)
here's a screenshot
I'm not sure if this is what you wanted to achieve, please let me know if it is or give us more info about your problem:)
Btw it seems like you are in the early stage of developing in react, reminds me of the days. Maybe you could try reading the docs or this free course from scrimba

Force remount when value changes

I have a dropdown list that contains some cart items from a shop. I want the dropdown to re-render every time a cart item is added, but it doesn't and only shows my new cart addition when I close and open the cart again (remounts).
const CartDropdown = () => {
const {setCartProducts, cartProducts} = useContext(CartContext)
const {setProducts, currentProducts} = useContext(ProductsContext)
// useEffect(() => {}, [cartProducts])
const cleanCart = () => {
const cleanProducts = currentProducts
console.log(cleanProducts)
for (let i in cleanProducts) {
if (cleanProducts[i].hasOwnProperty('quantity')){
cleanProducts[i].quantity = 0
}
}
setProducts(cleanProducts)
setCartProducts([])
}
return(
<div className='cart-dropdown-container'>
<div className='cart-items' forceRemount={force}>
{cartProducts.map((product) => (
<div>
<img src={product.imageUrl}></img>
</div>)
)}
</div>
<button onClick={cleanCart}>LIMPAR</button>
<Button children={'FINALIZE PURCHASE'}/>
</div>
)
}
I want the CartDropdown to remount when the cartProducts changes.
It really depends on what those setters and getters on your useContext are returning.
Assuming they are from a useState(), then you have to make sure you always pass a different object to the setters.
From the docs:
Bailing out of a state update
If you update a State Hook to the same value as the current state,
React will bail out without rendering the children or firing effects.
(React uses the Object.is comparison algorithm.)
In other words, simply mutating currentProducts and calling setProducts passing the same (but now mutated) object will not trigger a reprender.
So your code
const cleanProducts = currentProducts
console.log(cleanProducts)
// Mutating cleanProducts
setProducts(cleanProducts)
Should be something like
const cleanProducts = [...currentProducts] // <--- changed this line
console.log(cleanProducts)
// Mutating cleanProducts
setProducts(cleanProducts)
And you have to do this everywhere you call setters.
Another thing, you should add a key attribute to elements used in a .map(). For instance:
{cartProducts.map((product) => (
<div key={product.imageUrl}>
<img src={product.imageUrl}></img>
</div>)
)}

How to update an index inside an array with hooks without hitting "Error: Too many re-renders."

I have an array of cells that I need to update based on some user input from a socket server. But whenever I try to update an index using useState(), react complains about "Error: Too many re-renders.". I tried adding a state to indicate that the grid has changed, but that still produces the same error. Please help me :<
const GameUI = ({ height, width }) => {
const [grid, setGrid] = React.useState([]);
const [didUpdate, setDidUpdate] = React.useState(0);
React.useEffect(() => {
for (let index = 0; index < 64; index++) {
const gridItem = <GridSquare key={`${index}`} color="" />;
setGrid((oldArray) => [...oldArray, gridItem]);
}
}, []);
//not re-rendering the modified component
const handleChange = () => {
let board = [...grid]
board[1] = <GridSquare key={`${0}${1}`} color="1" />;
setGrid(board)
// let count = didUpdate;
// count += 1
// setDidUpdate(count)
};
// handleChange();
return (
<div className="App">
<GridBoard grid={grid} />
<ScoreBoard />
<Players />
{handleChange()}
{/* <MessagePopup /> */}
</div>
);
};
Every time you change a state provided from the useState hook, it re-renders your component. You are calling handleChange on every render, which is calling your setGrid state hook. Therefore, you are rendering your component infinitely.
When do you actually need to call handleChange? Every animation frame? Every action event of some kind? Create an appropriate useEffect or useCallback hook (that includes used dependencies [array] as the second parameter), apply the appropriate event to your method and trigger it accordingly. If you go with a useEffect, don't forget to return a function that disables the event handling when the component is eventually unmounted or else you will have duplicate events triggering every time it gets remounted in the same application.

React.js | Infinite render if I pass setState as a callback function, even after destructuring props

Issue
I have a child component that gets some button id-name configs as props, renders selectable HTML buttons according to those configs and returns the selected button's value(id) to the callback function under a useEffect hook. However it causes an infinite render loop because I need to pass the props as a dependency array. Note that React.js wants me to destructure props, but it still causes an infinite render loop even if I do that.
Child Component
import React, {createRef, useState, useEffect} from "react";
const OptionButton = ({ buttons, buttonClass, callback }) => {
const [value, setValue] = useState()
const refArray = []
const buttonWidth = ((100 - (Object.keys(buttons).length - 1)) - ((100 - (Object.keys(buttons).length - 1)) % Object.keys(buttons).length)) / Object.keys(buttons).length
useEffect(() => {
if (callback) {
callback(value);
}
}, [value, callback])
const select = (event) => {
event.target.style.backgroundColor = "#10CB81"
refArray.forEach((currentRef) => {
if (currentRef.current.id !== event.target.id) {
currentRef.current.style.backgroundColor = "#F5475D"
}
})
setValue(event.target.id)
}
return(
<span style={{display: "flex", justifyContent: "space-between"}}>
{Object.entries(buttons).map((keyvalue) => {
const newRef = createRef()
refArray.push(newRef)
return <button ref={newRef} id={keyvalue[0]} key={keyvalue[0]} className={buttonClass} onClick={select} style={{width: `${buttonWidth}%`}}>{keyvalue[1]}</button>
})}
</span>
)
}
export default OptionButton
So as you can see here my child component gets button configs as key-value (button value-button name) pairs, renders these buttons and when user clicks one of these buttons it gets the id of that button, sets it to 'value' constant using useState hook and then passes that value to parent component callback.
Parent Component
return(
<OptionButton buttons={{"ButtonValue": "ButtonName", "Button2Value": "Button2Name"}} callback={(value) => this.setState({buttonState: value})} buttonClass="buttonclass"/>
)
It's just fine if I don't use this.setState at the callback function. For example if I just do
(value) => console.log(value)
there is no infinite loop. As far as I can tell it only happens if I try to use setState.
What am I doing wrong? How can I fix this? Thanks in advance.
The reason why there is infinite loop is because you have callback as dependency in useEffect. And what you are passing to the component is you pass a new callback function on each new render, hence it always enters useEffect because of that. Since you are using classes consider passing instance method as callback instead of arrow function as here.
Also I think you are overusing refs. You could also achieve what you are doing by just storing say the id of clicked button, and then dynamically styling all buttons, e.g. inside map if id of current button is same as the one stored, use #10CB81 bg color in style object, otherwise different one.
Also there are better ways to check which btn was clicked, see here.

React global variable not being updated inside function that passing to children component

I have a parent component that having some props passing from grandparent component and I am using one prop (object) and pass the value of that object to children component as props. I also pass a function to child component in order to get the updated value back from child component.
ParentComponent.js
const ParentComponent = props => {
const { record, saveRecord } = props;
const editedRecord = {...record}
const handleRecordValues = (name, value) => {
editedRecord[name] = value;
};
...
const content = <div>
<ChildComponent name={record.name} value={record.value} setValue={handleRecordValues} />
<Button onClick={() => saveRecord(editedRecord)} />
</div>
return content;
}
ChildrenComponent.js
const ChildComponent = props => {
const { name, value, setValue } = props;
const [input, setInput] = useState(value);
const handleChange = (e, text) => {
setInput(text);
setValue(name, value);
}
return <TextField value={input} onChange={handleChange}/>
}
Above are the sample components I have. The issue is when I pass the editedRecord to saveRecord func to grandparent component the editedRecord is always the same as record as it is copied from record and value is not updated for that variable. I expect the editedRecord being updated by the handleRecordValues func.
For example, the record that I get is {}. And I create a new const editedRecord which is also {}.
After I input some value from ChildComponent the editedRecord should be updated to {name: value}. However when I click on Button in ParentComponent the editedRecord parameter is still {}.
Updated
Instead of using const I use
const [editedRecord, setEditedRecord] = useState(record);
const handleRecordValues = (name, value) => {
const newRecord = {
...editedRecord
};
newRecord[name] = value;
setEditedRecord(newRecord);
};
Now the editedRecord value got updated but another issue came up:
when I have multiple components as child components it only update the last one entry I have entered.
Your setValue/handleRecordValues function changes a variable ... but React has no way of knowing when that variable changes.
To let React know, you have to call saveRecord(editedRecord) after you make the change, or in other words you have to invoke a state-setting function, so that React knows about the change.
In general in React, if you don't change context/state/props (and for context/state, that means doing so using the appropriate React functions), React can't know to re-render your components in response. This means that any data that your components depend on to render needs to be changed via one of those three mechanisms, not just via ordinary Javascript, ie. a.b = c.
EDIT: To clarify a point in the comments. When you make a state variable:
const [myState, myStateSetter] = useState('');
there is nothing "magic" about myState; it's just another JS variable. Javascript doesn't give React any way to know when that variable changes, so if you just do:
myState = 4;
React has no idea that you did so. It only knows that it changed if you tell it that it changed ... ie. if you call:
myStateSetter(4);
Here's how I would alter the parent component to make everything work with react. The main issue you were having is that react needs to know that a change has occurred, so we need to set up the values as state/set state.
const ParentComponent = props => {
const { record, saveRecord } = props;
const [editedRecord,setEditedRecord] = useState(record);
useEffect(()=>{
//This sets the record straight whenever the parent passes a new record.
//You'd need to make sure the record is referentially stable when it isn't being updated, though
setEditedRecord(record);
},[record])
const handleRecordValues = (name, value) => {
setEditedRecord(record=>{...record,[name]:value});
};
...
const content = <div>
<ChildComponent name={editedRecord.name} value={editedRecord.value} setValue={handleRecordValues} />
<Button onClick={() => saveRecord(editedRecord)} />
</div>
return content;
}

Categories