i have an object in redux state like this . the keys are actually the question ids and the values are answer ids.
userProgress: [
{
8: '2207',
12: '28',
38 : '42'
}
]
Now i want to send new values like
dispatch(setUserProgress({
12: '2800'
}))
it should find the value and update accordingly and if not found, it will add it at bottom.
Desired Result :
userProgress: [
{
8: '2207',
12: '2800',
38 : '42'
}
]
Assuming that your action is {type:'the type',payload:{12:'2800'}} then you can do the following:
return {...state, userProgress: {...state.userProgress,...payload}}
const reducer = (state, { type, payload }) => {
if (type === 'the type') {
return {
...state,
userProgress: { ...state.userProgress, ...payload },
};
}
return state;
};
console.log(
'add new answer at the end',
reducer(
{ userProgress: { a: 'a' } },
{ type: 'the type', payload: { newValue: 'newValue' } }
)
);
console.log(
'change existing value',
reducer(
{ userProgress: { b: 'b' } },
{ type: 'the type', payload: { b: 'newValue' } }
)
);
Related
Here are the instructions for what I am supposed to do:
Implement a function accepting 2 arguments: state and actions. The
function should change the state basing on the given actions
array.
state is an object. You are supposed to add, change, or delete its properties instead of creating a new object
actions is an array of objects. Each object in this array has the next properties:
type contains a string: either 'addProperties', 'removeProperties' or 'clear';
The second property of each object depends on type and may be one of the following:
if type is addProperties, second property is extraData. It contains an object
with key: value pairs to add to the state;
if type is removeProperties, second property is keysToRemove. It contains an array
with the list of property names (keys) to remove from the state; (Not existing
properties should be ignored)
if type is clear you should remove all the properties from the
state. No second property in this case;
Example of usage:
If state is {foo: 'bar', bar: 'foo'}, then
transformState(state, [
{
type: 'addProperties',
extraData: {
name: 'Jim',
hello: 'world',
}
},
{
type: 'removeProperties',
keysToRemove: ['bar', 'hello'],
},
{
type: 'addProperties',
extraData: { another: 'one' },
}
])
should modify the state, doing the following:
add two properties to the state
then remove keys bar and hello from the state
and finally add another one property to the state
After these operations the object state will have the following look
{
foo: 'bar',
name: 'Jim',
another: 'one',
}
Another example:
const state = { x: 1 };
transformState(state, [
{ type: 'addProperties', extraData: { yet: 'another property' } }
{ type: 'clear' },
{ type: 'addProperties', extraData: { foo: 'bar', name: 'Jim' } }
]);
state === { foo: 'bar', name: 'Jim' }
Here is my code so far:
function transformState(state, actions) {
const transformedState = state;
for (const { type } of actions) {
if (type === 'clear') {
Object.keys(transformedState)
.forEach(key => delete transformedState[key]);
} else if (type === 'addProperties') {
for (const { extraData } of actions) {
for (const data in extraData) {
transformedState[data] = extraData[data];
}
}
} else if (type === 'removeProperties') {
for (const { keysToRemove } of actions) {
for (const item in keysToRemove) {
delete transformedState[keysToRemove[item]];
}
}
}
}
return transformedState;
}
It works with almost all scenarios, but it doesn't remove some properties and I can not figure out why. The 2 test it won't pass:
test('Should apply several types', () => {
const state = {
foo: 'bar', bar: 'foo',
};
transformState(state, [
{
type: 'addProperties',
extraData: {
name: 'Jim', hello: 'world',
},
},
{
type: 'removeProperties', keysToRemove: ['bar', 'hello'],
},
{
type: 'addProperties', extraData: { another: 'one' },
},
]);
expect(state)
.toEqual({
foo: 'bar', name: 'Jim', another: 'one',
});
});
test('Should work with a long list of types', () => {
const state = {
foo: 'bar', name: 'Jim', another: 'one',
};
transformState(state, [
{
type: 'removeProperties', keysToRemove: ['another'],
},
{ type: 'clear' },
{ type: 'clear' },
{ type: 'clear' },
{
type: 'addProperties', extraData: { yet: 'another property' },
},
{ type: 'clear' },
{
type: 'addProperties',
extraData: {
foo: 'bar', name: 'Jim',
},
},
{
type: 'removeProperties', keysToRemove: ['name', 'hello'],
},
]);
expect(state)
.toEqual({ foo: 'bar' });
});
In the first test in the add properties section, if I change the order of "hello" and "name", it still only removes name and I can't figure out why. Here is the test output:
● Should apply several types
expect(received).toEqual(expected) // deep equality
- Expected
+ Received
Object {
"another": "one",
"foo": "bar",
- "name": "Jim",
+ "hello": "world",
}
196 |
197 | expect(state)
> 198 | .toEqual({
| ^
199 | foo: 'bar', name: 'Jim', another: 'one',
200 | });
201 | });
at Object.<anonymous> (src/transformState.test.js:198:6)
● Should work with a long list of types
expect(received).toEqual(expected) // deep equality
- Expected
+ Received
Object {
"foo": "bar",
+ "yet": "another property",
}
229 |
230 | expect(state)
> 231 | .toEqual({ foo: 'bar' });
| ^
232 | });
233 |
at Object.<anonymous> (src/transformState.test.js:231:6)
This is my reducer file in react
const initialState = {
products: [
{
name: 'Icecream',
inCart: false,
num: 2
},
{
name: 'Cake',
inCart: true,
num: 5
}
]
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case REVERSE:
let newProd = [ ...state.products ];
newProd.filter((a) => {
if (a.inCart === true) {
return a;
}
});
console.log(newProd);
return {
...state,
products: newProd
};
default:
return state;
}
};
and this is the console log of newProd which shows the filter function doesn't work
0: {name: "Icecream", inCart: false, num: 2}
1: {name: "Cake", inCart: true, num: 5}
length: 2
__proto__: Array(0)
How can I filter the products array so I can only get the item that has inCart = true and replace it with the old products array ?
Filter returns a new array & won't mutate the original one. You are filtering but not assigning the output of filter to a variable.
Do this
export const reducer = (state = initialState, action) => {
switch (action.type) {
case REVERSE:
let newProd = state.products.filter((a) => { //<---- like this
if (a.inCart === true) {
return a;
}
});
console.log(newProd);
return {
...state,
products: newProd
};
default:
return state;
}
};
You can also do it in a single liner - like this:
...
case REVERSE:
return {
...state,
products: state.products.filter(a => a.inCart)
};
...
trying to delete id:2 car from my mongodb database. using react redux, my question is how to check dispatch id equal to car id in reducer?
button
<button onClick={this.handleDeleteCar(carID : data.id)>delete</button>
data log
carview:
[
{ id: 5c7496f0def4602bf413ea9a,
parentId: 5c7496f0def4602bf413ea97,
car: [
{
id: 2,
name: bmw
},
{
id:3,
name: amg
}
]
}
]
reducer
case carConstants.car_DELETE_REQUEST:
return {...state,items: state.items.map(car =>car.id === action.id
? { ...car, deleting: true }
: car
)
};
Use Array.prototype.filter()
return {
...state,
items: state.items.filter((car) => car.id !== action.id)
}
EDIT:
In your case, you can use an Array.prototype.filter nested inside an Array.prototype.map:
return {
...state,
carView: state.carView.map((view) => {
return {
...view,
car: view.car.filter(({ id }) => id !== action.id)
}
})
}
This can become quickly messy if you deal with deeply nested objects and it's a good idead to try to keep your state normalized in redux
Above answer using Array.filter is a good one, but if that won't fit your use case you can use a nested findIndex check.
let state = [
{ id: "5c7496f0def4602bf413ea9a",
parentId: "5c7496f0def4602bf413ea97",
car: [
{
id: 4,
name: "bmw"
},
{
id:5,
name: "amg"
}
]
},
{ id: "5c7496f0def4602bf413ea9a",
parentId: "5c7496f0def4602bf413ea97",
car: [
{
id: 2,
name: "bmw"
},
{
id:3,
name: "amg"
}
]
}
];
const action = { type: 'example', id: 2 }
const index = state.findIndex(({car}) => car.findIndex(({id}) => id === action.id) !== -1);
if (index === -1) {
// you would probably return current state here realistically
throw new Error('no index');
}
console.log(index);
state = [ ...state.slice(0, index), ...state.slice(index + 1)];
console.log(state);
In my reducer called 'reducer_product_list', I have this array :
let initialState = [
{name: 'Article 1', price: 5, quantity: 10},
{name: 'Article 2', price: 15, quantity: 8},
{name: 'Article 3', price: 7, quantity: 15},
{name: 'Article 4', price: 9, quantity: 5},
{name: 'Article 5', price: 11, quantity: 100},
{name: 'Article 6', price: 23, quantity: 20},
]
When I get the action 'ADD_TO_CART', I want to decrease the quantity of the selected object. The payload is one of those objects.
I typed the code above :
export default (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
initialState.map((product, i) => {
if (product.name === action.payload.name) {
initialState[i].quantity -= 1
return state;
}
});
default: return state
}
}
If I console.log my initialState, the quantity is decreasing, but in my container that renders the view, the quantity stays the same.
Thank you for your help.
Try this:
export default (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
return state.map(product => {
if (product.name === action.payload.name) {
return {...product, quantity: product.quantity-1}
};
return product;
});
default: return state
}
}
The reason is you have to return a new state object derived from the current state reflecting the desired changes according to the requested action. See Redux Reducers for more details.
Thank you for you help, it works well!
I just had to change one thing: instead of mapping on the initialState, I mapped on the state.
export default (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
return state.map(product => {
if (product.name === action.payload.name) {
return {...product, quantity: product.quantity - 1}
};
console.log(state)
return product;
});
default: return state
}
}
I'm deleting invitations by passing their IDs to the back end, which works. However, my reducer is not working properly to re-render the new, filtered array of invitations. When I refresh the page, the deleted invitation is gone. What am I doing wrong?
The action:
export function deleteInvitation(id) {
const user = JSON.parse(localStorage.getItem('user'));
console.log('now deleting id ', id);
return function(dispatch) {
axios
.delete(`${ROOT_URL}/invitation/`, {
headers: { authorization: user.token },
params: { id: id }
})
.then(response => {
console.log(id);
dispatch({
type: DELETE_INVITATION,
id
});
});
};
}
The reducer:
export default function(state = {}, action) {
switch (action.type) {
case INVITATION_SUCCESS:
return { ...state, invited: true, error: {} };
case INVITATION_FAILURE:
return { ...state, invited: false, error: { invited: action.payload } };
case FETCH_INVITATIONS:
return { ...state, invitations: action.payload };
case DELETE_INVITATION:
return {
...state,
invitations: state.invitations.filter(_id => _id !== action.id)
};
default:
return state;
}
}
I'm making a guess about the structure of the invitations array...
In the reducer, the filter function appears to be incorrect. The action is passing an id property, which I'm guessing is a property of an invitation object. But the filter function is filtering objects from state.invitations where the object is the id. That is, the reducer is doing something like this:
const action = {id: 0}
const invitation = [{
_id: 0,
name: 'Name 0',
location: 'Location 0'
},
{
_id: 1,
name: 'Name 1',
location: 'Location 1'
},
{
_id: 2,
name: 'Name 2',
location: 'Location 2'
}
];
console.log(invitation.filter(_id => _id !== action.id));
which will return the full original array because the filter function is checking for the inequality of action.id (a number) to an invitation (an object). Basically:
{
_id: 0,
name: 'Name 0', !=== number
location: 'Location 0'
}
will return true for any num and/or any invitation object, so the filter function will return every item in state.invitations.
To correct this, check the invitation._id against the action.id, like this:
const action = {id: 0}
const invitation = [{
_id: 0,
name: 'Name 0',
location: 'Location 0'
},
{
_id: 1,
name: 'Name 1',
location: 'Location 1'
},
{
_id: 2,
name: 'Name 2',
location: 'Location 2'
}
];
console.log(invitation.filter(invitation => invitation._id !== action.id));