Update state of a immutable component from parent component - javascript

I am developing for a use case where I need to slightly change a pre existing component. This is the code for the preexisting component, but I can not change the ABC.tsx file, can just import it.
export const ABC: React.FC<IABC> = (props) => {
const [selectedKey, setSelectedKey] = useState('0');
return ();
};
Now, the use case is from the calling component I want to change the selectedKey state of ABC to a different value say '1'. Is there a way to do so in react? I can use the ABC to create clones/wrapper components but not sure how to proceed with modifying states of ABC. Any leads would really help.

you will have to tamper with the ABC.tsx file, maybe do something like this
export const ABC: React.FC<IABC> = (props) => {
const [selectedKey, setSelectedKey] = useState(props.abc_integer);
return ();
};
Otherwise there is no other way to do it, even if you run a clone or do some React children on, you will still need to tamper. get whoever is on your team that wrote component ABC to change it to suit what you want or you simple clone its code and run it through a wrapper.
psa: As far as I know, any other method is anti-react pattern

Related

React Testing Library: OnClick isn't working in a child component

I have a test like this:
it('Side effect should trigger', async ()=>{
await act(async () => {
let container = render(<Grandparent/>);
const btn = container.findByTestId('childBtn');
fireEvent.click(btn)
});
const changedMsg = await container.findByText('Changed');
expect(changedMsg).toBeInTheDocument();
})
My components look like this:
Grandparent:
const Grandparent = ()=>{
const [labelText, setLabelText] = useState('Unchanged');
return (<Parent labelText={labelText} setLabelText={setLabelText}/>);
}
Parent:
const Parent = ({labelText, setLabelText})=>{
return (<div>
<label>{labelText}</label>
<Child setLabelText={setLabelText} />
</div>)
}
Child:
const Child = ({setLabelText}) =>{
return(<div>
<Button data-testid='childBtn' onClick={()=>setLabelText('Changed')}/>
</div>)
}
My issue is that the assertion in my unit test fails. I've logged the return value from findByTextId('childBtn') and I am indeed getting a button (something like <button class='MuiButtonBase-root' data-testid='childBtn'></button>), but the side effect from clicking it is not going through. I added some logging in the onClick function and it never showed up when I re-ran the test, so the issue is that the onClick handler isn't firing at all.
How do I fix this? Is trying to test something that involves side effects from a bunch of nested components like this not feasible in the first place?
EDIT: I think you have some code mistakes in your unit test since you are mixing react-testing-library API and testing-library, try this:
it("Side effect should trigger", async () => {
const {findByTestId, findByText} = render(<Grandparent />);
fireEvent.click(await findByTestId("childBtn"));
const changedMsg = await findByText("Changed");
expect(changedMsg).toBeInTheDocument();
});
});
I did not test it, but it should work.
act() wrapper is not needed in react-testing-library since the render method is using it internally.
With react-testing-library you dont' have to care about implementation details of your components, nor you care to test internal state, etc... You just test "What the end user sees on screen in response to certain events". If you need to test functions, like fetch, interactions with global object, etc... you need to mock all those methods.
The philosophy of this approach is to make your unit tests ( which are never funny to write ) so that they won't have to change too often even if you are going to refactor your React Components. You just care about what it' s being rendered as DOM nodes and that's what you have to test.
NOTE: In the code you pasted there are typos btw, props have to be destructured in your code if you want to use them directly without doing props.prop, like : const Child = ({setLabelText}) =>{ .

Children useCallback dependency hell

From what I understand you use useCallback to prevent rerendering so I've been using it in every function and my spider senses are telling me it already sounds bad.
But the story doesn't ends there, since I've been using it everywhere I'm now passing dependencies to all my child components that they shouldn't need to worry about like in the following example :
EDIT // SANDBOX: https://codesandbox.io/s/bold-noether-0wdnp?file=/src/App.js
Parent component (needs colorButtons and currentColor)
const ColorPicker = ({onChange}) => {
const [currentColor, setCurrentColor] = useState({r: 255, g:0, b: 0})
const [colorButtons, setColorButtons] = useState({0: null})
const handleColorButtons = useCallback((isToggled, id) => {
/* code that uses colorButtons and currentColor */
}, [colorButtons, currentColor])
return <div className="color-picker">
<RgbColorPicker color={currentColor} onChange={setCurrentColor} />
<div className="color-buttons">
{
Object.entries(colorButtons).map(button => <ColorButton
//...
currentColor={currentColor}
onClick={handleColorButtons}
colorButtons={colorButtons}
/>)
}
</div>
</div>
}
1st child (needs style and currentColor but gets colorButtons for free from its parent)
const ColorButton = ({currentColor, onClick, id, colorButtons}) => {
const [style, setStyle] = useState({})
const handleClick = useCallback((isToggled) => {
/* code that uses setStyle and currentColor */
}, [style, currentColor, colorButtons])
return <ToggleButton
//...
onClick={handleClick}
style={style}
dependency1={style}
dependency2={currentColor}
dependency3={colorButtons}
>
</ToggleButton>
}
2nd child (only needs its own variables but gets the whole package)
const ToggleButton = ({children, className, onClick, style, data, id, onRef, ...dependencies}) => {
const [isToggled, setIsToggled] = useState(false)
const [buttonStyle, setButtonStyle] = useState(style)
const handleClick = useCallback(() => {
/* code that uses isToggled, data, id and setButtonStyle */
}, [isToggled, data, id, ...Object.values(dependencies)])
return <button
className={className || "toggle-button"}
onClick={handleClick}
style={buttonStyle || {}}
ref={onRef}
>
{children}
</button>
}
Am I doing an anti-pattern and if so, what is it and how to fix it ? Thanks for helping !
React hook useCallback
useCallback is a hook that can be used in functional React components. A functional component is a function that returns a React component and that runs on every render, which means that everything defined in its body get new referential identities every time. An exception to this can be accomplished with React hooks which may be used inside functional components to interconnect different renders and maintain state. This means that if you save a reference to a regular function defined in a functional component using a ref, and then compare it to the same function in a later render, they will not be the same (the function changes referential identity between renderings):
// Render 1
...
const fnInBody = () => {}
const fn = useRef(null)
console.log(fn.current === fnInBody) // false since fn.current is null
fn.current = fnInBody
...
// Render 2
...
const fnInBody = () => {}
const fn = useRef(null)
console.log(fn.current === fnInBody) // false due to different identity
fn.current = fnInBody
...
As per the docs, useCallback returns "a memoized version of the callback that only changes if one of the dependencies has changed" which is useful "when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders".
To sum up, useCallback will return a function that maintains its referential identity (e.g. is memoized) as long as the dependencies don't change. The returned function contains a closure with the used dependencies and must thus be updated once the dependencies change.
This results in this updated version of the previous example
// Render 1
...
const fnInBody = useCallback(() => {}, [])
const fn = useRef(null)
console.log(fn.current === fnInBody) // false since fn.current is null
fn.current = fnInBody
...
// Render 2
...
const fnInBody = useCallback(() => {}, [])
const fn = useRef(null)
console.log(fn.current === fnInBody) // true
fn.current = fnInBody
...
Your use case
Keeping the above description in mind, let's have a look at your use of useCallback.
Case 1: ColorPicker
const handleColorButtons = useCallback((isToggled, id) => {
/* code that uses colorButtons and currentColor */
}, [colorButtons, currentColor])
This function will get a new identity every time colorButtons or currentColor changes. ColorPicker itself rerenders either when one of these two are set or when its prop onChange changes. Both handleColorButtons and the children should be updated when currentColor or colorButtons change. The only time the children benefit from the use of useCallback is when only onChange changes. Given that ColorButton is a lightweight component, and that ColorPicker rerenders mostly due to changes to currentColor and colorButtons, the use of useCallback here seems redundant.
Case 2: ColorButton
const handleClick = useCallback((isToggled) => {
/* code that uses setStyle and currentColor */
}, [style, currentColor, colorButtons])
This is a situation similar to the first case. ColorButton rerenders when currentColor, onClick, id or colorButtons change and the children rerender when handleClick, style, colorButtons or currentColor change. With useCallback in place, the props id and onClick may change without rerendering the children (according to the above visible code at least), all other rerenders of ColorButton will lead to its children rerendering. Again, the child ToggleButton is lightweight and id or onClick are not likely to change more often than any other prop so the use of useCallback seems redundant here as well.
Case 3: ToggleButton
const handleClick = useCallback(() => {
/* code that uses isToggled, data, id and setButtonStyle */
}, [isToggled, data, id, ...Object.values(dependencies)])
This case is elaborate with a lot of dependencies but from what I see, one way or the other, most of the component props will lead to a "new version" of handleClick and with the children being lightweight components, the argument to use useCallback seems weak.
So when should I use useCallback?
As the docs say, use it in the very specific cases when you need a function to have referential equality between renders ...
You have a component with a subset of children that are expensive to rerender and that should rerender much less often than the parent component but rerender due to a function prop changing identity whenever the parent rerenders. To me, this use case also signals bad design and I would attempt to divide the parent component into smaller components but what do I know, maybe this is not always possible.
You have a function in the body of the functional component which is used in another hook (listed as a dependency) which is triggered every time due to the function changing identity whenever the component rerenders. Typically, you can omit such a function from the dependency array by ignoring the lint rule even if this is not by the book. Other suggestions are to place such a function outside the body of the component or inside the hook that uses it, but there might be scenarios where none of this works out as intended.
Good to know connected to this is ...
A function living outside a functional component will always have referential equality between renders.
The setters returned by useState will always have referential equality between renders.
I said in the comments that you can use useCallback when there is function doing expensive calculations in a component that rerenders often but I was a bit off there. Let's say you have a function that does heavy calculations based on some prop that changes less often than a component rerenders. Then you COULD use useCallback and run a function inside it that returns a function with a closure with some computed value
const fn = useCallback(
(
() => {
const a = ... // heavy calculation based on prop c
const b = ... // heavy calculation based on prop c
return () => { console.log(a + b) }
}
)()
, [c])
...
/* fn is used for something, either as a prop OR for something else */
This would effectively avoid calculating a and b every time the component rerenders without c changing, but the more straightforward way to do this would be to instead
const a = useMemo(() => /* calculate and return a */, [c])
const b = useMemo(() => /* calculate and return b */, [c])
const fn = () => console.log(a + b)
so here the use of useCallback just complicates things in a bad way.
Conclusion
It's good to understand more complicated concepts in programming and to be able to use them, but part of the virtue is also to know when to use them. Adding code, and especially code that involves complicated concepts, comes at the price of reduced readability and code that is harder to debug with a lot of different mechanisms that interplay. Therefore, make sure you understand the hooks, but always try to not use them if you can. Especially useCallback, useMemo and React.memo (not a hook but a similar optimization) should, in my opinion, only be introduced when they are absolutely needed. useRef has its very own use cases but should also not be introduced when you can solve your problem without it.
Good work on the sandbox! Always makes it easier to reason about code. I took the liberty of forking your sandbox and refactoring it a bit: sandbox link. You can study the changes yourself if you want. Here is a summary:
It's good that you know and use useRef and useCallback but I was able to remove all the uses, making the code much easier to understand (not only by removing these uses but by also removing the contexts where they are used).
Try to work with React to simplify things. I know this is not a hands-on suggestion but the more you get into React, the more you will realize that you can do things co-operating with React, or you can do things your own way. Both will work but the latter will result in more headache for you and everybody else.
Try to isolate the scope of a component; only delegate data that is necessary to child components and constantly question where you keep your state. Earlier you had click handlers in all three components and the flow was so complicated I didn't even bother to fully understand it. In my version, there is just one click handler in ColorPicker that is being delegated down. The buttons don't have to know what happens when you click them as long as the click handler takes care of that. Closures and the ability to pass functions as arguments are strong advantages of React and Javascript.
Keys are important in React and it's good to see that you use them. Typically, the key should correspond to something that uniquely identifies a specific item. Good to use here would be ${r}_${g}_${b} but then we would only be able to have one sample of each color in the button array. This is a natural limitation but if we don't want it, the only way to assign keys is to assign a unique identifier, which you did. I prefer using Date.now() but some would probably advise against it for some reason. You could use a global variable outside the functional component too if you don't want to use a ref.
Try to do things the functional (immutable) way, and not the "old" Javascript way. For example, when adding to an array, use [...oldArray, newValue] and when assigning to an object, use {...oldObject, newKey: newValue }.
There are more things to say but I think it's better for you to study the refactored version and you can let me know if you wonder about anything.

Props updates in setInterval not reflected when changing component [duplicate]

Why in the following pseudo-code example Child doesn't re-render when Container changes foo.bar?
Container {
handleEvent() {
this.props.foo.bar = 123
},
render() {
return <Child bar={this.props.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
Even if I call forceUpdate() after modifying the value in Container, Child still shows the old value.
Update the child to have the attribute 'key' equal to the name. The component will re-render every time the key changes.
Child {
render() {
return <div key={this.props.bar}>{this.props.bar}</div>
}
}
Because children do not rerender if the props of the parent change, but if its STATE changes :)
What you are showing is this:
https://facebook.github.io/react/tips/communicate-between-components.html
It will pass data from parent to child through props but there is no rerender logic there.
You need to set some state to the parent then rerender the child on parent change state.
This could help.
https://facebook.github.io/react/tips/expose-component-functions.html
I had the same problem.
This is my solution, I'm not sure that is the good practice, tell me if not:
state = {
value: this.props.value
};
componentDidUpdate(prevProps) {
if(prevProps.value !== this.props.value) {
this.setState({value: this.props.value});
}
}
UPD: Now you can do the same thing using React Hooks:
(only if component is a function)
const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);
When create React components from functions and useState.
const [drawerState, setDrawerState] = useState(false);
const toggleDrawer = () => {
// attempting to trigger re-render
setDrawerState(!drawerState);
};
This does not work
<Drawer
drawerState={drawerState}
toggleDrawer={toggleDrawer}
/>
This does work (adding key)
<Drawer
drawerState={drawerState}
key={drawerState}
toggleDrawer={toggleDrawer}
/>
Confirmed, adding a Key works. I went through the docs to try and understand why.
React wants to be efficient when creating child components. It won't render a new component if it's the same as another child, which makes the page load faster.
Adding a Key forces React to render a new component, thus resetting State for that new component.
https://reactjs.org/docs/reconciliation.html#recursing-on-children
Obey immutability
Quite an old question but it's an evergreen problem and it doesn't get better if there are only wrong answers and workarounds.
The reason why the child object is not updating is not a missing key or a missing state, the reason is that you don't obey the principle of immutability.
It is the aim of react to make apps faster and more responsive and easier to maintain and so on but you have to follow some principles. React nodes are only rerendered if it is necessary, i.e. if they have updated. How does react know if a component has updated? Because it state has changed. Now don't mix this up with the setState hook. State is a concept and every component has its state. State is the look or behaviour of the component at a given point in time. If you have a static component you only have one state all the time and don't have to take care of it. If the component has to change somehow its state is changing.
Now react is very descriptive. The state of a component can be derived from some changing information and this information can be stored outside of the component or inside. If the information is stored inside than this is some information the component has to keep track itself and we normally use the hooks like setState to manage this information. If this information is stored outside of our component then it is stored inside of a different component and that one has to keep track of it, its theirs state. Now the other component can pass us their state thru the props.
That means react rerenders if our own managed state changes or if the information coming in via props changes. That is the natural behaviour and you don't have to transfer props data into your own state.
Now comes the very important point: how does react know when information has changed? Easy: is makes an comparison! Whenever you set some state or give react some information it has to consider, it compares the newly given information with the actually stored information and if they are not the same, react will rerender all dependencies of that information.
Not the same in that aspect means a javascript === operator.
Maybe you got the point already.
Let's look at this:
let a = 42;
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> false
We are creating an instance of a value, then create another instance, assign the value of the first instance to the second instance and then change the first instance.
Now let's look at the same flow with objects:
let a = { num: 42};
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a.num += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> true
The difference this time is that an object actually is a pointer to a memory area and with the assertion of b=a you set b to the same pointer as a leading to exactly the same object.
Whatever you do in a can be accesed by your a pointer or your b pointer.
Your line:
this.props.foo.bar = 123
actually updates a value in the memory where "this" is pointing at.
React simply can't recognize such alterations by comparing the object references. You can change the contents of your object a thousand times and the reference will always stay the same and react won't do a rerender of the dependent components.
That is why you have to consider all variables in react as immutable. To make a detectable change you need a different reference and you only get that with a new object. So instead of changing your object you have to copy it to a new one and then you can change some values in it before you hand it over to react.
Look:
let a = {num: 42};
console.log('a looks like', a);
let b = {...a};
console.log('b looks like', b);
console.log('is a the same as b?', a === b); // --> false
The spread operator (the one with the three dots) or the map-function are common ways to copy data to a new object or array.
If you obey immutability all child nodes update with new props data.
According to React philosophy component can't change its props. they should be received from the parent and should be immutable. Only parent can change the props of its children.
nice explanation on state vs props
also, read this thread Why can't I update props in react.js?
You should use setState function. If not, state won't save your change, no matter how you use forceUpdate.
Container {
handleEvent= () => { // use arrow function
//this.props.foo.bar = 123
//You should use setState to set value like this:
this.setState({foo: {bar: 123}});
};
render() {
return <Child bar={this.state.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
}
Your code seems not valid. I can not test this code.
You must have used dynamic component.
In this code snippet we are rendering child component multiple time and also passing key.
If we render a component dynamically multiple time then React doesn't render that component until it's key gets changed.
If we change checked by using setState method. It won't be reflected in Child component until we change its key. We have to save that on child's state and then change it to render child accordingly.
class Parent extends Component {
state = {
checked: true
}
render() {
return (
<div className="parent">
{
[1, 2, 3].map(
n => <div key={n}>
<Child isChecked={this.state.checked} />
</div>
)
}
</div>
);
}
}
My case involved having multiple properties on the props object, and the need to re-render the Child on changing any of them.
The solutions offered above were working, yet adding a key to each an every one of them became tedious and dirty (imagine having 15...). If anyone is facing this - you might find it useful to stringify the props object:
<Child
key={JSON.stringify(props)}
/>
This way every change on each one of the properties on props triggers a re-render of the Child component.
Hope that helped someone.
I have the same issue's re-rendering object props, if the props is an object JSON.stringify(obj) and set it as a key for Functional Components. Setting just an id on key for react hooks doesn't work for me. It's weird that to update the component's you have to include all the object properties on the key and concatenate it there.
function Child(props) {
const [thing, setThing] = useState(props.something)
return (
<>
<div>{thing.a}</div>
<div>{thing.b}</div>
</>
)
}
...
function Caller() {
const thing = [{a: 1, b: 2}, {a: 3, b: 4}]
thing.map(t => (
<Child key={JSON.stringify(t)} something={thing} />
))
}
Now anytime the thing object changes it's values on runtime, Child component will re-render it correctly.
You should probably make the Child as functional component if it does not maintain any state and simply renders the props and then call it from the parent. Alternative to this is that you can use hooks with the functional component (useState) which will cause stateless component to re-render.
Also you should not alter the propas as they are immutable. Maintain state of the component.
Child = ({bar}) => (bar);
export default function DataTable({ col, row }) {
const [datatable, setDatatable] = React.useState({});
useEffect(() => {
setDatatable({
columns: col,
rows: row,
});
/// do any thing else
}, [row]);
return (
<MDBDataTableV5
hover
entriesOptions={[5, 20, 25]}
entries={5}
pagesAmount={4}
data={datatable}
/>
);
}
this example use useEffect to change state when props change.
I was encountering the same problem.
I had a Tooltip component that was receiving showTooltip prop, that I was updating on Parent component based on an if condition, it was getting updated in Parent component but Tooltip component was not rendering.
const Parent = () => {
let showTooltip = false;
if(....){ showTooltip = true; }
return(
<Tooltip showTooltip={showTooltip}></Tooltip>
)
}
The mistake I was doing is to declare showTooltip as a let.
I realized what I was doing wrong I was violating the principles of how rendering works, Replacing it with hooks did the job.
const [showTooltip, setShowTooltip] = React.useState<boolean>(false);
define changed props in mapStateToProps of connect method in child component.
function mapStateToProps(state) {
return {
chanelList: state.messaging.chanelList,
};
}
export default connect(mapStateToProps)(ChannelItem);
In my case, channelList's channel is updated so I added chanelList in mapStateToProps
In my case I was updating a loading state that was passed down to a component. Within the Button the props.loading was coming through as expected (switching from false to true) but the ternary that showed a spinner wasn't updating.
I tried adding a key, adding a state that updated with useEffect() etc but none of the other answers worked.
What worked for me was changing this:
setLoading(true);
handleOtherCPUHeavyCode();
To this:
setLoading(true);
setTimeout(() => { handleOtherCPUHeavyCode() }, 1)
I assume it's because the process in handleOtherCPUHeavyCode is pretty heavy and intensive so the app freezes for a second or so. Adding the 1ms timeout allows the loading boolean to update and then the heavy code function can do it's work.
You can use componentWillReceiveProps:
componentWillReceiveProps({bar}) {
this.setState({...this.state, bar})
}
Credit to Josh Lunsford
Considering the rendering limitations with props and the gains we have with states, if you use reaction hooks, there are a few tricks you can use. For example, you can convert props to state manually using useEffect. It probably shouldn't be the best practice, but it helps in theses cases.
import { isEqual } from 'lodash';
import { useEffect, useState } from 'react';
export const MyComponent = (props: { users: [] }) => {
const [usersState, setUsersState] = useState([]);
useEffect(() => {
if (!isEqual(props.users, usersState)) {
setUsersState(props.users);
}
}, [props.users]);
<OtherComponent users={usersState} />;
};

React: why child component doesn't update when prop changes

Why in the following pseudo-code example Child doesn't re-render when Container changes foo.bar?
Container {
handleEvent() {
this.props.foo.bar = 123
},
render() {
return <Child bar={this.props.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
Even if I call forceUpdate() after modifying the value in Container, Child still shows the old value.
Update the child to have the attribute 'key' equal to the name. The component will re-render every time the key changes.
Child {
render() {
return <div key={this.props.bar}>{this.props.bar}</div>
}
}
Because children do not rerender if the props of the parent change, but if its STATE changes :)
What you are showing is this:
https://facebook.github.io/react/tips/communicate-between-components.html
It will pass data from parent to child through props but there is no rerender logic there.
You need to set some state to the parent then rerender the child on parent change state.
This could help.
https://facebook.github.io/react/tips/expose-component-functions.html
I had the same problem.
This is my solution, I'm not sure that is the good practice, tell me if not:
state = {
value: this.props.value
};
componentDidUpdate(prevProps) {
if(prevProps.value !== this.props.value) {
this.setState({value: this.props.value});
}
}
UPD: Now you can do the same thing using React Hooks:
(only if component is a function)
const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);
When create React components from functions and useState.
const [drawerState, setDrawerState] = useState(false);
const toggleDrawer = () => {
// attempting to trigger re-render
setDrawerState(!drawerState);
};
This does not work
<Drawer
drawerState={drawerState}
toggleDrawer={toggleDrawer}
/>
This does work (adding key)
<Drawer
drawerState={drawerState}
key={drawerState}
toggleDrawer={toggleDrawer}
/>
Obey immutability
Quite an old question but it's an evergreen problem and it doesn't get better if there are only wrong answers and workarounds.
The reason why the child object is not updating is not a missing key or a missing state, the reason is that you don't obey the principle of immutability.
It is the aim of react to make apps faster and more responsive and easier to maintain and so on but you have to follow some principles. React nodes are only rerendered if it is necessary, i.e. if they have updated. How does react know if a component has updated? Because it state has changed. Now don't mix this up with the setState hook. State is a concept and every component has its state. State is the look or behaviour of the component at a given point in time. If you have a static component you only have one state all the time and don't have to take care of it. If the component has to change somehow its state is changing.
Now react is very descriptive. The state of a component can be derived from some changing information and this information can be stored outside of the component or inside. If the information is stored inside than this is some information the component has to keep track itself and we normally use the hooks like setState to manage this information. If this information is stored outside of our component then it is stored inside of a different component and that one has to keep track of it, its theirs state. Now the other component can pass us their state thru the props.
That means react rerenders if our own managed state changes or if the information coming in via props changes. That is the natural behaviour and you don't have to transfer props data into your own state.
Now comes the very important point: how does react know when information has changed? Easy: is makes an comparison! Whenever you set some state or give react some information it has to consider, it compares the newly given information with the actually stored information and if they are not the same, react will rerender all dependencies of that information.
Not the same in that aspect means a javascript === operator.
Maybe you got the point already.
Let's look at this:
let a = 42;
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> false
We are creating an instance of a value, then create another instance, assign the value of the first instance to the second instance and then change the first instance.
Now let's look at the same flow with objects:
let a = { num: 42};
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a.num += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> true
The difference this time is that an object actually is a pointer to a memory area and with the assertion of b=a you set b to the same pointer as a leading to exactly the same object.
Whatever you do in a can be accesed by your a pointer or your b pointer.
Your line:
this.props.foo.bar = 123
actually updates a value in the memory where "this" is pointing at.
React simply can't recognize such alterations by comparing the object references. You can change the contents of your object a thousand times and the reference will always stay the same and react won't do a rerender of the dependent components.
That is why you have to consider all variables in react as immutable. To make a detectable change you need a different reference and you only get that with a new object. So instead of changing your object you have to copy it to a new one and then you can change some values in it before you hand it over to react.
Look:
let a = {num: 42};
console.log('a looks like', a);
let b = {...a};
console.log('b looks like', b);
console.log('is a the same as b?', a === b); // --> false
The spread operator (the one with the three dots) or the map-function are common ways to copy data to a new object or array.
If you obey immutability all child nodes update with new props data.
Confirmed, adding a Key works. I went through the docs to try and understand why.
React wants to be efficient when creating child components. It won't render a new component if it's the same as another child, which makes the page load faster.
Adding a Key forces React to render a new component, thus resetting State for that new component.
https://reactjs.org/docs/reconciliation.html#recursing-on-children
According to React philosophy component can't change its props. they should be received from the parent and should be immutable. Only parent can change the props of its children.
nice explanation on state vs props
also, read this thread Why can't I update props in react.js?
You should use setState function. If not, state won't save your change, no matter how you use forceUpdate.
Container {
handleEvent= () => { // use arrow function
//this.props.foo.bar = 123
//You should use setState to set value like this:
this.setState({foo: {bar: 123}});
};
render() {
return <Child bar={this.state.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
}
Your code seems not valid. I can not test this code.
You must have used dynamic component.
In this code snippet we are rendering child component multiple time and also passing key.
If we render a component dynamically multiple time then React doesn't render that component until it's key gets changed.
If we change checked by using setState method. It won't be reflected in Child component until we change its key. We have to save that on child's state and then change it to render child accordingly.
class Parent extends Component {
state = {
checked: true
}
render() {
return (
<div className="parent">
{
[1, 2, 3].map(
n => <div key={n}>
<Child isChecked={this.state.checked} />
</div>
)
}
</div>
);
}
}
My case involved having multiple properties on the props object, and the need to re-render the Child on changing any of them.
The solutions offered above were working, yet adding a key to each an every one of them became tedious and dirty (imagine having 15...). If anyone is facing this - you might find it useful to stringify the props object:
<Child
key={JSON.stringify(props)}
/>
This way every change on each one of the properties on props triggers a re-render of the Child component.
Hope that helped someone.
I have the same issue's re-rendering object props, if the props is an object JSON.stringify(obj) and set it as a key for Functional Components. Setting just an id on key for react hooks doesn't work for me. It's weird that to update the component's you have to include all the object properties on the key and concatenate it there.
function Child(props) {
const [thing, setThing] = useState(props.something)
return (
<>
<div>{thing.a}</div>
<div>{thing.b}</div>
</>
)
}
...
function Caller() {
const thing = [{a: 1, b: 2}, {a: 3, b: 4}]
thing.map(t => (
<Child key={JSON.stringify(t)} something={thing} />
))
}
Now anytime the thing object changes it's values on runtime, Child component will re-render it correctly.
You should probably make the Child as functional component if it does not maintain any state and simply renders the props and then call it from the parent. Alternative to this is that you can use hooks with the functional component (useState) which will cause stateless component to re-render.
Also you should not alter the propas as they are immutable. Maintain state of the component.
Child = ({bar}) => (bar);
export default function DataTable({ col, row }) {
const [datatable, setDatatable] = React.useState({});
useEffect(() => {
setDatatable({
columns: col,
rows: row,
});
/// do any thing else
}, [row]);
return (
<MDBDataTableV5
hover
entriesOptions={[5, 20, 25]}
entries={5}
pagesAmount={4}
data={datatable}
/>
);
}
this example use useEffect to change state when props change.
I was encountering the same problem.
I had a Tooltip component that was receiving showTooltip prop, that I was updating on Parent component based on an if condition, it was getting updated in Parent component but Tooltip component was not rendering.
const Parent = () => {
let showTooltip = false;
if(....){ showTooltip = true; }
return(
<Tooltip showTooltip={showTooltip}></Tooltip>
)
}
The mistake I was doing is to declare showTooltip as a let.
I realized what I was doing wrong I was violating the principles of how rendering works, Replacing it with hooks did the job.
const [showTooltip, setShowTooltip] = React.useState<boolean>(false);
define changed props in mapStateToProps of connect method in child component.
function mapStateToProps(state) {
return {
chanelList: state.messaging.chanelList,
};
}
export default connect(mapStateToProps)(ChannelItem);
In my case, channelList's channel is updated so I added chanelList in mapStateToProps
In my case I was updating a loading state that was passed down to a component. Within the Button the props.loading was coming through as expected (switching from false to true) but the ternary that showed a spinner wasn't updating.
I tried adding a key, adding a state that updated with useEffect() etc but none of the other answers worked.
What worked for me was changing this:
setLoading(true);
handleOtherCPUHeavyCode();
To this:
setLoading(true);
setTimeout(() => { handleOtherCPUHeavyCode() }, 1)
I assume it's because the process in handleOtherCPUHeavyCode is pretty heavy and intensive so the app freezes for a second or so. Adding the 1ms timeout allows the loading boolean to update and then the heavy code function can do it's work.
You can use componentWillReceiveProps:
componentWillReceiveProps({bar}) {
this.setState({...this.state, bar})
}
Credit to Josh Lunsford
Considering the rendering limitations with props and the gains we have with states, if you use reaction hooks, there are a few tricks you can use. For example, you can convert props to state manually using useEffect. It probably shouldn't be the best practice, but it helps in theses cases.
import { isEqual } from 'lodash';
import { useEffect, useState } from 'react';
export const MyComponent = (props: { users: [] }) => {
const [usersState, setUsersState] = useState([]);
useEffect(() => {
if (!isEqual(props.users, usersState)) {
setUsersState(props.users);
}
}, [props.users]);
<OtherComponent users={usersState} />;
};

Storing an object in state of a React component?

Is it possible to store an object in the state of a React component? If yes, then how can we change the value of a key in that object using setState? I think it's not syntactically allowed to write something like:
this.setState({ abc.xyz: 'new value' });
On similar lines, I've another question: Is it okay to have a set of variables in a React component such that they can be used in any method of the component, instead of storing them in a state?
You may create a simple object that holds all these variables and place it at the component level, just like how you would declare any methods on the component.
Its very likely to come across situations where you include a lot of business logic into your code and that requires using many variables whose values are changed by several methods, and you then change the state of the component based on these values.
So, instead of keeping all those variables in the state, you only keep those variables whose values should be directly reflected in the UI.
If this approach is better than the first question I wrote here, then I don't need to store an object in the state.
this.setState({ abc.xyz: 'new value' }); syntax is not allowed.
You have to pass the whole object.
this.setState({abc: {xyz: 'new value'}});
If you have other variables in abc
var abc = this.state.abc;
abc.xyz = 'new value';
this.setState({abc: abc});
You can have ordinary variables, if they don't rely on this.props and this.state.
You can use ES6 spread on previous values in the object to avoid overwrite
this.setState({
abc: {
...this.state.abc,
xyz: 'new value'
}
});
In addition to kiran's post, there's the update helper (formerly a react addon). This can be installed with npm using npm install immutability-helper
import update from 'immutability-helper';
var abc = update(this.state.abc, {
xyz: {$set: 'foo'}
});
this.setState({abc: abc});
This creates a new object with the updated value, and other properties stay the same. This is more useful when you need to do things like push onto an array, and set some other value at the same time. Some people use it everywhere because it provides immutability.
If you do this, you can have the following to make up for the performance of
shouldComponentUpdate: function(nextProps, nextState){
return this.state.abc !== nextState.abc;
// and compare any props that might cause an update
}
UPDATE
This answer is many years old and is now obsolete.
You should probably use hooks and/or refactor your code if you're trying to do a deep update.
You should probably be using Functional Components with useState hooks or a reducer.
I'm keeping this answer alive for historical context.
Beyond here be DRAGONS!
this.setState({abc: {xyz: 'new value'}}); will NOT work, as state.abc will be entirely overwritten, not merged.-
This works for me:
this.setState((previousState) => {
previousState.abc.xyz = 'blurg';
return previousState;
});
Unless I'm reading the docs wrong, Facebook recommends the above format.
https://facebook.github.io/react/docs/component-api.html
Additionally, I guess the most direct way without mutating state is to directly copy by using the ES6 spread/rest operator:
const newState = { ...this.state.abc }; // deconstruct state.abc into a new object-- effectively making a copy
newState.xyz = 'blurg';
this.setState(newState);
Easier way to do it in one line of code
this.setState({ object: { ...this.state.object, objectVarToChange: newData } })
Even though it can be done via immutability-helper or similar I do not wan't to add external dependencies to my code unless I really have to. When I need to do it I use Object.assign. Code:
this.setState({ abc : Object.assign({}, this.state.abc , {xyz: 'new value'})})
Can be used on HTML Event Attributes as well, example:
onChange={e => this.setState({ abc : Object.assign({}, this.state.abc, {xyz : 'new value'})})}
If you want to store an object in the state using functional components you can try the following.
import React from 'react';
import {useState, useEffect} from 'react';
const ObjectState= () => {
const [username, setUsername] = useState({});
const usernameSet = () => {
const name = {
firstname: 'Naruto',
familyname: 'Uzmaki'
}
setUsername(prevState => name);
}
return(
<React.Fragment>
<button onClick= {usernameSet}>
Store Object
</button>
{username.firstname} {username.familyname}
</React.Fragment>
)
}
export default ObjectState;
If you want to add an object to a pre-existing object.
import React from 'react';
import {useState, useEffect} from 'react';
const ObjectState= () => {
const [username, setUsername] = useState({village: 'Konoha'});
const usernameSet = () => {
setUsername((prevState) => {
const data = {
...prevState,
firstname: 'Naruto',
familyname: 'Uzmaki'
}
return data
});
}
return(
<React.Fragment>
<button onClick= {usernameSet}>
Store Object
</button>
{username.village} {username.firstname} {username.familyname}
</React.Fragment>
)
}
export default ObjectState;
P.S. : Naming the component 'Object' leads to an 'Maximum call stack size
exceeded error'. Other names are fine but for some reason 'Object' is
not.
Like the following is not ok.
const Object = () => {
// The above code
};
export default Object;
If anyone knows why or how to prevent it please add it to the comments.

Categories

Resources