React: stale props in child component - javascript

Here is the CodeSandBox link: https://codesandbox.io/s/stale-prop-one-g92sv?file=/src/App.js
I find the child components will not show the correct counter values after two button clicks, though the counter is actually incrementing:
import "./styles.css";
import { useState } from "react";
const MyComponent = ({ value }) => {
const [counter] = useState(value);
return <span>{counter}</span>;
};
export default function App() {
const [counter, setCounter] = useState(0);
const isVisible = counter !== 1;
console.log(counter);
return (
<div className="App">
<div>
<button onClick={() => setCounter((counter) => counter + 1)}>
Click me
</button>
</div>
{isVisible && (
<div>
Message 1 is: <MyComponent value={counter} />
</div>
)}
<div style={isVisible ? { display: "block" } : { display: "none" }}>
Message 2 is: <MyComponent value={counter} />
</div>
</div>
);
}
I try to force child component re-render by assigning counter to its key:
export default function App() {
const [counter, setCounter] = useState(0);
const isVisible = counter !== 1;
console.log(counter);
return (
<div className="App">
<div>
<button onClick={() => setCounter((counter) => counter + 1)}>
Click me
</button>
</div>
{isVisible && (
<div>
Message 1 is: <MyComponent key = {counter} value={counter} />
</div>
)}
<div style={isVisible ? { display: "block" } : { display: "none" }}>
Message 2 is: <MyComponent key = {counter} value={counter} />
</div>
</div>
);
}
It works, but I still have no idea why the previous one does not work, since the props.value in MyComponent has changed...
Thanks in advance.

With this:
const MyComponent = ({ value }) => {
const [counter] = useState(value);
return <span>{counter}</span>;
};
You're telling React to set the initial state to the first value prop passed to the component, on mount.
When the component re-renders, the component has already been mounted, so the value passed to useState is ignored - instead, the counter in that child is taken from the state of MyComponent - which is equal to the initial state in MyComponent, the initial value prop passed.
For what you're trying to do, you only have a single value throughout the app here that you want to use everywhere, so you should only have one useState call, in the parent - and then render the counter in the child from the prop, which will change with the parent state.
const MyComponent = ({ value }) => {
return <span>{value}</span>;
};

Related

React parent couldn't update a child component in a mapped JSX list

NOTE: You can view and edit the code in CodeSandbox.
I have the following parent file which creates a useState list of child component called ProgressBar:
import React, { useState } from 'react';
import ProgressBar from './ProgressBar';
import './App.css';
var idCounter = 0;
export default function App() {
const [barsArray, setBarsArray] = useState([]);
const [input, setInput] = useState('');
function add() {
setBarsArray((prev) => [
...prev,
<ProgressBar key={idCounter++} restart={false} />,
]);
}
function remove() {
setBarsArray((prev) => prev.filter((bar) => bar.key !== input));
}
function reset() {
setBarsArray((prev) =>
prev.map((bar) => (bar.key === input ? { ...bar, restart: true } : bar))
);
}
return (
<div className='App'>
<div className='buttons'>
<button className='button-add' onClick={add}>
Add
</button>
<button className='button-delete' onClick={remove}>
Delete
</button>
<button className='button-delete' onClick={reset}>
Reset
</button>
<input
type='number'
value={input}
onInput={(e) => setInput(e.target.value)}
/>
</div>
<div className='bars-container'>
{barsArray.map((bar) => (
<div className='bars-index' key={bar.key}>
{bar}
<p>{bar.key}</p>
</div>
))}
</div>
</div>
);
}
The file of the child ProgressBar has the following content:
import React, { useEffect, useState } from 'react';
import './ProgressBar.css';
export default function ProgressBar(props) {
const [progress, setProgress] = useState(0);
let interval;
useEffect(() => {
interval = setInterval(() => {
setProgress((prev) => prev + 1);
}, RnadInt(10, 120));
}, []);
useEffect(() => {
if (progress >= 100) clearInterval(interval);
}, [progress]);
if (props.restart === true) {
setProgress(0);
}
return (
<>
<div className='ProgressBarContainer'>
<div className='ProgressBar' style={{ width: progress + '%' }}></div>
</div>
</>
);
}
function RnadInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
My problem is that the reset button in the parent doesn't work, as far as I'm concerned, if you change the passed props to the child, the child automatically re-renders, but even though I'm updating the props in reset function in the parent, which maps the old array of child components to a new array and only changes the props of the selected child.
Thanks!
Adding the element in state via add would require to keep the ref of element instead of actual prop bind to element. Suggestion here to use the model object and while rendering use the JSX element.
Please use the below code which defines the barsArray as object state and later uses it render ProgressBar component (from map call).
Check the working codesandbox - https://codesandbox.io/s/admiring-glitter-j3b7sw?file=/src/App.js:0-1446
import React, { useState } from "react";
import ProgressBar from "./ProgressBar";
import "./App.css";
var idCounter = 0;
export default function App() {
const [barsArray, setBarsArray] = useState([]);
const [input, setInput] = useState("");
function add() {
setBarsArray((prev) => [...prev, { id: idCounter++, restart: false }]);
}
function remove() {
setBarsArray((prev) =>
prev.filter((bar) => bar.id.toString() !== input.toString())
);
}
function reset() {
setBarsArray((prev) =>
prev.map((bar) => {
return bar.id.toString() === input.toString()
? { ...bar, restart: true }
: { ...bar };
})
);
}
return (
<div className="App">
<div className="buttons">
<button className="button-add" onClick={add}>
Add
</button>
<button className="button-delete" onClick={remove}>
Delete
</button>
<button className="button-delete" onClick={reset}>
Reset
</button>
<input
type="number"
value={input}
onInput={(e) => setInput(e.target.value)}
/>
</div>
<div className="bars-container">
{barsArray.map((bar) => (
<div className="bars-index" key={bar.id}>
<ProgressBar key={bar.id} restart={bar.restart} />
<p>{bar.key}</p>
</div>
))}
</div>
</div>
);
}
This simple fix worked for me, in the file of the child, call the conditional expression in a useEffect hook. Currently, it doesn't listen to the changes in props.restart and only runs on the initial render.
check https://reactjs.org/docs/hooks-reference.html#useeffect
useEffect(() => {
if (props.restart === true) {
setProgress(0);
}
}, [props.restart])
link to working codesandbox https://codesandbox.io/s/distracted-gauss-24d0pu

Getting the value of a checkbox inside another component when a button is clicked

I have two components, the parent App component and a child component (Checkbox component) in this scenario. I have a checkbox inside the child component when this checkbox is clicked or checked.
I want the result to be displayed on the console only when a button is clicked inside the parent component. What I have presently is only showing on the console when the checkbox is checked and this is happening inside the child component.
import { useState } from "react";
// import Checboxtest from "./Checkboxtest";
import "./styles.css";
// const Person = props => (
const Checboxtest = ()=> {
const [isChecked, setIsChecked] = useState(false);
const [checkboxvalue, setcheckboxvalue] = useState("");
const handleOnChange = (e) => {
setIsChecked(!isChecked);
setcheckboxvalue(e.target.value);
};
return (
<div className="App">
<h1>Checkbox Value </h1>
<input
type="checkbox"
id="Orange"
name="Orange"
value="Orange"
checked={isChecked}
onChange={handleOnChange}
/>
Orange
{isChecked ? console.log(checkboxvalue) : console.log("nill")}
</div>
);
}
export default function App() {
return (
<div className="App">
<h1>When clicked on the button below, the value of the checkbox inside the child component should be display on the console.</h1>
<button>See checkbox value</button>
<Checboxtest />
</div>
);
}
You need another state value to control this scenario. It can be implemented in your App component or inside of your Checkbox component.
Let implement the state in the parent component, so define a state in the App component:
export default function App() {
// create a state value
const [showResult, setShowResult] = useState(false);
// create handler
const handleShowResult = () => setShowResult(prevState => !prevState)
return (
<div className="App">
<h1>When clicked on the button below, the value of the checkbox inside the child component should be display on the console.</h1>
<button onClick={handleShowResult}>See checkbox value</button>
<Checboxtest showResult={showResult}/>
</div>
);
}
When you click on the button, the showResult value will change. the showResult props will pass the state of checkbox result visibility to the Checkbbox component.
Now, implement the showResult props in the Checkbox:
const Checboxtest = ({showResult})=> {
const [isChecked, setIsChecked] = useState(false);
const [checkboxvalue, setcheckboxvalue] = useState("");
const handleOnChange = (e) => {
setIsChecked(!isChecked);
setcheckboxvalue(e.target.value);
};
return (
<div className="App">
<h1>Checkbox Value </h1>
<input
type="checkbox"
id="Orange"
name="Orange"
value="Orange"
checked={isChecked}
onChange={handleOnChange}
/>
// check showResult value
Orange
{showResult ? console.log(checkboxvalue) : console.log("nill")}
</div>
);
}
You can manage state on the parent component i.e. in App.js and pass the isChecked and changeCheckBoxValue to Checboxtest. so that checked value is propagated to parent value and when you click button, App.js component has all details.
App.js
import React, { useState } from 'react';
// import Test from './Test';
import Checboxtest from './Checboxtest';
const App = () => {
const [isChecked, setIsChecked] = useState( false );
const [checkboxvalue, setcheckboxvalue] = useState("nil");
const changeCheckBoxValue = (e) => {
const val = e.target.value;
setIsChecked(state => !state);
isChecked ? setcheckboxvalue("nil"): setcheckboxvalue(val);
}
const seeCheckedValue = () => {
console.log( checkboxvalue )
}
return (
<div>
<div className="App">
<h1>When clicked on the button below, the value of the checkbox inside the child component should be display on the console.</h1>
<button onClick={seeCheckedValue}>See checkbox value</button>
<Checboxtest isChecked={isChecked}
changeCheckBoxValue={changeCheckBoxValue}
/>
</div>
</div>
);
};
export default App;
Checboxtest Component
const Checboxtest = ( { isChecked, changeCheckBoxValue } ) => {
return (
<div className="App">
<h1>Checkbox Value </h1>
<input
type="checkbox"
id="Orange"
name="Orange"
value="Orange"
checked={isChecked}
onChange={changeCheckBoxValue}
/>
Orange
</div>
);
}
export default Checboxtest

React JS pass the data or child component to parent component

Is it possible to pass the data from the child component to the parent component using props?
-Parent component
--- ItemList component.
--- DisplatSelect component from the itemList component
I have a list of item in the child component which came from to the parent component, then I want to send the index of the selected data to the other child component located in the parent component.
Can't example well, kindly see the attached screenshot for other references.
Thanks a lot!
enter image description here
You can keep the data in the Parent component and use a function to pass the props from the child to the Parent. This concept is called Lifting State Up where you define the state at the highest common ancestor so all the child components are using the same data which in this case is the selecetd item
function Parent() {
const [selectedItem, setSelectedItem] = useState(null);
const data = []; // Your Data
return (
<>
<h1>Your selected Item = {selectedItem}</h1>
{data.map((item) => {
<Child item={item} setSelectedItem={setSelectedItem} />;
})}
</>
);
}
function Child({ item, setSelectedItem }) {
return <Button onClick={() => setSelectedItem(item.id)}> {item} </Button>;
}
The simplest way, I think, is for the child component where the selection is made to accept a function properly, something like onSelectionChanged. If you had a button for each item passed to the child you could do something like:
Child Component A
const ChildA = ({ items, onSelectionChanged }) => {
return (
<div>
{items.map((item, index) => (
<button onClick={() => onSelectionChanged(index)}>Item</button>
))}
</div>
)
}
Child Component B
const ChildB = ({ selectedItem }) => {
return (
<div>
Selected {selectedItem}
</div>
)
}
Parent Component
const Parent = () => {
const [selection, sets election] = useState({});
const onSelectionChanged = index => {
console.log(`ChildA selection changed: ${index}`);
}
return (
<div>
<ChildA items={items} onSelectionChanged={onSelectionChanged} />
<ChildB selectedItem={selection} />
</div>
)
}
So when your child component handles a change in the selection, it invokes the function passed as a prop onSelectionChanged. You can pass whatever data you want from ChildA to that function.
Note that the parent Component keeps the selected value (from ChildA) in local state, then passes that value to ChildB via a prop.
You can have a state variable in the parent component and pass it to child components to share data between them. I'll post a sample code block on how you can do this for your case.
export default function ParentComponent (props) {
const data = ['image_1_url', 'image_2_url', ...] // Data for the images
const [selectedIndex, setSelectedIndex] = useState(-1); // Selected index (-1 represents no selection)
return (
<ImageList data={data} selectImage={setSelectedIndex} />
{(selectedIndex !== -1) ? (<SelectedImage data={data[selectedIndex]} />) : (<No ImageSelected/>)}
);
}
And the image list component can then use the selectImage prop to select the image
export default function ImageList (props) {
return (
<div>
props.data.map((imageUrl, index) => (
<div onClick={() => {props.setSelected(index)}}>
<img src={imageUrl}/>
</div>
))
</div>
);
}
Yes it's possible. We have one parent state value and update every on click child component to the component.
import React, { useState } from "react";
const Child1 = (props) => {
return (
props.items.map( (item, index) => (
<button key={index.toString()} onClick={() => { props.updateIndex(item.id) }}>
{item.name}
</button>
) )
)
}
const Child2 = (props) => {
return (
<h1>Item selected: {props.selectItem}</h1>
)
}
const ParentComponent = () => {
const listItems = [
{
id:1,
name: "sample name 1"
},
{
id:2,
name: "sample name 2"
}
]
const [selectItem, setSelectItem] = useState('None');
return (
<>
<Child1 items={listItems} updateIndex={setSelectItem}/>
<Child2 selectItem={selectItem}/>
</>
)
}
export default function App() {
return (
<div className="App">
<ParentComponent/>
</div>
);
}

How to pass HTML attributes to child component in React?

I have a parent and a child component, child component has a button, which I'd like to disable it after the first click. This answer works for me in child component. However the function executed on click now exists in parent component, how could I pass the attribute down to the child component? I tried the following and it didn't work.
Parent:
const Home = () => {
let btnRef = useRef();
const handleBtnClick = () => {
if (btnRef.current) {
btnRef.current.setAttribute("disabled", "disabled");
}
}
return (
<>
<Card btnRef={btnRef} handleBtnClick={handleBtnClick} />
</>
)
}
Child:
const Card = ({btnRef, handleBtnClick}) => {
return (
<div>
<button ref={btnRef} onClick={handleBtnClick}>Click me</button>
</div>
)
}
In general, refs should be used only as a last resort in React. React is declarative by nature, so instead of the parent "making" the child disabled (which is what you are doing with the ref) it should just "say" that the child should be disabled (example below):
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({isDisabled, onButtonClick}) => {
return (
<div>
<button disabled={isDisabled} onClick={onButtonClick}>Click me</button>
</div>
)
}
Actually it works if you fix the typo in prop of Card component. Just rename hadnlBtnClick to handleBtnClick
You don't need to mention each prop/attribute by name as you can use javascript Object Destructuring here.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = (props) => {
return (
<div>
<button {...props}>Click me</button>
</div>
)
}
You can also select a few props and use them differently in the child components. for example, see the text prop below.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card text="I'm a Card" isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({text, ...restProps}) => {
return (
<div>
<button {...restProps}>{text}</button>
</div>
)
}

react hooks: useState nested other useState, setState is not working?

i write example of dynamically adding text with react useHooks.And using useState to nest.
But, there is a problem, the setState is not working?
when i click the text,The setState in the component does not take effect(Normally, it is add a class to the currently clicked text and delete the class on other texts), how do i do?
Below is the specific code linkļ¼š
https://codesandbox.io/s/nifty-sky-z0p9w?file=/src/App.js
Thanks!
The issue in your code is that is this:
const [activeItem, setActiveItem] = useState(null);
You are defining this at App level. And you are expecting this will work in TableCel which in another component which has its own state.
Better way would be out your TableCel component outside from App and use state there. If you need to use inside App then pass activeItems and setActiveItem as a props as well.
Here is code:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [activeItem, setActiveItem] = useState(null);
const TableCel = (props) => {
const { title } = props;
return <div>{title}</div>;
};
const cell = [
{
key: 1,
header: <TableCel id={1} title={"Header"} />,
render: (cvm) => (
<>
<p>--</p>
</>
)
},
{
key: 2,
header: <TableCel id={2} title={"Header"} />,
render: (cvm) => (
<>
<p>--</p>
</>
)
},
{
key: 3,
header: <TableCel id={3} title={"Header"} />,
render: (cvm) => (
<>
<p>--</p>
</>
)
},
{
key: 4,
header: <TableCel id={4} title={"Header"} />,
render: (cvm) => (
<>
<p>--</p>
</>
)
},
{
key: 5,
header: <TableCel id={5} title={"Header"} />,
render: (cvm) => (
<>
<p>--</p>
</>
)
}
];
const [cellList, addCells] = useState(cell);
const [count, setCount] = useState(6);
// console.log(cellList);
const addCell = (value) => {
const _cell = {
key: value,
header: <TableCel title="Header" id={value} />,
render: (cvm) => (
<>
<p>--</p>
</>
)
};
addCells([...cellList, _cell]);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button
onClick={() => {
setCount(count + 1);
addCell(count + 1);
}}
>
add li
</button>
<div>
{cellList
.filter((cell) => cell.key < 60)
.map((filteredPerson, index) => (
<li
key={index}
onClick={() => {
setActiveItem(filteredPerson.key);
}}
className={filteredPerson.key === activeItem ? "cel-active" : ""}
>
{filteredPerson.header}
</li>
))}
</div>
</div>
);
}
Here is the demo: https://codesandbox.io/s/morning-cookies-8eud6?file=/src/App.js:0-2086
The reason it does not work its because you're accessing the value of activeItem state inside of a function. accessing state inside of a function is not recommended cause it will always have the initial value even if the state were updated. That's why <TableCel> does not re-render since it does not know that activeItem state had already change.
I recommend that you only access the state inside of useEffect(), useCallback(), useMemo() and inside of the return statement of your component.
For Example:
function App() {
const [state, setState] = useState('initial Value')
function someFunction(){
// It's not recommended to access the state inside of this function
// cause the value of the state will always be ('initial Value')
// even if the state were updated
console.log(state)
}
useEffect(()=>{
// It's good if you access the value of the state here
// It will be assured it will always have the updated value
console.log(state)
},[state])
return (
// You can also access the value of the state inside return statement
<>
{console.log(state)}
<SomeComponent props={state}/>
</>
)
}
Solution:
Pass the state of activeItem by using context hook. In this way <TableCel> will really know everytime activeItem state change.
Take a look in this code in sandbox link were I use context hook to solve the problem
https://codesandbox.io/s/quiet-star-zo4ej?file=/src/App.js

Categories

Resources