I have a loading object, with boolean values for each loading. My state looks like this:
state: {
loading: {
itemA: false,
itemB: false,
itemC: false
}
}
I want to update my state using setLoading({ itemA: true }) but having this only update itemA while keeping itemB and itemC the same as whatever the current state is.
return {
...state,
loading: {
...state.loading,
itemA,
itemB,
itemC
}
};
Here is the full reducer (condensed):
setLoading: state => ({ itemA, itemB, itemC }) => {
return {
...state,
loading: {
...state.loading,
itemA,
itemB,
itemC
}
};
}
Unfortunately, if I setLoading({ itemC: true }), A and B are now undefined.
How do I ensure they are not undefined, but rather whatever is in the state?
Please note - I have tried passing just anything and doing object assign or spreading loading.
However, to increase readability, I am wondering if I can destructure the props and not have to define each loading (I have like 12 things on this page that load separately - a dashboard).
Thanks
Use Object.assign()
const state = {
loading: {
itemA: false,
itemB: false,
itemC: false
}
}
Object.assign(state.loading, {itemA: true});
console.log(state.loading);
Just pass a variable, that you unfold
setLoading: state => (newLoadingState) => {
return {
...state,
loading: {
...state.loading,
...newLoadingState
}
};
}
You could provide default values in your destructure
const fn = state => ({
itemA = state.loading.itemA,
itemB = state.loading.itemB,
itemC = state.loading.itemC
}) => {
return {
...state,
loading: {
...state.loading,
itemA,
itemB,
itemC
}
};
};
Now when the variables are missing, they're taken from the state passed in;
const fn2 = fn({loading: {itemA: false, itemB: false, itemC: false}});
fn2({itemB: true});
// => {loading: {itemA: false, itemB: true, itemC: false}}
Related
I'm doing Todo App in React and I'd use some help. This is probably trivial question, but I don't know how to do it.
context.js:
const initialState = {
isSidebarOpen: true,
isLoading: false,
isEditing: false,
todos: [
{ id: 1608592490852, todo: "Buy milk", important: false },
{ id: 1608592490939, todo: "Take out trash", important: false },
{ id: 1608634291740, todo: "Buy flowers for mom", important: false },
{ id: 1608634291874, todo: "Repair washing machine", important: false },
],
importantTodos: [{ id: 1608634291874, todo: "Repair washing machine" }],};
const handleAction = (e, item) => {
const { id, todo } = item;
const actionName = e.target.getAttribute("name");
if (actionName === actionTypes.addImportant) {
dispatch({ type: actionTypes.addImportant, payload: id });
}
reducer.js:
const reducer = (state, action) => {
const { todos } = state;
switch (action.type) {
case actionTypes.addTodo:
return {
...state,
todos: [...state.todos, { id: action.payload.id, todo: action.payload.todo, important: false }],
};
case actionTypes.addImportant:
const importantTodo = todos.find((item) => item.id === action.payload);
console.log(state);
return {
...state,
todos: [...state.todos, { ...todos, important: true }],
importantTodos: [...state.importantTodos, { id: importantTodo.id, todo: importantTodo.todo }],
};
default:
throw new Error(`No Matching "${action.type}" - action type`);
}};
When adding ToDo to importantTodos, I'd also like to change it's attribute important:false to important: true in todos array. Currently, this code works without changing the attribute when
todos: [...state.todos, { ...todos, important: true }],
line is deleted. With it it just copies all todos, and stores them as new array of objects at the todos array. I think the problem is in my spread operators as I don't understeand them as I tought I do.
Add this snippet in the addImportant case
const updatedTodos = todos.map(todoEl => {
if(todoEl.id === action.payload){
const {id, todo} = todoEl;
return {id, todo, important: true}
}
return todoEl;
})
Update return statement:
return {
...state,
todos: updatedTodos,
importantTodos: [...state.importantTodos, { id: importantTodo.id, todo: importantTodo.todo }],
};
I am trying to write a function that removes all the elements from an array called selectedItems.
It looks like this:
const selectNone = (e: any) => {
let selectedItemsCopy = selectedItems;
selectedItemsCopy = []
setState(
{
...state,
areAllSelected: false,
isChecked: false,
selectedItems: selectedItemsCopy
}
);
}
but in setState, selectedItems does not get updated to the empty copy of the array. What am I doing wrong?
The problem that you have is, that you are actually mutating state, which is basically prohibited in React. In this case, it's very simple to solve, just put the [] inside the setState directly, without any variables:
const selectNone = (e: any) => {
setState(
{
...state,
areAllSelected: false,
isChecked: false,
selectedItems: []
}
);
}
The method you are using to make a copy of the array, doesn't actually copy the array and instead just creates a reference to the original (that's how JS works). In order to make a proper copy (for example to be able to make modifications, not just clear the array), you should use Array.from().
Here's an example
const selectNone = (e: any) => {
let selectedItemsCopy = Array.from(selectedItems);
selectedItemsCopy[x] = changedValue;
setState(
{
...state,
areAllSelected: false,
isChecked: false,
selectedItems: selectedItemsCopy
}
);
}
I have an application that uses redux for state management and sagas for async calls, and I am trying to figure out the correct structure for pagination. I have a reducer like this:
function articles(
state = {
isFetching: false,
didInvalidate: false,
page: 1,
items: []
},
action
) {
switch (action.type) {
case 'INVALIDATE_ARTICLE':
return Object.assign({}, state, {
didInvalidate: true,
page: 1
})
case 'REQUEST_ARTICLES':
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case 'RECEIVE_ARTICLES':
let updatedPage = 'max';
if (action.response.result.length == 10) {
updatedPage = state.page + 1
};
return Object.assign({}, state, {
categoryId: action.category,
isFetching: false,
didInvalidate: false,
items: action.response.result,
lastUpdated: action.receivedAt,
page: updatedPage
})
default:
return state
}
}
And the saga that calls those related actions like this:
function* fetchArticles(action) {
const { domain, category, page } = action.payload;
try {
yield put(requestArticles(category))
const response = yield fetch(`${domain}/url?categories=${category}&page=${page}`)
const stories = yield response.json();
yield all(stories.map(story => {
return call(fetchFeaturedImage, `${url][0]}`, story)
}))
const normalizedData = normalize(stories, articleListSchema);
yield put(receiveArticles(category, normalizedData))
}
catch (err) {
console.log('error fetching articles in saga', err)
}
}
Currently I am reading the redux state from the components that call the saga and passing the page in to the saga as the payload; but I am thinking this might get messy as I will eventually have to read the state to get the page from multiple different components that want to call this saga, so I am wondering if using sagas select() method might be the correct approach to read the redux state and get the page directly from within the saga so I don't have to worry about passing it?
Any opinions on how to correctly structure this?
I have a state with multiple objects inside it. Right now I can modify all the elements from the first level, like the step property. But I want to know how to modify the elements from an object inside of my state.
Initial state
const initialState = {
step : 1,
user: {
identification : {
email: '',
username: '',
},
localization: {
address: '',
country: '',
}}
My payload is this object:
identification : {
email: "some#html.com"
username: "s032190" }
And my rootReducer is this:
function rootReducer(state = initialState, action) {
switch (action.type) {
case ADD_USER:
return { ...state, user: action.payload }
case CHANGE_STEP:
return { ...state, step: action.payload }
case ADD_FIELD:
Object.keys(state.altaUsuario).forEach (cat => {
return { ...state.user, [cat] : action.payload}
})
default:
return state
}
};
Is it fine to use a loop? I tried with the map or the foreach, nothing works.
I also tried to call property in the spread operator, something like this:
return { ...state, altaUsuario[cat] : action.payload}
But it gives me a syntax error.
I can also modify the payload if is necessary.
Any idea??
You can try something like this
return { ...state,user : {...state.user ,identification :action.payload }}
if your action.payload is
{
identification : {
email: "some#html.com"
username: "s032190"
}
}
you have to override the identification property
return { ...state, user: { ...state.user, identification: action.payload.identification}
I am using redux for my application's state but I find it hard to update the state correctly when it is nested.
As you can see I use the ...state in my reducer but is there a way to use this when I only need to update a key of a child object and keep the rest of the state ? See below for example
// initial state
state = {
isFetching: true,
isUpdating: false,
content: {
key: 'content',
otherkey: 'content' // Keep this one
}
}
// returned new state
{
...state,
content: {
key: 'thing I only want to update'
}
}
Actions
export function requestUser() {
return {
type: REQUEST_USER
};
}
export function receiveUser(data) {
return {
type: RECEIVE_USER,
payload: data
};
}
export function fetchUser(userhash) {
return function (dispatch) {
dispatch(requestUser);
return new Promise(resolve => setTimeout(resolve, 500)).then(() => {
const data = {
status: 200,
data: {
firstName: 'Neal',
lastName: 'Van der Valk',
email: 'email#outlook.com',
hash: 'zea7744e47747851',
permissions: {
'admin': true,
'results': true,
'database': true,
'download': true,
'property': true,
'departments': true,
'users': true,
'devices': true,
'integrations': true,
},
notifications: {
'daily': true,
'weekly': false
}
}
};
dispatch(receiveUser(data));
});
};
}
Reducer
const INITIAL_STATE = {
isFetching: true,
isUpdating: false,
content: null
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case REQUEST_USER:
return {
...state,
isFetching: true
};
case RECEIVE_USER:
return {
...state,
isFetching: false,
content: action.payload.data
};
You can try Object.assign()
This example shows the usage.
{
...state,
content: Object.assign({}, state.content, {
key: 'thing I only want to update'
}
}
You can also use the same spread operator ...
var state = {
isFetching: true,
isUpdating: false,
content: {
key: 'content',
otherkey: 'content' // Keep this one
}
};
var newState = {
...state,
content: {
...state.content,
key: 'thing I only want to update'
}
}
console.log(newState);
In your initial state, content should be {} instead of null.
Then you can change the state in your reducer with Object.assign.
example :
case RECEIVE_USER:
return{
...state,
content: Object.assign({}, state.content, {
key: 'thing I only want to update'
}