Reselect: createSelector not working correctly - javascript

I have a problem with my memoized selectors.
Reading the docs on https://redux.js.org/usage/deriving-data-selectors
I taken this snippets:
const state = {
a: {
first: 5
},
b: 10
}
const selectA = state => state.a
const selectB = state => state.b
const selectA1 = createSelector([selectA], a => a.first)
const selectResult = createSelector([selectA1, selectB], (a1, b) => {
console.log('Output selector running')
return a1 + b
})
const result = selectResult(state)
// Log: "Output selector running"
console.log(result)
// 15
const secondResult = selectResult(state)
// No log output
console.log(secondResult)
// 15
My problem is that the secondResult function, log the result.
All this is a little premise.
My very problem:
I'am using react with #reduxjs/toolkit
I have a list of todo
I created a "todoAdapter" to manage a list as entities
I want to use memoized selected to update a single "todo" without re-render the entire list optimizing my app.
but...
When I dispatch an update with "adapter.updateOne", the standard selector "SelectAll" every time (i think) changes ref.
Example
I have this selectors from "slice"
export const {
selectAll,
selectIds: selectTodosIDs,
selectTotal: selectTodosCount,
selectById: selectTodoById
} = todosSelector;
If I create this selector
export const selectIdsCustom = createSelector(
selectTodosIDs,
(ids) => {
console.log('execute output function');
return ....
}
)
It' all ok (state.todos.ids not change obviously).
If I create this selector:
export const selectTodosCustom = createSelector(
selectAll,
(todos) => {
console.log('execute output function');
return ....
}
)
selectTodosCustom run "always".
Whyyy???
With updateOne I am modifing "only" an entity inside "state.todos.entities"
Where am I wrong ??
What I did not understand?
My app is just un case study. The complete app is on:
https://codesandbox.io/s/practical-hermann-60i7i
I only created the same app in typescript and, when I as my app had this problem:
I download the original app form link above (official redux example)
npm install
npm start
But I have the some problem also in the official example!!!!!
Problem is my env? some library version?

When I dispatch an update with "adapter.updateOne", the standard
selector "SelectAll" every time (i think) changes ref.
Yes, you are right. It is correct.
selectAll from #reduxjs/toolkit depends on ids and entities from the entities state.
{
ids: ['1', '2'],
entities: {
1: {
id: '1',
title: 'First',
},
2: {
id: '2',
title: 'Second',
},
},
}
Every time you dispatch an update with adapter.updateOne, the reference to the entities object changes. This is normal, this is how immerjs (used under the hood of reduxtoolkit) provides correct immutability:
dispatch(updateOne({ id: '1', changes: { title: 'First (altered)' } }));
const state = {
ids: ['1', '2'],
entities: { // <--- new object
1: { // <--- new object
id: '1',
title: 'First (altered)',
},
2: { // <--- old object
id: '2',
title: 'Second',
},
},
};
If the entities object remained old, the selector selectAll would return a memoized value with an incorrect title for the first element.
To optimize the re-render of the list (which in actually useful only for large lists), you should use selector selectIds in the parent component and selector selectById in the child components.
const Child = ({ id }) => {
const dispatch = useDispatch();
const book = useSelector((state) => selectById(state, id));
const handleChange = useCallback(() => {
// the parent component will not be re-render
dispatch(
bookUpdate({
id,
changes: {
title: `${book.title} (altered)`,
},
})
);
}, [dispatch, id, book]);
return (
<div>
<h2>{book.title}</h2>
<button onClick={handleChange}>change title</button>
</div>
);
};
function Parent() {
const ids = useSelector(selectIds);
return (
<div>
{ids.map((id) => (
<Child key={id} id={id}></Child>
))}
</div>
);
}
UPDATE
In this case, item not change ref and in "entities" I am changing a
single prop inside it.
It is not right???
No, you can't do that when using redux. Redux is based on the idea of immutability. Any state change creates a new state object.
Updating an entity is just a deep change of the state object. And all objects on the path to the updated element must be new.
If you do not follow this rule, then base tools will not work correctly. All of them use strict comparison by default.
But you can still avoid re-render the component even when the selector returns the equal arrays with different references. Just pass your own comparison function:
...
const todoIds = useSelector(
selectFilteredTodoIds,
// the component will be re-render only if this function returns false
(next, prev) => next.every((id, index) => id === prev[index])
);
...

Related

Using react components to render data controlled by third party library

I'm building a React component that needs to consume data from a tree library built in vanilla JS. This library holds and manages data for all "tree nodes" and they're state - expanded/collapsed, selected, hidden, etc.
I'm unsure how to approach building react components because they ideally control their own state or use a store designed for use in react.
Here's a super simple example of data that might be loaded into the tree library.
[{
id: 1,
text: 'Node 1'
}, {
id: 2
text: 'Node 2',
state: {
selected: true
}
}]
It gets loaded into the tree lib via the constructor new Tree(nodes); and the tree lib provides a ton of methods to work with it: tree.deselect(2) and tree.selected() // -> []
I've toyed around with some basic components to render this example:
I start with <TreeNodes nodes={tree.nodes()} />
const TreeNodes = ({ nodes }: TreeNodesProps) => {
return (<>{ nodes.map(node => <TreeNode key={node.id} node={node} />) }</>);
}
const TreeNode = ({ node }: TreeNodeProps) => {
const onClick = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault();
node.toggleSelect();
}
return <div className={clsx({ selected: node.selected()})} onClick={onClick}>{node.text}</div>
}
The tree library fires events like node.selected to let me know when something has changed in the data.
My question is, what's the best/proper way to then sync my data to react components?
I was debating listening for all tree events and updating a state object in the root component but that feels wrong:
const [nodes, setNodes] = useState(tree.nodes());
this.tree.on('node.selected', () => {
setNodes(tree.nodes())
});
I honestly don't feel adding a listener is wrong as long as it works fine. Also I think you should wrap it up in a useEffect this way:
function MyApp() {
const [nodes, setNodes] = useState(tree.nodes());
useEffect(() => {
tree.on("node.selected", () => {
setNodes(tree.nodes());
});
return () => {/* remove listener here */}
}, []);
}
You either have this solution or you will have to make all changes to the data by yourself.

REACT: array.map() function not rendering components properly when dealing with an array with newly added object at 0 index

Need help with the REACT code below. Making a note taking app and tried to make it so that a new note appears at the top of the list of my notes. My code works fine when I add the new note at the end of the array, but if I switch it so that when I add it in the beginning like so:
const newNotes = [newNote, ...notes];
it then displays all the same old notes with the last one repeated twice. the code responsible for displaying the notes on the screen looks like this.
const listNotes = ()=>{
console.log(notes);
return notes.map((obj, index) => (
<Note
key={index}
id={obj.id}
subject={obj.subject}
text={obj.text}
date={obj.date}
handleDeleteNote={deleteNote}
/>
))
}
After some debugging I notice that the new list is being created successfully and the map is creating the notes components "successfully" and when I print notes to the console right before the map function it seems to show that the the newly created list is fine. but when it gets to the ".map()" for some reason it's not following the list exactly, it's almost like it doesn't realize there's a new item in the list until it's halfway through redering the notes.
full code of component below.
import React, { useState, useEffect } from "react";
import { nanoid } from "nanoid"; //allows for random IDs
import NoteSearch from "./NoteSearch"; // component that searches notes
import Note from "./Note"; // Component for saved notes
import AddNewNote from "./AddNewNote"; //Component to add new note
const NotesList = () => {
// States
const [notes, setNotes] = useState([
{
id: nanoid(),
subject: "Fruit",
text: "bananas are awesome",
date: "9/23/2022",
},
{
id: nanoid(),
subject: "ew",
text: "onions are awesome",
date: "9/24/2022",
},
{
id: nanoid(),
subject: "veggie",
text: "carrots are awesome",
date: "9/25/2022",
},
]);
// Add and Delete Note handlers
const addNote = (subject, text) => {
const date = new Date();
const newNote = {
id: nanoid(),
subject: subject,
text: text,
date: date.toLocaleDateString(),
};
const newNotes = [newNote, ...notes];
setNotes(newNotes);
};
const deleteNote = (CurrentID) => {
const newNotes = notes.filter((note)=>note.id !== CurrentID);
setNotes(newNotes);
};
//functions
const listNotes = ()=>{
console.log(notes);
return notes.map((obj, index) => (
<Note
key={index}
id={obj.id}
subject={obj.subject}
text={obj.text}
date={obj.date}
handleDeleteNote={deleteNote}
/>
))
}
// This is the notes app DOM
return (
<div className="notes-wrapper">
<NoteSearch />
<div className="all-notes">
<AddNewNote handleAddNote={addNote}/>
{
listNotes()
}
</div>
</div>
);
};
Use id as key not index:
<Note
key={obj.id}
id={obj.id}
subject={obj.subject}
text={obj.text}
date={obj.date}
handleDeleteNote={deleteNote}
/>
You are changing your list dynamically and it is discouraged to use index as key and may cause an issue.
React doc.: We don’t recommend using indexes for keys if the order of items may
change.
This is because you are using indexes as keys. React thinks that you don't want to unmount/remount the n first nodes (those that were already existing). It will just update their props. Likely your Node component initializes its state in the constructor or after the component did mount and do not recompute the state on props changes. As a consequence the list is not updated visually, except for the last new node.
Using the id as key should solve the problem.

React Redux - Confusion on Using Functional Component with useDispatch vs Class Component and mapStateToProps

I am trying to have a user be able to click an item from a list of all possible items and have a modal open to display data about that item (including the current quantity they have) and buttons to increment/decrement that amount.
To my understanding since I am just showing data that is being passed in and then dispatching an action to update the store I should be using a functional component to display the data and useDispatch to call the store action.
Currently when I update the store I see the change in Redux debugging tools but the change is not reflected in the modal until I reopen it. While I have been looking for answers to this I see many similar questions but they all use Class Components and mapStateToProps (such as this post). I thought best practices was to use functional components unless needed. Am I wrong to think that if I am getting a value from the store in a functional component it should update on change?
Code Snippets
Dialog
export default function ItemDialog({
...
selectedItem,
}) {
const dispatch = useDispatch()
const inventory = useSelector(
state => state.user.inventory
)
let userItem = inventory.find(
userItem => userItem.name === selectedItem.name
)
const changeItemCount = (item, change) => {
item.change = change
dispatch({
type: "USER_INVENTORY_UPDATED",
payload: item
})
}
const showQuantity = userItem => {
return userItem.quantity > 0 ? `(${userItem.quantity})` : ""
}
...
render(
<p className="text-xl text-center font-semibold">
{selectedItem.name}
</p>
<p className="text-center font-light">
{showQuantity(userItem)}
</p>
...
<AddBoxIcon
onClick={() => changeItemCount(selectedItem, 1)}
/>
)
Store
const userReducer = (state = InitialUserState, action) => {
let inventoryCopy = { ...state.inventory }
switch (action.type) {
case "USER_INVENTORY_UPDATED":
let category = action.payload.category
let updatedItemIndex = inventoryCopy[category].findIndex(
item => item.name === action.payload.name.toUpperCase()
)
// If item is already there
if (updatedItemIndex >= 0) {
inventoryCopy[category][updatedItemIndex].quantity +=
action.payload.change
} else {
// If item needs to be added to inventory category
let newItem = {
name: action.payload.name,
quantity: action.payload.change
}
inventoryCopy[category].push(newItem)
}
return {
...state,
inventory: inventoryCopy
}
...
default:
return state
}
}
Check your spread operator when you return your updated state. You may need to deep clone the old state depending on how many nested objects it has.
The docs have more information on shallow cloning objects.
Deeply cloning your state object will help you get rid of:
let inventoryCopy = { ...state.inventory }

What is a robust way of rendering a dynamic quantity of React child components, using the Redux container component pattern?

Say I have a functional React presentation component, like so:
const Functional = (props) => {
// do some stuff
return (
<div>
// more HTML based on the props
</div>
);
}
Functional.propTypes = {
prop1: React.PropTypes.string.isRequired,
prop2: React.PropTypes.string.isRequired,
// ...
};
If I'm using Redux and following the container component pattern, what would be the best way to render a dynamic number of these <Functional/> components inside a wrapper component, based on elements inside a array (which is inside my Redux state)?
E.g. My Redux state might look like this:
{
functionalItems: [
{
prop1: 'x1',
prop2: 'y1',
// ...
},
{
prop1: 'x2',
prop2: 'y2'
},
// ... more items
],
// ...
}
So each item in the functionalItems array should correspond to a <Functional/> component, which all get rendered adjacent to each other.
This is the second time I have come across this problem, so I'm hoping that it's common enough that there is good solution out there.
I'll post the solutions I can come up with (but which have undesirable traits), as answers to this question.
I'd like to suggest that you pass the entire array to the wrapper component like this:
const mapStateToProps = (state) => ({
items: getFunctionalItems(state),
// ...
});
and then in your Wrapper.jsx, do it like this:
const Wrapper = (props) => {
const elements = props.items.map((item, index) => {
<Functional prop1={ item.prop1 } prop2={ item.prop2 } ...
key={ ... /* you can use index here */ }/>
});
return (
<div>
{ elements }
</div>
);
};
...where getFunctionalItems() is an accessor function that is the canonical means of accessing the functional items from the state.
This way, you can handle changes in state structure, or a different rendering layout. (ergo more robust (I think)). And it looks more like following the Single Responsibility Principle.
Solution Description
Create a wrapper functional presentation component that takes in a quantity and functions to fetch the <Functional/> prop values.
Create a container component that connects to this new wrapper component and passes in the quantity (based on the Redux state) and accessor functions to fetch the <Functional/> prop values.
Example Code
Wrapper.jsx:
const Wrapper = (props) => {
const elements = [];
for (let i = 0; i < props.quantity; i++) {
elements.push(
<Functional prop1={ getPropValue1(i) } prop2={ getPropValue2(i) } ...
key={ ... }/>
);
}
return (
<div>
{ elements }
</div>
);
};
Wrapper.propTypes = {
quantity: React.PropTypes.number.isRequired,
getPropValue1: React.PropTypes.func.isRequired,
getPropValue2: React.PropTypes.func.isRequired,
// ...
};
ContainerComponent.js:
const mapStateToProps = (state) => ({
quantity: state.functionalItems.length,
getPropValue1: (index) => state.functionalItems[index].prop1,
getPropValue2: (index) => state.functionalItems[index].prop2,
// ...
});
const ContainerComponent = connect(mapStateToProps)(Wrapper);

How can I update state.item[1] in state using setState?

I'm creating an app where the user can design his own form. E.g. specify name of the field and details of which other columns that should be included.
The component is available as a JSFiddle.
My initial state looks like this:
var DynamicForm = React.createClass({
getInitialState: function() {
var items = {};
items[1] = { name: 'field 1', populate_at: 'web_start',
same_as: 'customer_name',
autocomplete_from: 'customer_name', title: '' };
items[2] = { name: 'field 2', populate_at: 'web_end',
same_as: 'user_name',
autocomplete_from: 'user_name', title: '' };
return { items };
},
render: function() {
var _this = this;
return (
<div>
{ Object.keys(this.state.items).map(function (key) {
var item = _this.state.items[key];
return (
<div>
<PopulateAtCheckboxes this={this}
checked={item.populate_at} id={key}
populate_at={data.populate_at} />
</div>
);
}, this)}
<button onClick={this.newFieldEntry}>Create a new field</button>
<button onClick={this.saveAndContinue}>Save and Continue</button>
</div>
);
}
I want to update the state when the user changes any of the values, but I'm having a hard time to target the correct object:
var PopulateAtCheckboxes = React.createClass({
handleChange: function (e) {
item = this.state.items[1];
item.name = 'newName';
items[1] = item;
this.setState({items: items});
},
render: function() {
var populateAtCheckbox = this.props.populate_at.map(function(value) {
return (
<label for={value}>
<input type="radio" name={'populate_at'+this.props.id} value={value}
onChange={this.handleChange} checked={this.props.checked == value}
ref="populate-at"/>
{value}
</label>
);
}, this);
return (
<div className="populate-at-checkboxes">
{populateAtCheckbox}
</div>
);
}
});
How should I craft this.setState to get it to update items[1].name ?
Here's how you can do it without helper libs:
handleChange: function (e) {
// 1. Make a shallow copy of the items
let items = [...this.state.items];
// 2. Make a shallow copy of the item you want to mutate
let item = {...items[1]};
// 3. Replace the property you're intested in
item.name = 'newName';
// 4. Put it back into our array. N.B. we *are* mutating the array here,
// but that's why we made a copy first
items[1] = item;
// 5. Set the state to our new copy
this.setState({items});
},
You can combine steps 2 and 3 if you want:
let item = {
...items[1],
name: 'newName'
}
Or you can do the whole thing in one line:
this.setState(({items}) => ({
items: [
...items.slice(0,1),
{
...items[1],
name: 'newName',
},
...items.slice(2)
]
}));
Note: I made items an array. OP used an object. However, the concepts are the same.
You can see what's going on in your terminal/console:
❯ node
> items = [{name:'foo'},{name:'bar'},{name:'baz'}]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> clone = [...items]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> item1 = {...clone[1]}
{ name: 'bar' }
> item1.name = 'bacon'
'bacon'
> clone[1] = item1
{ name: 'bacon' }
> clone
[ { name: 'foo' }, { name: 'bacon' }, { name: 'baz' } ]
> items
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] // good! we didn't mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don't need to clone items 0 and 2 because we're not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied
You could use the update immutability helper for this:
this.setState({
items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})
Or if you don't care about being able to detect changes to this item in a shouldComponentUpdate() lifecycle method using ===, you could edit the state directly and force the component to re-render - this is effectively the same as #limelights' answer, as it's pulling an object out of state and editing it.
this.state.items[1].name = 'updated field name'
this.forceUpdate()
Post-edit addition:
Check out the Simple Component Communication lesson from react-training for an example of how to pass a callback function from a state-holding parent to a child component which needs to trigger a state change.
Wrong way!
handleChange = (e) => {
const { items } = this.state;
items[1].name = e.target.value;
// update state
this.setState({
items,
});
};
As pointed out by a lot of better developers in the comments: mutating the state is wrong!
Took me a while to figure this out. Above works but it takes away the power of React. For example componentDidUpdate will not see this as an update because it's modified directly.
So the right way would be:
handleChange = (e) => {
this.setState(prevState => ({
items: {
...prevState.items,
[prevState.items[1].name]: e.target.value,
},
}));
};
To modify deeply nested objects/variables in React's state, typically three methods are used: vanilla JavaScript's Object.assign, immutability-helper and cloneDeep from Lodash.
There are also plenty of other less popular third-party libs to achieve this, but in this answer, I'll cover just these three options. Also, some additional vanilla JavaScript methods exist, like array spreading, (see #mpen's answer for example), but they are not very intuitive, easy to use and capable to handle all state manipulation situations.
As was pointed innumerable times in top voted comments to the answers, whose authors propose a direct mutation of state: just don't do that. This is a ubiquitous React anti-pattern, which will inevitably lead to unwanted consequences. Learn the right way.
Let's compare three widely used methods.
Given this state object structure:
state = {
outer: {
inner: 'initial value'
}
}
You can use the following methods to update the inner-most inner field's value without affecting the rest of the state.
1. Vanilla JavaScript's Object.assign
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the shallow copying:', outer.inner) // initial value
const newOuter = Object.assign({}, outer, { inner: 'updated value' })
console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<main id="react"></main>
Keep in mind, that Object.assign will not perform a deep cloning, since it only copies property values, and that's why what it does is called a shallow copying (see comments).
For this to work, we should only manipulate the properties of primitive types (outer.inner), that is strings, numbers, booleans.
In this example, we're creating a new constant (const newOuter...), using Object.assign, which creates an empty object ({}), copies outer object ({ inner: 'initial value' }) into it and then copies a different object { inner: 'updated value' } over it.
This way, in the end the newly created newOuter constant will hold a value of { inner: 'updated value' } since the inner property got overridden. This newOuter is a brand new object, which is not linked to the object in state, so it can be mutated as needed and the state will stay the same and not changed until the command to update it is ran.
The last part is to use setOuter() setter to replace the original outer in the state with a newly created newOuter object (only the value will change, the property name outer will not).
Now imagine we have a more deep state like state = { outer: { inner: { innerMost: 'initial value' } } }. We could try to create the newOuter object and populate it with the outer contents from the state, but Object.assign will not be able to copy innerMost's value to this newly created newOuter object since innerMost is nested too deeply.
You could still copy inner, like in the example above, but since it's now an object and not a primitive, the reference from newOuter.inner will be copied to the outer.inner instead, which means that we will end up with local newOuter object directly tied to the object in the state.
That means that in this case mutations of the locally created newOuter.inner will directly affect the outer.inner object (in state), since they are in fact became the same thing (in computer's memory).
Object.assign therefore will only work if you have a relatively simple one level deep state structure with innermost members holding values of the primitive type.
If you have deeper objects (2nd level or more), which you should update, don't use Object.assign. You risk mutating state directly.
2. Lodash's cloneDeep
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the deep cloning:', outer.inner) // initial value
const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
newOuter.inner = 'updated value'
console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<main id="react"></main>
Lodash's cloneDeep is way more simple to use. It performs a deep cloning, so it is a robust option, if you have a fairly complex state with multi-level objects or arrays inside. Just cloneDeep() the top-level state property, mutate the cloned part in whatever way you please, and setOuter() it back to the state.
3. immutability-helper
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
const update = immutabilityHelper
console.log('Before the deep cloning and updating:', outer.inner) // initial value
const newOuter = update(outer, { inner: { $set: 'updated value' } })
console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://wzrd.in/standalone/immutability-helper#3.0.0"></script>
<main id="react"></main>
immutability-helper takes it to a whole new level, and the cool thing about it is that it can not only $set values to state items, but also $push, $splice, $merge (etc.) them. Here is a list of commands available.
Side notes
Again, keep in mind, that setOuter only modifies the first-level properties of the state object (outer in these examples), not the deeply nested (outer.inner). If it behaved in a different way, this question wouldn't exist.
Which one is right for your project?
If you don't want or can't use external dependencies, and have a simple state structure, stick to Object.assign.
If you manipulate a huge and/or complex state, Lodash's cloneDeep is a wise choice.
If you need advanced capabilities, i.e. if your state structure is complex and you need to perform all kinds of operations on it, try immutability-helper, it's a very advanced tool which can be used for state manipulation.
...or, do you really need to do this at all?
If you hold a complex data in React's state, maybe this is a good time to think about other ways of handling it. Setting a complex state objects right in React components is not a straightforward operation, and I strongly suggest to think about different approaches.
Most likely you better be off keeping your complex data in a Redux store, setting it there using reducers and/or sagas and access it using selectors.
I had the same problem. Here's a simple solution that works !
const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });
According to the React documentation on setState, using Object.assign as suggested by other answers here is not ideal. Due to the nature of setState's asynchronous behavior, subsequent calls using this technique may override previous calls causing undesirable outcomes.
Instead, the React docs recommend to use the updater form of setState which operates on the previous state. Keep in mind that when updating an array or object you must return a new array or object as React requires us to preserve state immutability. Using ES6 syntax's spread operator to shallow copy an array, creating or updating a property of an object at a given index of the array would look like this:
this.setState(prevState => {
const newItems = [...prevState.items];
newItems[index].name = newName;
return {items: newItems};
})
First get the item you want, change what you want on that object and set it back on the state.
The way you're using state by only passing an object in getInitialState would be way easier if you'd use a keyed object.
handleChange: function (e) {
item = this.state.items[1];
item.name = 'newName';
items[1] = item;
this.setState({items: items});
}
Don't mutate the state in place. It can cause unexpected results. I have learned my lesson! Always work with a copy/clone, Object.assign() is a good one:
item = Object.assign({}, this.state.items[1], {name: 'newName'});
items[1] = item;
this.setState({items: items});
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Use array map with arrow function, in one line
this.setState({
items: this.state.items.map((item, index) =>
index === 1 ? { ...item, name: 'newName' } : item,
)
})
Sometimes in React, mutating the cloned array can affect the original one, this method will never cause mutation:
const myNewArray = Object.assign([...myArray], {
[index]: myNewItem
});
setState({ myArray: myNewArray });
Or if you just want to update a property of an item:
const myNewArray = Object.assign([...myArray], {
[index]: {
...myArray[index],
prop: myNewValue
}
});
setState({ myArray: myNewArray });
As none of the above options was ideal to me I ended up using map:
this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })
Mutation free:
// given a state
state = {items: [{name: 'Fred', value: 1}, {name: 'Wilma', value: 2}]}
// This will work without mutation as it clones the modified item in the map:
this.state.items
.map(item => item.name === 'Fred' ? {...item, ...{value: 3}} : item)
this.setState(newItems)
It's really simple.
First pull the entire items object from state, updated the part of the items object as desired, and put the entire items object back in state via setState.
handleChange: function (e) {
items = Object.assign(this.state.items); // Pull the entire items object out. Using object.assign is a good idea for objects.
items[1].name = 'newName'; // update the items object as needed
this.setState({ items }); // Put back in state
}
Found this surprisingly hard and none of the ES6 spread magic seemed to work as expected.
Was using a structure like this to get rendered element properties for layout purposes.
found using the update method from immutability-helper to be the most straight forward one in this simplified example:
constructor(props) {
super(props)
this.state = { values: [] }
this.updateContainerState = this.updateContainerState.bind(this)
}
updateContainerState(index, value) {
this.setState((state) => update(state, { values: { [index]: { $set: value } } }))
}
as adapted from https://github.com/kolodny/immutability-helper#computed-property-names
of the to be updated array member is a more nested complex object use the appropriate deep copy method based on complexity.
There are surely better ways to handle layout parameters, but this is about how to handle arrays. The relevant values for each child element could also be computed outside of them, but I found it more convenient to pass containerState down, so they childs can fetch properties at will and Update the parent state array at their given index.
import React from 'react'
import update from 'immutability-helper'
import { ContainerElement } from './container.component.style.js'
import ChildComponent from './child-component'
export default class ContainerComponent extends React.Component {
constructor(props) {
super(props)
this.state = { values: [] }
this.updateContainerState = this.updateContainerState.bind(this)
}
updateContainerState(index, value) {
this.setState((state) => update(state, { values: { [index]: { $set: value } } }))
}
// ...
render() {
let index = 0
return (
<ContainerElement>
<ChildComponent
index={index++}
containerState={this.state}
updateContainerState={this.updateContainerState}
/>
<ChildComponent
index={index++}
containerState={this.state}
updateContainerState={this.updateContainerState}
/>
</ContainerElement>
)
}
}
#JonnyBuchanan's answer works perfectly, but for only array state variable. In case the state variable is just a single dictionary, follow this:
inputChange = input => e => {
this.setState({
item: update(this.state.item, {[input]: {$set: e.target.value}})
})
}
You can replace [input] by the field name of your dictionary and e.target.value by its value. This code performs the update job on input change event of my form.
Use the event on handleChange to figure out the element that has changed and then update it. For that you might need to change some property to identify it and update it.
See fiddle https://jsfiddle.net/69z2wepo/6164/
I would move the function handle change and add an index parameter
handleChange: function (index) {
var items = this.state.items;
items[index].name = 'newName';
this.setState({items: items});
},
to the Dynamic form component and pass it to the PopulateAtCheckboxes component as a prop. As you loop over your items you can include an additional counter (called index in the code below) to be passed along to the handle change as shown below
{ Object.keys(this.state.items).map(function (key, index) {
var item = _this.state.items[key];
var boundHandleChange = _this.handleChange.bind(_this, index);
return (
<div>
<PopulateAtCheckboxes this={this}
checked={item.populate_at} id={key}
handleChange={boundHandleChange}
populate_at={data.populate_at} />
</div>
);
}, this)}
Finally you can call your change listener as shown below here
<input type="radio" name={'populate_at'+this.props.id} value={value} onChange={this.props.handleChange} checked={this.props.checked == value} ref="populate-at"/>
If you need to change only part of the Array,
You've a react component with state set to.
state = {items: [{name: 'red-one', value: 100}, {name: 'green-one', value: 999}]}
It's best to update the red-one in the Array as follows:
const itemIndex = this.state.items.findIndex(i=> i.name === 'red-one');
const newItems = [
this.state.items.slice(0, itemIndex),
{name: 'red-one', value: 666},
this.state.items.slice(itemIndex)
]
this.setState(newItems)
or if you have a dynamically generated list and you don't know the index but just have the key or id:
let ItemsCopy = []
let x = this.state.Items.map((entry) =>{
if(entry.id == 'theIDYoureLookingFor')
{
entry.PropertyToChange = 'NewProperty'
}
ItemsCopy.push(entry)
})
this.setState({Items:ItemsCopy});
Try with code:
this.state.items[1] = 'new value';
var cloneObj = Object.assign({}, this.state.items);
this.setState({items: cloneObj });
Following piece of code went easy on my dull brain. Removing the object and replacing with the updated one
var udpateditem = this.state.items.find(function(item) {
return item.name == "field_1" });
udpateditem.name= "New updated name"
this.setState(prevState => ({
items:prevState.dl_name_template.filter(function(item) {
return item.name !== "field_1"}).concat(udpateditem)
}));
How about creating another component(for object that needs to go into the array) and pass the following as props?
component index - index will be used to create/update in array.
set function - This function put data into the array based on the component index.
<SubObjectForm setData={this.setSubObjectData} objectIndex={index}/>
Here {index} can be passed in based on position where this SubObjectForm is used.
and setSubObjectData can be something like this.
setSubObjectData: function(index, data){
var arrayFromParentObject= <retrieve from props or state>;
var objectInArray= arrayFromParentObject.array[index];
arrayFromParentObject.array[index] = Object.assign(objectInArray, data);
}
In SubObjectForm, this.props.setData can be called on data change as given below.
<input type="text" name="name" onChange={(e) => this.props.setData(this.props.objectIndex,{name: e.target.value})}/>
this.setState({
items: this.state.items.map((item,index) => {
if (index === 1) {
item.name = 'newName';
}
return item;
})
});
handleChanges = (value, key) => {
// clone the current State object
let cloneObject = _.extend({}, this.state.currentAttribute);
// key as user.name and value= "ABC" then current attributes have current properties as we changes
currentAttribute[key] = value;
// then set the state "currentAttribute" is key and "cloneObject" is changed object.
this.setState({currentAttribute: cloneObject});
and Change from Text box add onChange event
onChange = {
(event) => {
this.handleChanges(event.target.value, "title");
}
}
Try this it will definetly work,other case i tried but didn't work
import _ from 'lodash';
this.state.var_name = _.assign(this.state.var_name, {
obj_prop: 'changed_value',
});

Categories