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 }],
};
Related
I have a question I am making React app. The thing is that in useEffect I loop through six items every time when only one thing changes. How to solve it to change only one variable which was changed in reducer function not looping for 6 items when only one was changed, or is it okay to keep code like this?
const initialReducerValue = {
name: {
val: '',
isValid: false,
},
lastName: {
vaL: '',
isValid: false
},
phoneNumber: {
val: '',
isValid: false
},
city: {
val: '',
isValid: false,
},
street: {
val: '',
isValid: false
},
postal: {
val: '',
isValid: false
},
}
const OrderForm = () => {
const orderReducer = (state, action) => {
if (action.type === 'HANDLE TEXT CHANGE') {
return {
...state,
[action.field]: {
val: action.payload,
isValid: true
}
}
}
}
const [formState, formDispatch] = useReducer(orderReducer, initialReducerValue)
const [formIsValid, setFormIsValid] = useState(false)
const changeTextHandler = (e) => {
formDispatch({
type: 'HANDLE TEXT CHANGE',
field: e.target.name,
payload: e.target.value
})
}
useEffect(() => {
const validationArray = []
for (const key of Object.keys(formState)) {
validationArray.push(formState[key].isValid)
}
const isTrue = validationArray.every(item => item)
setFormIsValid(isTrue)
}, [formState])
This code
const validationArray = []
for (const key of Object.keys(formState)) {
validationArray.push(formState[key].isValid)
}
const isTrue = validationArray.every(item => item)
is equivalent to
const isTrue = Object.values(formState).every(item => item.isValid);
This still iterates over all items when only one was changed, but with a temporary array less.
For six items, I would not spend time trying to optimize this code further, but that's your choice.
I have a state with the following structure. It contains a list of Workouts and each workout has a list of exercises related to this workout.
I want to be able to do 2 things:
add new exercises to the specific workout from the list of workouts
delete a specific exercise from the specific workout
E.g. In my UI I can add new exercises to Workout with the name Day 2.
So my action payload gets 2 params: workout index (so I can find it later in the state) and exercise that should be added to/deleted from the list of exercises of the specific workout.
State
state = {
workouts: [
{
name: "Day 1",
completed: false,
exercises: [{
name: "push-up",
completed: false
},
{
name: "running",
completed: false
}]
},
{
name: "Day 2",
completed: false,
exercises: [{
name: "push-up",
completed: false
}]
},
{
name: "Day 3",
completed: false,
exercises: [{
name: "running",
completed: false
}]
}]
}
Actions
export class AddExercise implements Action {
readonly type = ADD_EXERCISE
constructor(public payload: {index: number, exercise: Exercise}) {}
}
export class DeleteExercise implements Action {
readonly type = DELETE_EXERCISE
constructor(public payload: {index: number, exercise: Exercise}) {}
}
And I am stuck on the reducer. Can you advise how it should be done properly?
This is how it looks right now (not finalized yet):
Reducer
export function workoutsReducer(state = initialState, action: WorkoutActions.Actions) {
switch(action.type) {
case WorkoutActions.ADD_EXERCISE:
const workout = state.workouts[action.payload.index];
const updatedExercises = [
...workout.exercises,
action.payload.exercise
]
return {
...state,
workouts: [...state.workouts, ]
}
return {};
default:
return state;
}
}
Thank you!
Please, try something like the following (I included comments within the code, I hope it makes it clear):
export function workoutsReducer(state = initialState, action: WorkoutActions.Actions) {
switch(action.type) {
case WorkoutActions.ADD_EXERCISE:
// You can take advantage of the fact that array map receives
// the index as the second argument
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
const workouts = state.workouts.map((workout, index) => {
if (index != action.payload.index) {
return workout;
}
// If it is the affected workout, add the new exercise
const exercises = [
...workout.exercises,
action.payload.exercise
]
return { ...workout, exercises }
})
// return the updated state
return {
...state,
workouts
}
case WorkoutActions.DELETE_EXERCISE:
// very similar to the previous use case
const workouts = state.workouts.map((workout, index) => {
if (index != action.payload.index) {
return workout;
}
// the new exercises array will be composed by every previous
// exercise except the provided one. I compared by name,
// I don't know if it is accurate. Please, modify it as you need to
const exercises = workout.exercises.filter((exercise) => exercise.name !== action.payload.exercise.name);
return { ...workout, exercises }
})
// return the new state
return {
...state,
workouts
}
default:
return state;
}
}
Hello i just started redux with react native i am trying to make a food delivery app. each food has options like drinks or dessert for example. I would like that if the user adds a basket an items we check if the options chosen are the same to increase the quantity if they are not the same I would like to add another item with the new option to the global state. only I tried a lot of things and none seemed to meet my expectations. the structure of the data that I pass to the global state is in this form.
cartProduct = [
{
id: itemId,
restaurantName: restaurantName,
name: itemName,
quantity: quantity,
price: price
selectedOption: [
optionId: optionId,
itemId: itemId,
name: itemName,
]
}
]
Here is the code I wrote in the reducers with the add to cart action
switch (action.type) {
case 'ADD_TO_CART':
const cartProductIndex = state.cartProduct.findIndex(item => item.id === action.value.id)
if (cartProductIndex == -1) {
nextState = {
...state,
cartProduct: [...state.cartProduct, action.value]
}
} else {
state.cartProduct[cartProductIndex].selectedOption.map(item => {
if (item.item === action.value.selectedOption.item) {
let newArray = [...state.cartProduct]
newArray[cartProductIndex] = {
...newArray[cartProductIndex],
quantity: state.cartProduct[cartProductIndex].quantity + 1,
totalPrice: state.cartProduct[cartProductIndex].totalPrice + action.value.totalPrice
}
nextState = {
...state,
cartProduct: newArray
}
} else {
nextState = {
...state,
cartProduct: [...state.cartProduct, action.value]
}
}
})
}
return nextState || state
I would do something like this.
switch (action.type) {
case 'ADD_TO_CART':
const cartProductIndex = state.cartProduct.findIndex(item => item.id === action.value.id)
if (cartProductIndex == -1) {
return {
...state,
cartProduct: [...state.cartProduct, action.value]
}
}
const newCartProduct = state.cartProduct[cartProductIndex].selectedOption.map(item => {
if (item.item === action.value.selectedOption.item) {
item.quantity++
}
return item
})
return {
...state,
cartProduct: newCartProduct
}
}
You cannot update the state in a map loop, a map returns a new array. You want to use map to create a new array, and then use that value. Once a function is complete you should return, you don't need that else.
Here is my state
const initState = [
{
id: 1,
criteria: [],
isInclusion: true,
},
{
id: 2,
criteria: [],
isInclusion: true,
},
];
I am trying to add a new object into criteria array with my dispatch
dispatch(
addCriteria({
id: item.id,
type: item.type,
groupId: 1,
})
);
in the reducer I do the following but it doesnt add to an array of an existing group but instead, it adds as a new object to the array with that criteria added in it.
const addReducer = (state = initState, action) => {
switch (action.type) {
case QUERYGROUPS.ADD_CRITERIA: {
const newCriteria = {
id: action.payload.id,
type: action.payload.type,
};
const newState = [
...state,
state.map(group =>
group.id === action.payload.groupId
? {
...group,
criteria: newCriteria,
}
: group
),
];
return newState;
}
}
};
You are adding all items to the state object (also the one you want to modify) with ...state and than you map over it again. This will not work.
You should change your state object to be an object to access the items by reference and not index and use the id as access. This will also be faster(thanks #Yash Joshi ):
const initState = {
1: {
criteria: [],
isInclusion: true,
},
2: {
criteria: [],
isInclusion: true,
},
};
This will let you access and update the state more easily and easier to stay immutable.
This will let you update it like this:
case QUERYGROUPS.ADD_CRITERIA: {
const newCriteria = {
id: action.payload.id,
type: action.payload.type,
};
const newState = {
...state,
[action.payload.groupId]: {
...state[action.payload.groupId],
criteria: [
...state[action.payload.groupId].criteria,
newCriteria
],
}
};
return newState;
To add a new item to it:
const newState = {
...state,
[action.payload.groupId]: {
isInclusion: false,
criteria: [ ],
}
};
Hope this helps. Happy coding.
Try spread operator (es6):
return [
...state,
newCriteria,
]
This will return a new array with the newCriteria object on it.
Here is how I'd do it:
const newCriteria = {
id: action.payload.id,
type: action.payload.type,
};
const newState = state.map(gr => {
if (gr.id !== action.payload.groupId) {
return gr;
}
return {
...gr,
criteria: [...gr.criteria, newCriteria];
}
});
I think you should follow Domino987 approach it will be faster. But if you still wish to continue with your approach. You can try this:
const newState = state.map(item => item.id === newItem.id ? ({ ...item, ...newItem }) : item );
return newState;
Hope this Helps!
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'
}