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;
});
}
...
Related
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)
]
}
};
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;
}
}
I have created a module for reservations like this, this is my vuex store:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const state = {
reservations: [],
stats: {
pending: 0,
confirmed: 0,
cancelled: 0
}
};
const actions = {
fetchReservations({commit}){
axios.get('/reservations').then(({data})=>{
commit('setReservations', data);
}).catch(error => {
throw new Error(error);
});
},
deleteReservation({commit}, reservationId){
axios.delete('/reservations/'+ reservationId).then(()=>{
commit('removerReservationInList', reservationId);
});
},
confirmReservation({commit}, reservationId){
axios.patch('/reservations/'+ reservationId +'/confirm').then(({data})=>{
commit('updateReservationInList', data);
});
},
cancelReservation({commit}, reservationId){
axios.patch('/reservations/'+ reservationId +'/cancel').then(({data})=>{
commit('updateReservationInList', data);
});
},
fetchReservationStats({commit}){
axios.get('/reservations/stats').then(({data})=>{
commit('setReservationsStats', data);
});
}
};
const mutations = {
setReservations(state, reservations) {
state.reservations = reservations;
},
removeReservationInList(state, reservationId){
state.reservations = state.reservations.filter((reservation)=>{
return reservation.id !== reservationId
});
},
updateReservationInList(state, data){
state.reservations = state.reservations.map(reservation => {
if (reservation.id !== data.id) {
return reservation;
}
reservation.state_id = data.state_id;
return reservation;
});
},
setReservationsStats(state, data) {
state.stats = data;
}
};
const getters = {
reservationsList(state){
return state.reservations
},
reservationsStats(state){
return state.stats;
}
};
export default new Vuex.Store({
state,
actions,
mutations,
getters
});
And those are the reservations:
[
{"id":1,"name":"Rollin Koss","email":"predovic.wyatt#example.net","state_id":2, "booking_date":"2020-12-12","number_of_guests":3},
{"id":2,"name":"Kellie Schroeder","email":"nicolette39#example.com","state_id":1,"booking_date":"2020-12-02","number_of_guests":14},
{"id":3,"name":"Autumn Goldner IV","email":"vkutch#example.org","state_id":3, "booking_date":"2020-12-15","number_of_guests":14}
.....
]
So, I get the stats in other request.
I was thinking doing it in another way, for example, when I get the reservations, return the stats like this:
[
"reservations": [
{"id":1,"name":"Rollin Koss","email":"predovic.wyatt#example.net","state_id":2, "booking_date":"2020-12-12","number_of_guests":3},
{"id":2,"name":"Kellie Schroeder","email":"nicolette39#example.com","state_id":1,"booking_date":"2020-12-02","number_of_guests":14},
{"id":3,"name":"Autumn Goldner IV","email":"vkutch#example.org","state_id":3, "booking_date":"2020-12-15","number_of_guests":14},
....
....
],
"stats": {
"pending": 50,
"confirmed": 30
"cancelled": 10
}
]
state_id = 1 is for pending reservations, state_id = 2 is for confirmed reservations, state_id = 3 is for cancelled reservations
And then for example, when I update a pending reservation to confirmed, the pending should decrease and the confirmed should increase, and if a confirmed reservation is cancelled, the stats should reflects that, also, if some reservation is deleted for example a pending, then it should decrease, I am not sure how to do it. Thank you.
Instead of storing stats as a state object, you could use a getter which will be reactive to your reservation state.
getters: {
stats(state){
return {
pending: countState(1),
confirmed: countState(2),
cancelled: countState(3),
};
function countState(stateId){
return state.reservations.reduce((acc, cur) => (cur.state_id === stateId ? ++acc : acc), 0);
}
}
EDIT: If you'd like it to reflect your current paginated reservations you could move the stats code from vuex into the component itself using a computed function, like so:
computed: {
stats: function () {
// This should point to your current set of paginated reservations
const reservations = this.$store.getters.reservations;
return {
pending: countState(1),
confirmed: countState(2),
cancelled: countState(3),
};
function countState(stateId) {
return reservations.reduce((acc, cur) => (cur.state_id === stateId ? ++acc : acc), 0);
}
},
},
I would like to know in my scenario: After receiving props, the function gets called in the compoentdidupdate method, in which am summing up the amount if id is same, But it keeps on adding the values multiple times on load. how to resolve this.
class Data extends React.PureComponent{
constructor(props){
super(props);
this.state={
total: "";
}
}
componentDidMount() {
this.callFetch();
}
callFetch(){
this.props.dispatch(getData("all"));
this.props.dispatch(newData("new"));
}
componentDidUpdate(){
const { alldata, newdata } = this.props.query;
const obj =[...alldata, ...newdata];
const result=[];
if(obj.length > 0) {
obj.forEach(function (o) {
var existing = result.filter(function (i) { return i.id=== o.id})[0];
if (!existing)
result.push(o);
else
existing.amount+= o.amount;
});
this.setState({total: result})
}
}
render(){
return(
this.state.total.length > 0 ?
this.state.total.map(e=>{
<div>price:{e.amount}</div>
}) : ""
)
}
props am receiving is below, but in output am receiving 1200, and keeps on increasing,
alldata= [{
id: 1,
amount: 200
}, {
id: 2,
amount: 400
}]
newdata= [{
id: "1",
amount: 400
}]
expected output:
price: 600
price: 400
Hey #miyavv Have a look at the below piece of code. Comparing prevProps and current props should help. It is general technique used in reactjs to solve non-needed runs in componentDidUpdate.
class Data extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
total: ""
};
}
componentDidMount() {
this.callFetch();
}
callFetch() {
this.props.dispatch(getData("all"));
this.props.dispatch(newData("new"));
}
componentDidUpdate(prevProps) {
const { alldata, newdata } = this.props.query;
const obj = [...alldata, ...newdata];
const result = [];
// new Line added
const { alldata: prevAllData, newdata: prevNewData } = prevProps.query;
if (prevAllData !== alldata || prevNewData !== newdata) {
if (obj.length > 0) {
obj.forEach(function(o) {
var existing = result.filter(function(i) {
return i.id === o.id;
})[0];
if (!existing) result.push(o);
else existing.amount += o.amount;
});
this.setState({ total: result });
}
}
}
render() {
return this.state.total.length > 0
? this.state.total.map(e => {
<div>price:{e.amount}</div>;
})
: "";
}
}
Let me know if it helps !!. Thanks
That’s because you are dispatching two actions which will result to call componentDidUpdate multiple times. And in your componentDidUpdate method there’s no conditioning telling that your data is already fetched.
You could call only one action that will further dispatch two actions you are dispatching in callFetch method. You could also keep a flag which will tell whether the data is yet fetched or not.
If you are using redux-thunk, than it can be easily achieved. You can define one action which will be dispatched in callFetch method. And in your newly defined action you can dispatch your mentioned two actions.
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.