Storing an object in state of a React component? - javascript

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.

Related

React fast global redux-like variable

I'm making a front-end application using react/webgl and I need to be vary of performance improvements since almost everything must be rendered real-time and dynamically.
I need to render something on a canvas and need to use some variable's globally across many different components, but they need to be updated fast. Technically redux is what I need, however accessing dispatched variables takes time and causes crucial performance issues.
So instead I opted in to use useRef() which solves the “slow” issue but now I cannot update it’s value across different components. Using useRef() solves my issue but since it's not globally accessible it causes problems on other parts of the application.
Declaration of the variable looks like this:
import { useRef } from 'react';
const WebGLStarter = (props) => {
...
const myValue = useRef();
myValue.current = someCalculation();
function render(myValue.current){
...
requestAnimationFrame(function () {
render(myValue.current);
});
}
...
}
Currently someCalculation() is on the same component as it's declaration. I want to use someCalculation() on a different file but I can't do it beacuse useRef() won't allow me to. And again, I can't use redux because it's slow.
TL;DR : I need something similar to redux but it needs to be fast enough to not cause performance issues on an infinite loop.
Create a context with the ref. Wrap your root with the provider, and use the hook to get access to the ref when you need to use/update it:
import { createContext, useRef, useContext } from 'react';
const defaultValue = /** default value **/;
const MyValueContext = createContext(defaultValue);
const MyValueContextProvider = ({ children }) => {
const myValueRef = useRef(defaultValue);
return (
<MyValueContext.Provider value={myValueRef}>
{children}
</MyValueContext.Provider>
);
};
const useMyValue = () => useContext(MyValueContext);
To use in components call the useMyValue hook. This would give you direct access to the ref, and since you don't update any state (just change the ref's current property) it won't cause re-renders:
const WebGLStarter = (props) => {
const myValue = useMyValue();
myValue.current = someCalculation();
...
};

React - useState not updating unless class object is duplicated

I am trying to update the state using the useState hook for React. However, I think I am using it incorrectly or at least not how it's intended.
I have a class which is initialised.
const [form, setForm] = useState(new Form());
I am then adding elements to this form:
const element = ...
setForm(form => ({
...form,
elements: [
...form.elements,
element
]
});
However, this doesn't seem to update the UI to reflect this change.
I have also tried the following:
setForm(form => {
form.elements.push(element);
return form;
});
The only way I can get it to work is doing the following and as you can imagine it becomes quite a mountain to climb as it's a bit all over the place:
const duplicated = JSON.parse(JSON.stringify(form));
duplicated.elements.push(element);
setForm(new Form(duplicated));
Is there a better approach to do this rather than having to duplicate the object everytime.

Would this be considered "breaking the rules of hooks"?

I have a compound component that is used to build a bunch of different (but similar) screens in a react native app. Data comes from a CMS, and depending on the value of a prop called type it needs to have different state variables, different validation, etc. I've written a config object who's methods map to the values of type, and contain the state and functions needed for each use case. The pattern looks like this (edited for example sake):
import { useState } from 'react';
const MyComponent = props => {
const { type } = props; // possible values of type are 'A', 'B', 'C'
const config = {
A() {
const [value, setValue] = useState('');
function onChange({ target }) {
setValue(target.value);
}
return {
value,
onChange
}
},
B() {
// ...
return {
value: '',
onChange: () => {}
}
},
C() {
// ...
return {
value: '',
onChange: () => {}
}
}
}[type]();
return <input value={config.value} onChange={config.onChange} />
};
export default MyComponent;
BUT!
In the react docs it says this:
Don’t call Hooks inside loops, conditions, or nested functions.
My question is - does the above example violate the rules of hooks? It seems to work for what I need it to, but I suspect that it may cause problems. Would appreciate any thoughts / discussion. Cheers!
Short answer: yes.
Long answer:
React depends on hooks to be called in the exact same order between renders (I believe internally the hooks are stored in a linked list, which is altered on mounting and unmounting of the component, if the order changes between renders without a mount and unmount, React has no way to know which hook belongs to which component).
Because your rendering from a CMS, the value of type is unlikely to change between renders, so you probably will never get in to trouble with this particular use case, but it is a bad habit to get in to, and there is almost certainly a better way to handle what you are trying to do. Maybe write a hook that takes type as an argument?
function useMyHook(type:string) {
const [state, setState] = useState('');
const onChange = useMemo(() => {
if (type = 'A')
return () => doXyz()
}, [type])
return {state, onChange}
}
This is contrived, but hopefully will get you thinking of alternate approaches to your problem.

Organizing and setting state from another file

I have some data (objects) that live in their own file (Origin.js).
This data is being exported using the spread operator within another object named OriginState:
Origin.js
//info
const info = {
title: '',
year: '',
};
//images
const images = {
logo: '',
heroImage: '',
};
//text
const text = {
header: '',
body: '',
};
export const OriginState = {
...info,
...images,
...text,
};
I am importing my OriginState object in another file and using it as state for my app like this:
OtherFile.js
import { OriginState } from './Origin.js';
const [state, setState] = useState({
...OriginState,
});
Here is an example handler where I am using this state to update some specified state values in an input later:
const handleChange = (e) => {
const { name, value } = e.target;
setState((state) => ({
...state,
[name]: value,
}));
};
Now, my question is... Is it incorrect to store state like this?
Additionally, am I using setState incorrectly in my handler function?
In most cases I've seen state declared and updated like this which is obviously easier to read:
const [count, setCount] = useState(0);
setCount(count + 1)
But I have a lot of state and didn't think it would be a good idea to have multiple setState hooks.
Is there a better way to do this? What I currently have just feels wrong.
Is it incorrect to store state like this?
const handleChange = (e) => {
const { name, value } = e.target;
setState((state) => ({
...state,
[name]: value,
}));
};
Nope, not at all, in fact, it is often the preferable pattern for state updates.
Any time your state update depends on the previous state, i.e. the classic counter example, or in your case, when there is nested state, you should use a functional state update to update from the previous state instead of the state from the previous render cycle.
Additionally, am I using setState incorrectly in my handler function?
In most cases I've seen state declared and updated like this which is
obviously easier to read:
const [count, setCount] = useState(0);
setCount(count + 1)
I see no issue with your state update logic in the handler. In this count example it would (should) be considered incorrect to update a count like this. See this codesandbox demo that attempts to show the issue between non-functional and functional state updates.
The correct state update should be setCount(count => count + 1)
But I have a lot of state and didn't think it would be a good idea to
have multiple setState hooks.
Is there a better way to do this? What I currently have just feels
wrong.
When it comes to form inputs and state I think it makes sense to have a single flat object. There isn't really a right or wrong answer in general though when it comes to using a single useState hook with "complex" state shape, or to use a single useState hook for each "chunk" of state. It's an opinionated answer, mostly do what makes sense for a specific use-case.
Generally though I'd say if a set of values are even loosely related then perhaps it makes sense to store them in a common object, but this is my opinion.
A potential issue I see with your imported data though is the chance that you may inadvertently overwrite some key-value pairs by the use of the Spread syntax.
export const OriginState = {
...info,
...images, // <-- could overwrite values from info
...text, // <-- could overwrite values from info and images
};
Your approach seems quite legit, and this is one of the best practices that if your newState is depend on the oldState use setState callback and get the old state from callback input because otherwise as you showed above if you use it like this:
const [count, setCount] = useState(0);
setCount(count + 1)
you may increase the chance to get stale data which will increase the potential for bug

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} />;
};

Categories

Resources