Reputation: 191
I use redux with normalizr to normalize the response from server, basically follow the real-world example. This way entities
reducer is very simple, just merge the response. The problem I have right now is kind of delete
operation. I've found this issue#21 of normalizr repo but still couldn't figure out how to solve this. For example,
Current state is
{
entities:
product_categories: {
...
13: {
...
products: ["1", "2"], <--------------- [i] Current state
...
}
},
products: {
1: {
id: "1"
}
}
}
The normalized response is
{
...
product_categories: {
...
13: {
...
products: ["1"], <---------------- [2] Normalized result
}
...
}
As you can see, the backend api just returns all product ids that belonged to this category, in this case "2" is detached. When 'entities' reducer merges the this response, "2" is still hanging around. Right now I just reload the page but i'm wondering if there's a better way to handle this case?
In entities
reducer, I just merge it like in real-world example.
return merge({}, state, action.payload.entities);
Upvotes: 11
Views: 2248
Reputation: 2426
Use lodash assign. If you use merge will not work.
Example:
const object = {
'a': [1,2,3]
}
const other = {
'a': [1,2]
}
// item 3 was not removed
const merge = _.merge({}, object, other)
console.log(merge) // {'a': [1,2,3]}
// item 3 was removed
const assign = _.assign({}, object, other)
console.log(assign) // {'a': [1,2]}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
Here, how would your code look like
// state
const state = {
entities: {
product_categories: {
'1': {
id: 1,
products: [1,2]
}
},
products: {
'1': {
id: 1
},
'2': {
id: 2
}
}
}
}
// example of action
// normalized by normalizr
const action = {
type: 'REMOVE_PRODUCT_CATEGORY_SUCCESS',
payload: {
entities: {
product_categories: {
'1': {
id: 1,
products: [1]
}
}
}
}
}
// product_categories entity reducer
function productCategoriesEntityReducer (state = {}, action) {
switch (action.type) {
default:
if (_.has(action, 'payload.entities.product_categories')) {
return _.assign({}, state, action.payload.entities.product_categories)
}
return state
}
}
// run reducer on state.entities.product_categories
const newState = productCategoriesEntityReducer(state.entities.product_categories, action);
console.log(newState)
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
Upvotes: 0
Reputation: 268235
Just don't worry about it being there. Think of your state as of a database. You don't truly delete records from the database either to avoid complicated cascades—usually you just change their status in the database. Similarly, with Normalizer, instead of truly deleting the entities, let them be in cache until the user leaves the page!
Upvotes: 11
Reputation: 4227
Below is an explanation of my solution followed by code.
To perform a delete I have updated my reducer to a handle a delete action: REMOVE_ENTITY_ITEM
. In the action I pass in the id
and name
of the entity that is to be removed.
In the reducer I first delete the entity itself which is at store.entities[entityName][entityId]
. Then next i need to remove its id
from all other entities which might be referring to it. Since I am using normalizr
all my entities are flat and if they refer to another entity then they will only have its id in an array. This makes it relatively straightforward to remove the reference. I just loop over all entities and filter out the reference to the entity being removed.
I use this approach in conjunction with the other two approaches of #1.) refreshing the app/state and #2.) flipping the entities status bit rather than deleting and then filtering the turned off items in the UI. These approaches have been well discussed here
const entities = (state={}, action) => {
if(action.payload && action.payload.entities) {
return merge( {} , state, action.payload.entities);
}else{
return deleteHandlingReducer(state, action)
}
}
const deleteHandlingReducer = (state=initialSate, action) => {
switch(action.type){
case "REMOVE_ENTITY_ITEM":
if (!action.meta || !action.meta.name || !action.meta.id) {
return state;
}else{
let newState = Object.assign({}, state);
if(newState[action.meta.name]){
delete newState[action.meta.name][action.meta.id];
Object.keys(state).map(key => {
let entityHash = state[key];
Object.keys(entityHash).map(entityId => {
let entity = entityHash[entityId];
if(entity[action.meta.name] &&
Array.isArray(entity[action.meta.name])){
entity[action.meta.name] = entity[action.meta.name].
filter(item => item != action.meta.id)
}
});
})
}
return newState;
}
default:
return state;
}
}
Now to delete i fire an action like this:
store.dispatch({
type: "REMOVE_ENTITY_ITEM",
meta: {
id: 1,
name: "job_titles"
}
});
Upvotes: 0