hoping this is a easy question but I can't seem to figure it out.
I'm attempting to append a key to a object that is held in state. This key and value pair don't exist prior which I think is whats giving me the problem. Anyways here's what I have so far:
const Reducer = (state, action) => {
switch (action.type) {
case "SET_STUDENT_TAGS":
const userID = action.payload.id;
let studentIndex = state.originalStudentList.students.findIndex(obj => obj.id === action.payload.id);
return {
...state,
originalStudentList: {
...state.originalStudentList,
students: {
[studentIndex]: {
tags: "test"
}
}
}
};
Now the issue i'm running into is that I can run findIndex once but any attempt after just crashes my app saying its not a function. Simiarly the set state logic in the reducer there doesn't work either as it can't find "tags". So I guess i'm at a loss of how to do this. Here's how the data that is getting fed in looks:
{
"students": [
{
"id": "1",
},
{
"id": "2",
}
]
}
here's the function from the input box:
const addTag = (event, student) => {
if (event.key === "Enter") {
if (event.target.value != "") {
dispatch({
type: "SET_STUDENT_TAGS",
payload: {
id: student.id,
value: event.target.value,
},
});
event.target.value = "";
} else {
return;
}
}
};
what I'd like to achieve:
{
"students": [
{
"id": "1",
"tags": ["cat", "dog"]
},
{
"id": "2",
"tags": ["cat", "parrot"]
}
]
}
So i'm at a lost as to how to do this correctly. Appreciate any help!
You're on the right track, but that syntax replaces the array with a regular object, which is why findIndex fails after the first time. For more info, see this answer.
And then to add a property to the specific student, you just replace it with an object spreading the original and adding the tags:
const Reducer = (state, action) => {
switch (action.type) {
case "SET_STUDENT_TAGS":
const userID = action.payload.id;
let studentIndex = state.originalStudentList.students.findIndex(obj => obj.id === action.payload.id);
let student = state.originalStudentList.students[studentIndex];
return {
...state,
originalStudentList: {
...state.originalStudentList,
students: [
state.originalStudentList.students.slice(0, studentIndex),
{
...student,
tags: action.payload.tags
},
state.originalStudentList.students.slice(studentIndex+1)
]
}
};
Related
In my reducer, I have the initial state which looks like this:
const initialState = {
isLoading: false,
events: [
{
year: 2021,
place: [
{
id: 1,
name: "BD"
},
{
id: 2,
name: "BD Test"
}
]
},
{ year: 2020, place: [{ id: 3, name: "AMS" }, { id: 4, name: "AMS TEST" }] }
]
};
I have been trying to implement the functionality of deletion operation. So, when the button will be clicked the "deleteItems" action will be dispatched that will remove the corresponding items from the place. This functionality works fine. But,I am trying to remove the whole items from the events array if there is no values in place.
This is what I have tried already but it just removes the individual place. But, I need to write the logic here of removing the whole items when place becomes empty.
case "deleteItems":
return {
...state,
events: state.events.map(event => {
const place = event.place.find(x => x.id === action.id);
if (place) {
return {
...event,
place: event.place.filter(x => x.id !== action.id)
};
}
return event;
})
};
So, after modifications, the state would look like this:(when there is no values in place for year 2021)
const initialState = {
isLoading: false,
events: [
{ year: 2020, place: [{ id: 3, name: "AMS" }, { id: 4, name: "AMS TEST" }] }
]
};
Does anybody know how to accomplish this. Any helps would be highly appreciated.Thanks in Advance.
Demo can be seen from here
I removed the places first.
Then I filtered events based on whether the place array is empty or not.
After that, I returned the state.
case "deleteItems":
const eventsPostDeletingPlaces = state.events.map(event => {
const place = event.place.find(x => x.id === action.id);
if (place) {
return {
...event,
place: event.place.filter(x => x.id !== action.id)
};
}
return event;
});
const eventsWithPlaces = eventsPostDeletingPlaces.filter((each) => each.place.length);
return {
...state,
events: eventsWithPlaces
}
Check the edited sandbox here
Basically the same logic as in the first answer, but with reduce instead of a map and an extra filter. Just an option.
case "deleteItems":
return {
...state,
events: state.events.reduce((events, event) => {
const place = event.place.find(x => x.id === action.id);
if (place) {
event.place = event.place.filter(x => x.id !== action.id);
}
if (event.place.length > 0) {
events.push(event);
}
return events;
}, [])
};
codesandbox
I am trying to update the roll property in an object which is nested in the players array.
My state looks like this:
players: [
{
"id": "44ufazeep0o",
"name": "test-player-1",
"roll": 0,
"totalWins": 0
},
{
"id": "eu8hutex7z9",
"name": "test-player-2",
"roll": 0,
"totalWins": 0
}
]
This is my action:
export const addRoll = (roll, id) => {
return {
type: ADD_ROLL,
roll,
id,
}
}
This is my reducer:
case ADD_ROLL:
return state.players.map((player) => {
if (player.id === action.id) {
return {
...player,
roll: action.roll,
}
}
return player;
});
...
I am watching state.players in an components like so:
import { connect } from 'pwa-helpers'; // connects a Custom Element base class to the Redux store
...
stateChanged(state) {
this.players = state.players;
}
render() {
return html`${this.players.map((player)=> html`<h1>${player.name}</h1>`)}`
}
Now, whenever I call store.dispatch(addRoll(this.roll, this.id)), this.players.map() returns undefined in the component where I am watching state.players.
Any input on why this might happen is much appreciated!
You have return an array instead of an object with players key in it from state after ADD_ROLL action is dispatched. Correct way to update it would be
case ADD_ROLL:
return {
...state,
players: state.players.map((player) => {
if (player.id === action.id) {
return {
...player,
roll: action.roll,
}
}
return player;
});
}
...
I’ve got a very deeply nested object in my React state. The aim is to change a value from a child node. The path to what node should be updated is already solved, and I use helper variables to access this path within my setState.
Anyway, I really struggle to do setState within this nested beast. I abstracted this problem in a codepen:
https://codesandbox.io/s/dazzling-villani-ddci9
In this example I want to change the child’s changed property of the child having the id def1234.
As mentioned the path is given: Fixed Path values: Members, skills and variable Path values: Unique Key 1 (coming from const currentGroupKey and both Array position in the data coming from const path
This is my state object:
constructor(props) {
super(props);
this.state = {
group:
{
"Unique Key 1": {
"Members": [
{
"name": "Jack",
"id": "1234",
"skills": [
{
"name": "programming",
"id": "13371234",
"changed": "2019-08-28T19:25:46+02:00"
},
{
"name": "writing",
"id": "abc1234",
"changed": "2019-08-28T19:25:46+02:00"
}
]
},
{
"name": "Black",
"id": "5678",
"skills": [
{
"name": "programming",
"id": "14771234",
"changed": "2019-08-28T19:25:46+02:00"
},
{
"name": "writing",
"id": "def1234",
"changed": "2019-08-28T19:25:46+02:00"
}
]
}
]
}
}
};
}
handleClick = () => {
const currentGroupKey = 'Unique Key 1';
const path = [1, 1];
// full path: [currentGroupKey, 'Members', path[0], 'skills', path[1]]
// result in: { name: "writing", id: "def1234", changed: "2019-08-28T19:25:46+02:00" }
// so far my approach (not working) also its objects only should be [] for arrays
this.setState(prevState => ({
group: {
...prevState.group,
[currentGroupKey]: {
...prevState.group[currentGroupKey],
Members: {
...prevState.group[currentGroupKey].Members,
[path[0]]: {
...prevState.group[currentGroupKey].Members[path[0]],
skills: {
...prevState.group[currentGroupKey].Members[path[0]].skills,
[path[1]]: {
...prevState.group[currentGroupKey].Members[path[0]].skills[
path[1]
],
changed: 'just now',
},
},
},
},
},
},
}));
};
render() {
return (
<div>
<p>{this.state.group}</p>
<button onClick={this.handleClick}>Change Time</button>
</div>
);
}
I would appreciate any help. I’m in struggle for 2 days already :/
Before using new dependencies and having to learn them you could write a helper function to deal with updating deeply nested values.
I use the following helper:
//helper to safely get properties
// get({hi},['hi','doesNotExist'],defaultValue)
const get = (object, path, defaultValue) => {
const recur = (object, path) => {
if (object === undefined) {
return defaultValue;
}
if (path.length === 0) {
return object;
}
return recur(object[path[0]], path.slice(1));
};
return recur(object, path);
};
//returns new state passing get(state,statePath) to modifier
const reduceStatePath = (
state,
statePath,
modifier
) => {
const recur = (result, path) => {
const key = path[0];
if (path.length === 0) {
return modifier(get(state, statePath));
}
return Array.isArray(result)
? result.map((item, index) =>
index === Number(key)
? recur(item, path.slice(1))
: item
)
: {
...result,
[key]: recur(result[key], path.slice(1)),
};
};
const newState = recur(state, statePath);
return get(state, statePath) === get(newState, statePath)
? state
: newState;
};
//use example
const state = {
one: [
{ two: 22 },
{
three: {
four: 22,
},
},
],
};
const newState = reduceStatePath(
state,
//pass state.one[1],three.four to modifier function
['one', 1, 'three', 'four'],
//gets state.one[1].three.four and sets it in the
//new state with the return value
i => i + 1 // add one to state.one[0].three.four
);
console.log('new state', newState.one[1].three.four);
console.log('old state', state.one[1].three.four);
console.log(
'other keys are same:',
state.one[0] === newState.one[0]
);
If you need to update a deeply nested property inside of your state, you could use something like the set function from lodash, for example:
import set from 'lodash/set'
// ...
handleClick = () => {
const currentGroupKey = 'Unique Key';
const path = [1, 1];
let nextState = {...this.state}
// as rightly pointed by #HMR in the comments,
// use an array instead of string interpolation
// for a safer approach
set(
nextState,
["group", currentGroupKey, "Members", path[0], "skills", path[1], "changed"],
"just now"
);
this.setState(nextState)
}
This does the trick, but since set mutates the original object, make sure to make a copy with the object spread technique.
Also, in your CodeSandbox example, you set the group property inside of your state to a string. Make sure you take that JSON string and construct a proper JavaScript object with it so that you can use it in your state.
constructor(props) {
super(props)
this.setState = { group: JSON.parse(myState) }
}
Here's a working example:
CodeSandbox
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!
How do i write this inside of an reducer to change the state?
doc = {
id:"zf123ada123ad",
name:"examp",
subdoc:{
name:"subdoc examp",
subsubdoc:[{
id:"zcgsdf123zaar21",
subsubsubdoc:[{
id:"af2317bh123",
value: "heyhey" //this value I want to update
}]
}]
}
}
let's say i have an reducer that looks like this
The action.payload look something like this
{
theInputId1: "someId",
theInputId2: "anotherId",
theInputValue: "someValue"
}
export function updateSubSubSubDoc(state = {}, action){
switch(action.type){
case 'UPDATE_THE_SUBSUBSUB':
return {
state.doc.subdoc.subsubdoc.find(x => x.id ==
theInputId1).subsubsubdoc.find(x => x.id == theInputId2).value = theInputValue // just example code for you to understand where i'm going.
}
default:
return state
}
}
What I want to do it update one subsubsub doc in a state that is current
With ES6, this is one way that you could do that:
const initialState = { doc: { subdoc: { subsubdoc: {} } } };
export function doc(state = initialState, action) {
switch (action.type) {
case 'UPDATE_THE_SUBSUBSUB':
const subsubdocIdx = state.doc.subdoc.
subsubdoc.find(s => s.id == action.theInputId1);
const subsubdoc = state.doc.subdoc.subsubdoc[subsubdocIdx];
const subsubsubdocIdx = state.doc.subdoc.
subsubdoc[subsubdocIdx].
subsubsubdoc.find(s => s.id == action.theInputId2);
const subsubsubdoc = state.doc.subdoc.
subsubdoc[subsubdocIdx].
subsubsubdoc[subsubsubdocIdx];
return {
...state,
doc: {
...state.doc,
subdoc: {
...state.doc.subdoc,
subsubdoc: [
...state.doc.subdoc.subsubdoc.slice(0, subsubdocIdx),
{
...subsubdoc,
subsubsubdoc: [
...subsubdoc.slice(0, subsubsubdocIdx),
{
...subsubsubdoc,
value: action.theInputValue,
},
...subsubdoc.subsubsubdoc.slice(subsubsubdocIdx + 1, subsubdoc.subsubsubdoc.length - 1),
],
},
...state.doc.subdoc.subsubdoc.slice(subsubdocIdx + 1, state.doc.subdoc.subsubdoc.length - 1),
]
}
}
}
default:
return state;
}
}
(I haven’t tested this code.)
This is nested the same level as in your example, but you might consider using something like combineReducers to make this a little easier to manage. This is also presupposing you have other actions that create the document chain along the way, and you know these documents exist.
Here's an example how you might be able to do it with combineReducers:
function doc(state = {}, action) {
}
function subdoc(state = {}, action) {
}
function subsubdoc(state = [], action) {
}
function subsubsubdoc(state = [], action) {
switch (action.type) {
case 'UPDATE_THE_SUBSUBSUB':
const idx = state.find(s => s.id == action.theInputId2);
return [
...state.slice(0, idx),
{
...state[idx],
value: action.theInputValue,
},
...state.slice(idx + 1),
];
default:
return state;
}
}
export default combineReducers({
doc,
subdoc,
subsubdoc,
subsubsubdoc,
});
In this example, you don't need action.theInputId1, but you would need to store some reference in the data from the subsubdoc to the subsubsubdoc so that when you're rendering, you can piece it back together. Same with all of the other layers.