I have a redux state holding various keys and values:
data: {
item1: {
id: 1
name: 'Shirt',
cost: 3
},
item2: {
id: 2
name: 'Pants',
cost: 10
},
item3: {
id: 3
name: 'Hat',
cost: 7
}
}
I have an interface where the user can change these values. I want them to be able to make any number of changes to this data and if they want to go back to the original state, they can hit a reset button.
How would you go about structuring the redux state to hold the edit information?
What I'm imagining is simply starting with the original state, and then duplicating the original state. So I'd have originalMenu and editedMenu in the redux state. That way any deletions, inserts, edits, etc. will be contained to the editedMenu part of the state.
The above doesn't seem very clean because I'd need to store duplicate data in the redux state. I was also imagining having a menuEdits part of the state that just houses the changes. So it'd have the id and whatever values have been changed. Or an isDeleted field for removed items.
I think you should have three states in your data object: past, present and future. Can you check this https://github.com/reactjs/redux/blob/master/docs/recipes/ImplementingUndoHistory.md out?.I think it may be helpful.
Related
I have a page with several components on it, one of the components reads redux state.
When there some changes occur on the redux object component it reloads, shows the spinner and all the data on the component gets updated.
But my task is to update one single value in the redux object state without having to rerender and refresh everything.
For instance here is the redux object, which holds an array of objects with some data.
I need to update one value state in a certain object let say the last one.
But I need to do it without having to update all-state.
state:{
0:{
name : 'some name.....',
surname: 'some surname',
age: '23',
phone: '+12345678'
state: 'ON'
},
1:{
name : 'some name.....',
surname: 'some surname',
age: '23',
phone: '+12345678'
state: 'ON'
},
2:{
name : 'some name.....',
surname: 'some surname',
age: '23',
phone: '+12345678'
state: 'OFF' <-- I need to update only this one
},
}
I have one function in Redux actions that load all these data.
But now I need something like a separate function updateData function which will be updating one value in an existing state object without having to refresh everything.
Is it possible to do so in Redux?
Your state is an object (not an array) of objects.
You can't update a single property of your state. You have to supply a new object with the updated values. Study immutability in redux: https://redux.js.org/faq/immutable-data
Try this in your reducer:
const changedVal=state[2];
changedVal.state='ON';
return {...state,changedVal};
You will need to familiarise yourself with how actions and reducers work in Redux but yes, this is most definitely possible.
You can make an UPDATE_DATA action creator that you would call inside your react component(s). And you would have a reducer that will control how the data is processed and state changed.
-edit-
As mentioned in the comments, I use "immer" in reducers with Redux. Here's an example of how I've used it for editing and existing item in an array:
case EDIT_ITEM:
return produce(state, (draft) => {
const index = findItemIndex(draft, action.payload._id)
draft[index] = action.payload
})
So you pass in state and a callback, with draft being the mutable equivalent of the state object.
In short, updating one property isn't possible practically. Of course it can't be done, but it's very difficult. I can explain why.
Let's start with updating an item. This is definitely possible. Suppose you have a redux selector listening to the data, when sending the data over to a component.
const DisplayOneItem = React.memo(( name, age ) => { ... })
You can see, first the name (and age) is sent to this component, also a React.memo is applied so that only when one of the property is changed, it can then update. But don't get mixed with the following line:
const DisplayOneItem = React.memo(( item ) => { ... })
The above line takes entire item over, and when you change one property, the item changes as well, therefore no matter what, you will get a new update.
You might wonder what happens if React.memo isn't used? Yes, render all the time. Practically if you change a property, either item or data gets changed entirely. Otherwise how do you notify React there's a change? :)
NOTE: React can't update according to a value change by default. The reason why it updates is because you ask it to, through setState({ ...data }). So now it's the question about how granularity YOU control the dispatch (or render). People might think React is magic in terms of rendering, but actually it only does it upon a request. So now your question is, I ask it to render, but it shouldn't render? Haha, you get the point.
I'm building a desktop app using Electron, React, and Redux.
One of the key components of this desktop app is going to be tying certain components of my redux state to physical files on the drive. The website is the 'master' copy of whether certain data is accessible to the user. The app downloads a list of files it should have access to, and then tracks which files have already been downloaded. A typical workflow would be to load the list, then hit 'download', and when the item is downloaded, use it in the app.
When I update the local redux list of items, I need to also manage the state of those files. When a record is removed from the redux store, I need to delete the associated physical files.
This puts me in a weird space. It feels really, really odd to write a reducer that makes changes to the local file structure. It's even worse because I'm using electron-redux to keep multiple processes in sync -- I'm running a complete redux stack in both the main process and my renderer window(s), with electron-redux to keep them in a shared state.
The more I look at this, the more it feels like the reducer is completely the wrong place to do this. The flip side is that this code definitely needs to happen based on state, which makes redux the exact right place to do it. Not sure what the right solution here is -- I have contradicting design principles in play, and I'm not sure how to resolve that.
Edit:
Part of the difficulty here is that the reducer isn't doing a simple replacement of state. In this case, the reducer is doing a pretty complicated algorithm:
Current State:
[
{
id: 1,
serverState: 'red',
localState: 'file'
},
{
id: 2,
serverState: 'green',
localState: 'file-processed'
}
]
The server might send (and therefore my action payload contains):
[
{
id: 2,
serverState: 'purple',
},
{
id: 3,
serverState: 'blue',
}
]
As a result, I need to merge data, so my state becomes:
[
{
id: 2,
serverState: 'purple',
localState: 'file-processed'
},
{
id: 3,
serverState: 'blue',
localState: 'no-file'
}
]
Note that Item #2's localState didn't change, but the serverState items were overwritten. Item #1's local files would need to be removed from the file system, user no longer has permissions for them.
The reducer is not the right place for side-effects other than changing the store.
There are libraries made for handling side-effects in relation to redux. For example, there's redux-saga: https://redux-saga.js.org/
Redux-saga is a redux middleware, and sagas are triggered by the same actions that you would use to update the redux store.
Say I have animals in a redux store. It's a normalized object, the key is the id and the value is the object itself. Each animal has a key is_dog.
animals: {
'id1': { location: 'foo', is_dog: true, name: 'Dog1' },
'id2': { location: 'foo', is_dog: false, name: 'Cat1' },
'id3': { location: 'foo', is_dog: false, name: 'Cat2' },
'id4': { location: 'foo', is_dog: true, name: 'Dog2' },
}
I have a DogsList connected react component that looks at the store state and passes in an array of dogIds to the UI component. Calculation of dogIds is something like:
dogIds = _.chain(state.animals).filter('is_dog').map('id').value();
In the example above this would return ['id1', 'id4'].
However, let's assume I have thousands of animals, and each animal has other properties that change frequently, say currentLocation, lastFed, etc. I know that dogIds will only change if is_dog changes for any animal, but I'm not quite sure how to not do the filter and map calculations unnecessarily on every change to any animal. I tried using reselect but I don't think it really helps in this case.
I'm leaning towards storing dogIds in redux proper, and manually updating it only when relevant. Like when a new dog is added to animals, or if a cat changes to a dog (my analogy is falling apart here). But I feel like I'm doing something wrong by keeping computed properties in a redux store.
Perhaps you could look into using React's lifecycle methods like shouldComponentUpdate - here you could assess whether newDogIDs !== oldDogIDs and then only do stuff if they are different.
I have a redux store like so:
{
'1f458697-e2c0-4d11-ada0-ee7b113c2429': {
name: 'Jim',
title: 'Developer',
active: true
},
'4498eb08-3786-495a-a66c-21a65138ab24': {
name: 'Darren',
title: 'Human Resources',
active: false
},
'c7975551-153f-4eab-a875-ed5445f76252': {
name: 'Francesa',
title: 'Chief of Internal Operations',
active: false
}
}
And I have a selector that gets an active user.
export const getActiveUser = (users) => _.find(users, { 'active': true });
My question is, whats the nicest way to include the ID of the user in the payload to use within my React component:
Is this is a bad practice and a sign that I should be using an array instead.
Should I just change my user payload to include the ID. This feels wrong as I would be duplicating data.
Should I manipulate that specific selector to include the users ID.
Yes, use arrays for table-like data and filter function for filtering the result set (if multiple users can be active at a time). If only one user can be active at a time, consider changing the db schema and create a separate variable activeUserId instead of having all of them contain unnecessary payload (and change the shape of the redux store accordingly).
- Is this is a bad practice and a sign that I should be using an array
instead.
Normalizing the data, so you can edit or delete items (users in your case) in O(1) is a good approach, which is also covered in the Dan Abramov's idiomatic redux course.
- Should I just change my user payload to include the ID. This feels wrong as I would be duplicating data.
Since you don't usually change IDs, adding them inside the user's object should not be a problem, and it should give you quick access to the ID. However, if you don't won't to duplicate that's acceptable as well.
- Should I manipulate that specific selector to include the users ID.
Part of the the selectors role is to create derived data, just like what you do when you filter the users. So if you want to the the ID to each user, this is a good place to do so. Don't mutate the objects, just create new user objects with ID. However, if you've got lots of users, you should use memoized selectors or add the ID as part of the payload to prevent performance issues.
Given my initial redux state is :
const state = {
currentView: 'ROOMS_VIEW',
navbarLinks: List([
{name: 'Rooms', key: 'ROOMS_VIEW'},
{name: 'Dev', key: ''}
]),
roomListsSelected: {group: 0, item: 0},
roomLists: [
{
name: "Filters",
expanded: true,
listItems: [
{ icon: 'images/icon-warning.svg', name: 'Alerts', filter: room => room.hasAlert },
{ icon: 'images/icon-playlist.svg', name: 'In Progress', filter: room => room.progress > 20 },
{ icon: 'images/icon-playlist.svg', name: 'Almost Done', filter: room => room.progress > 90 },
{ icon: 'images/icon-playlist.svg', name: 'Complete', filter: room => room.status === 'complete' },
{ icon: 'images/icon-playlist.svg', name: 'Recently Completed', filter: room => false },
{ icon: 'images/icon-playlist.svg', name: 'All Rooms', filter: room => true }
]
}
],
rooms: List(generateRooms())
}
I need to make a reducer that does this:
state.roomList[n].expanded = !state.roomList[n].expanded
I am new to using a Redux workflow and the best way to solve this is to make roomList an immutable.js object or write some code to make a deep clone of my state object.
Also state.roomList will have new data pushed to it from future features.
Summery / Question: What is the best way to return a new state object in a reducer when making changes like this deep in the state, or should I change the structure of the Redux state object?
What I did In the end Immutable seems the way to go. There are some tricks with Immutable to reduce react rendering time and it meets all the project requirements. Also it is early enough in the project to use a new library without making major changes.
First, idiomatic Redux encourages you to "normalize" your state and flatten it as much as possible. Use objects keyed by item IDs to allow direct lookups of items, use arrays of IDs to denote ordering, and anywhere that one item needs to refer to another, it only stores the ID of the other item instead of the actual data. That allows you to do simpler lookups and updates of nested objects. See the Redux FAQ question on nested data.
Also, it looks like you're currently storing a number of functions directly in your Redux state. Technically that works, but it's definitely not idiomatic, and will break features like time-travel debugging, so it's heavily discouraged. The Redux FAQ gives some more info on why storing non-serializable values in your Redux state is a bad idea.
edit:
As a follow-up, I recently added a new section to the Redux docs, on the topic of "Structuring Reducers". In particular, this section includes chapters on "Normalizing State Shape" and "Updating Normalized Data", as well as "Immutable Update Patterns".
Reducer composition:
De-compose your reducers into smaller pieces so that a reducer is small enough to deal with simple data structure. eg. In your case you may have: roomListReducer listItemsReducer listItemReducer. Then at each reducer, its going to make it much more easier for you to read which part of the state you are dealing with. It helps a lot because each of your reducer is dealing with small piece of data that you don't have to worry things like 'should i deep copy or shallow copy'.
Immutable
I personally don't use immutable.js because I prefer to deal with plain objects. and there are just too much code to change to adopt a new API. But the idea is, make sure your state changes are always done through pure functions. Therefore, you can just simply write your own helper functions to do what you want, just make sure that they are tested thoroughly when dealing with complex objects.
Or simply enough, you can always deep copy your state in each reducer, and mutate in the copy then return the copy. But this is obviously not the best way.