Redux toolkit - update a key value in an object - redux

I need some help with modifying my reducer. I'm using Redux Toolkit and in one of the state slices I've got an object with some grouped settings:
initialState: {
...
userSettings: {mode: 2, subscription: false, setting3: 'text', setting4: 'another text'},
...
}
a reducer I have is:
setUserSettings: (state, action) => {
state.userSettings: action.payload
}
In different parts of a component, I'd update individual settings from the userSettings object:
dispatch(setUserSettings({ mode: 4 }))
in another place:
dispatch(setUserSettings({ setting3: 'some other text'})
How would I modify the reducer to be able to do it? Thanks

Since RTK use immer library underly, you can mutate the state by assigning directly. See Mutating and Returning State
import { configureStore, createSlice } from '#reduxjs/toolkit';
const settingsSlice = createSlice({
name: 'settings',
initialState: {
otherSettings: { ok: true },
userSettings: { mode: 2, subscription: false, setting3: 'text', setting4: 'another text' },
},
reducers: {
setUserSettings: (state, action) => {
state.userSettings = { ...state.userSettings, ...action.payload };
},
},
});
const { setUserSettings } = settingsSlice.actions;
const store = configureStore({ reducer: settingsSlice.reducer });
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch(setUserSettings({ mode: 4 }));
store.dispatch(setUserSettings({ setting3: 'some other text' }));
Output:
{
otherSettings: { ok: true },
userSettings: {
mode: 4,
subscription: false,
setting3: 'text',
setting4: 'another text'
}
}
{
otherSettings: { ok: true },
userSettings: {
mode: 4,
subscription: false,
setting3: 'some other text',
setting4: 'another text'
}
}

change your reducer to
setUserSettings: (state, action) => {state.userSettings={...state.userSettings,action.payload}}
what we did here is making a copy of the old state and then combine it with the new value you need to change
the new key of the object will override the old one

Related

How to remain same current page pagination in redux rtk

I build an applicant with Redux RTK with createEntity
Two issue that I couldn't found it on the docs
CreateEntity is only return {ids: [], entities: []}? Is possible that return eg: totalPage from the response also?
Cache page only work on the hardcode initialState in createSlice if the pageQuery is same.
First question:
Getting the response from server was
{
users: [{id: 1}, ...]
totalPage: 100
}
I'd like to send totalPage to auto generated hook also.
export const usersAdapter = createEntityAdapter({})
export const initialState = usersAdapter.getInitialState()
export const usersApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
getUsers: builder.query({
query: (args) => {
return {
url: '/api/users',
method: 'GET',
params: { page: 1, limit: 10 }
}
},
validateStatus: (response, result) => {
return response.status === 200 && !result.isError
},
transformResponse: (responseData) => {
const loadedUsers = responseData?.users.map((user) => user)
console.log("responseData: ", responseData) // <----- return { users: [], totalPage: 100 }. Could we set this totalPage value into Adapter?
return usersAdapter.setAll(initialState, loadedUsers)
},
providesTags: (result, error, arg) => {
if (result?.ids) {
return [
{ type: "User", id: "LIST" },
...result.ids.map((id) => ({ type: "User", id })),
]
} else return [{ type: "User", id: "LIST" }]
},
})
})
})
Use the hook in component
const { data } = useGetUsersQuery("page=1&limit=10");
console.log(data) // { ids: [], entity: [{}, {}] };
// expected return { ids: [], entity: [{}, {}], totalPage: 100}
Second question:
Store the page query in createSlice. The edit page will be remain same after refresh if the page query value same as initialState value.
import { createSlice } from "#reduxjs/toolkit"
const userReducer = createSlice({
name: "user",
initialState: {
query: `page=1&limit=10`,
},
reducers: {
setUserPageQuery: (state, action) => {
const query = action.payload
state.query = query
},
},
})
Page url Flow:
localhost:3000/users > localhost:3000/users/4 > refresh -> data will remain after refresh browser. (query "page=1&limit10" same as createSlice initialState value )
localhost:3000/users > localhost:3000/users/15 > refresh -> data state will gone after refresh browser. (query "page=2&limit10" different from createSlice initialState value )
Appreciate all the reply :)

Next js Redux, Objects are not valid as a React child

Error: Objects are not valid as a React child (found: object with keys {_id, name}). If you meant to render a collection of children, use an array instead.
Tried to fix this for days and no result.
i have a model
import mongoose from 'mongoose'
const CategoriesSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
parent: {
type: mongoose.Types.ObjectId,
ref: 'categories'
},
},
{
timestamps: true
})
let Dataset = mongoose.models.categories || mongoose.model('categories', CategoriesSchema)
export default Dataset
and i have getCategories like this
[getCategories ]
const getCategories = async (req, res) => {
try {
const categories = await Categories.find().populate("parent", "name");
res.json({ categories });
}
catch (err)
{
return res.status(500).json({ err: err.message });
}
};
in my Globale state i have
export const DataContext = createContext()
export const DataProvider = ({children}) => {
const initialState = {
notify: {}, auth: {}, cart: [], modal: [], orders: [], users: [], categories: []
}
const [state, dispatch] = useReducer(reducers, initialState)
useEffect(() => {
getData('categories').then(res => {
if(res.err)
return dispatch({type: 'NOTIFY', payload: {error: res.err}})
dispatch({ type: 'ADD_CATEGORIES', payload: res.categories })
})
},[])
return(
<DataContext.Provider value={{state, dispatch}}>
{children}
</DataContext.Provider>
)
}
when i call categories throw:exception
when i change dispatch in Globale state like :
dispatch({ type: 'ADD_CATEGORIES', payload: [] })
i get no elements in array :

How to add cases in ExtraReducer to match the actions created in currentReducer using createSlice() from #reduxjs/toolkit

Here below I have mentioned a redux slice. A fetchAllApps thunk function is created with createAsyncThunk for action 'allApps/allappsAdded/' which I dynamically got by allAppsAdded.type. When the fetchAllapps is dispatched it generated actions of type 'allApps/allappsAdded/pending', 'allApps/allappsAdded/fulfilled', 'allApps/allappsAdded/rejected' which I need to add in extraReducers to handle it by doing hardcode.Is there any way to add these action types like allAppsAdded.type programatically?. so that in future It makes easy for me to change these without redundant..
import {
configureStore,
createAsyncThunk,
createSlice
} from "#reduxjs/toolkit";
const initialState = {
apps: [],
categories: [],
loading: {
apps: false
}
};
const allappsSlice = createSlice({
name: "allapps",
initialState,
reducers: {
allappsAdded: (state, action) => {
state["apps"] = action.payload.apps;
state["categories"] = action.payload.categories;
}
},
extraReducers: {
}
});
export default () =>
configureStore({
reducer: allappsSlice.reducer
});
const { allappsAdded } = allappsSlice.actions;
const fetchAllApps = createAsyncThunk(allappsAdded.type, async () => {
console.log("ss");
setTimeout(() => ({ apps: [], categories: [] }), 2000);
});
export { allappsAdded, fetchAllApps };

Redux Toolkit: How to test actions with uid prepare callback

In the docs for testing incrementing todo ids, this assumes a predictable response.
In an example such as below, a unique id is generated.
How could this be tested?
This test passes, but I'm not sure if it's correct, shouldn't the id be defined based on what's in the prepare callback?
slice.js
add: {
reducer: (state, {payload}: PayloadAction<{id: string, item: Item}>) => {
state[payload.id] = payload.item
},
prepare: (item: Item) => ({
payload: {id: cuid(), item}
})
}
slice.test.js
it('should handle add', () => {
expect(
reducer(
{},
{
type: actions.add,
payload: {
id: 'id-here?',
item: {
other: 'properties...'
}
},
}
)
).toEqual({
'id-here?': {
other: 'properties...'
},
})
})
You can pull out the prepare function and also the reducer function into it's own constant and then test prepare in isolation:
todosSlice.js:
[...]
let nextTodoId = 0;
export const addTodoPrepare = (text) => {
return {
payload: {
text,
id: nextTodoId++
}
}
}
export const addTodoReducer = (state,
action) => {
const {id, text} = action.payload;
state.push({
id,
text,
completed: false
});
};
const todosSlice = createSlice({
name: 'todos',
initialState: [],
reducers: {
addTodo: {
prepare: addTodoPrepare,
reducer: addTodoReducer,
},
}
})
[...]
todosSlice.spec.js:
import todos, {addTodo, addTodoPrepare} from './todosSlice'
describe('addTodoPrepare',
() => {
it('should generate incrementing IDs',
() => {
const action1 = addTodoPrepare('a');
const action2 = addTodoPrepare('b');
expect(action1.payload).toEqual({
id: 0,
text: 'a'
})
expect(action2.payload).toEqual({
id: 1,
text: 'b'
})
})
})
describe('todos reducer',
() => {
[...]
})
For unit testing, NO, just test each reducer independently.
For integration testing and e2e testing, Yes.

Redux translating array of objects into hash tree

Here is the code I am working with:
const initialState = {
_requestStatus: null,
data: {
domains: {},
},
};
const transformCustomDomainsData = (state, json) => {
const { response } = json;
return {
domains: {
...state.data.domains,
...response.map(resp => ({
id: resp.id,
domain: resp.domain,
context: resp.context,
})),
},
};
};
I am trying to come up with code that will translate the initial fetch reducer response into a hash tree.
Something like this:
const initialState = {
byHash: {
'1': {id: '1', content: {title: 'item 1'}},
'2': {id: '2', content: {title: 'item 2'}},
'3': {id: '3', content: {title: 'item 3'}}
}
}
Where the key in the byHash is the id of each item in the array and the value is each object in the json response from the server.
Figured it out...
const transformCustomDomainsData = (state, json) => {
const { response } = json;
return {
domains: {
...state.data.domains,
...response.reduce((obj, resp) => {
obj[resp.id] = resp;
return obj;
}, {}),
},
};
};

Resources