Understanding Redux and State - javascript

I've taken two courses, treehouse and one on udemy, on react/redux and just when I think to myself "hey you got this, let's do a little practice" I run into some huge bug I can't seem to diagnose.
What I'm trying to do here sounds very simple, and in plain javascript it works. My state is an empty object state = {} and when my action is called, it creates an array inside of state noteName. So at the end of the day state should look like state = { noteName: [ ...state.noteName, action.payload]}.
When I console.log(this.props.inputvalue) it will return whatever is in the input element. I thought I understood objects because that consolelog should return the array noteName and not the actual value, correct?
Code
actions/index.js
export const INPUT_VALUE = 'INPUT_VALUE';
export function addNoteAction(text) {
return {
type: INPUT_VALUE,
payload: text
}
}
reducers/reducer_inputvalue.js
import { INPUT_VALUE } from '../actions';
// state is initialized as an empty object here
export default function(state = {}, action) {
switch (action.type) {
case INPUT_VALUE:
state.noteName = [];
// this SHOULD create an array that concats action.payload with
// whatever is already inside of state.name
return state.noteName = [...state.noteName, action.payload];
default:
return state;
}
}
noteitems.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class NoteItems extends Component {
render() {
return (
<ul>
{
this.props.inputvalue.noteName
?
this.props.inputvalue.noteName.map((note, index) => {
// this should iterate through noteName but returns undefined
return <li key={index}>{note}</li>;
})
:
<li>Nothing here</li>
}
</ul>
);
}
}
function mapStateToProps(state) {
return {
inputvalue: state.inputvalue
}
}
export default connect(mapStateToProps)(NoteItems);

This is happening because every time the action INPUT_VALUE is dispatched, you are resetting noteName. The main principle of redux is to not modify the state, but creating a new one based on the current. In your case:
const initialState = {
noteName: []
};
export default function(state = initialState, action) {
switch (action.type) {
case INPUT_VALUE: return {
noteName: [...state.noteName, action.payload]
};
default: return state;
}
}

You are overwriting state.noteName in the first line of your switch case.
switch (action.type) {
case INPUT_VALUE:
state.noteName = [];
In Redux, the point is to never overwrite a value, but to return a new value that might be a brand-new value, might be a value that is based on the old value (but still a new value... not overwriting the old), or it might be returning the old value (completely unmodified).
const counterReducer = (counter, action) => {
const options = {
[COUNTER_INCREMENT]: (counter, action) =>
({ value: counter.value + 1 }),
[COUNTER_DECREMENT]: (counter, action) =>
({ value: counter.value - 1 }),
[COUNTER_RESET]: (counter, action) =>
({ value: 0 })
};
const strategy = options[action.type];
return strategy ? strategy(counter, action) : counter;
};
At no point in that example am I modifying a value on counter. Everything is treated as read-only.

Related

Mutating Redux Store Array of Objects in ReactJS

My store consists of an array of objects as such:
const INIT_STATE = {
users:[],
contacts : []
};
And i am attempting to change mutate the store array like this:
const App = (state = INIT_STATE, action) => {
switch (action.type) {
case BLOCK_CONTACT:
state.contacts.map((contact, index) => {
if (contact._id === action.payload) {
state.contacts[index].status = "blocked;
return {
...state
};
}
return {
...state
};
})
return {
...state
};
}
}
But my store value does not change.
When i log the state value after the if statement, i get the correct state values but my state is not being updated. I think it might have something to do with my return statement but at this point i have tried different variations with no luck.
Any help will be appreciated.
Also if your app is in the start of the way, you can read this article about jotai state management (Jotai)
I figured it out. Incase anyone else that might need help on this, here is the code...
const App = (state = INIT_STATE, action) => {
switch (action.type) {
case BLOCK_CONTACT:
var list = state.contacts.map(contact => {
var blockList = {
...contact
};
if (contact._id === action.payload) {
blockList.status = 1;
}
return blockList;
})
return {
...state,
contacts: list
};
}
}

adding an object element to an immutable array(javascript) [duplicate]

How do I add elements in my array arr[] of redux state in reducer?
I am doing this-
import {ADD_ITEM} from '../Actions/UserActions'
const initialUserState = {
arr:[]
}
export default function userState(state = initialUserState, action)
{
console.log(arr);
switch (action.type)
{
case ADD_ITEM:
return {
...state,
arr: state.arr.push([action.newItem])
}
default:
return state
}
}
Two different options to add item to an array without mutation
case ADD_ITEM :
return {
...state,
arr: [...state.arr, action.newItem]
}
OR
case ADD_ITEM :
return {
...state,
arr: state.arr.concat(action.newItem)
}
push does not return the array, but the length of it (docs), so what you are doing is replacing the array with its length, losing the only reference to it that you had. Try this:
import {ADD_ITEM} from '../Actions/UserActions'
const initialUserState = {
arr:[]
}
export default function userState(state = initialUserState, action){
console.log(arr);
switch (action.type){
case ADD_ITEM :
return {
...state,
arr:[...state.arr, action.newItem]
}
default:return state
}
}
If you need to insert into a specific position in the array, you can do this:
case ADD_ITEM :
return {
...state,
arr: [
...state.arr.slice(0, action.pos),
action.newItem,
...state.arr.slice(action.pos),
],
}
Since this question gets a lot of exposure:
If you are looking for the answer to this question, there is a good chance that you are following a very outdated Redux tutorial.
The official recommendation (since 2019) is to use the official Redux Toolkit to write modern Redux code.
Among other things, that will eliminate string action constants and generate action creators for you.
It will also employ methods that allow you to just write mutating logic in your Reducers created by createReducer or createSlice, so there is no need to write immutable code in Reducers in modern Redux in the first place.
Please follow the official Redux tutorials instead of third-party tutorials to always get the most up-to-date information on good Redux practices and will also show you how to use Redux Toolkit in different common scenarios.
For comparison, in modern Redux this would look like
const userSlice = createSlice({
name: "user",
initialState: {
arr:[]
},
reducers: {
// no ACTION_TYPES, this will internally create a type "user/addItem" that you will never use by hand. You will only see it in the devTools
addItem(state, action) {
// you can use mutable logic in createSlice reducers
state.arr.push(action.payload)
}
}
})
// autogenerated action creators
export const { addItem } = slice.actions;
// and export the final reducer
export default slice.reducer;
If you want to combine two arrays, one after another then you can use
//initial state
const initialState = {
array: [],
}
...
case ADD_ARRAY :
return {
...state,
array: [...state.array, ...action.newArr],
}
//if array = [1,2,3,4]
//and newArr = [5,6,7]
//then updated array will be -> [1,2,3,4,5,6,7]
...
This Spread operator (...) iterates array element and store inside the array [ ] or spreading element in the array, what you can simply do using "for loop" or with any other loop.
I have a sample
import * as types from '../../helpers/ActionTypes';
var initialState = {
changedValues: {}
};
const quickEdit = (state = initialState, action) => {
switch (action.type) {
case types.PRODUCT_QUICKEDIT:
{
const item = action.item;
const changedValues = {
...state.changedValues,
[item.id]: item,
};
return {
...state,
loading: true,
changedValues: changedValues,
};
}
default:
{
return state;
}
}
};
export default quickEdit;
The easiest solution to nested arrays is concat():
case ADD_ITEM:
state.array = state.array.concat(action.paylod)
return state
concat() spits out an updated array without mutating the state. Simply set the array to the output of concat() and return the state.
This worked for me
//Form side
const handleSubmit = (e) => {
e.preventDefault();
let Userdata = { ...userdata, id: uuidv4() };
dispatch(setData(Userdata));
};
//Reducer side
const initialState = {
data: [],
};
export const dataReducer = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.SET_DATA:
return { ...state, data: [...state.data, action.payload] };
default:
return state;
}
};

How can I use Redux to only update one instance of a component?

I'm trying to use Redux to update my Card Component to disable and change colors on click. Redux dispatches the action fine, but it updates all Cards not just the one that was clicked. Each Card has an object associated with it that hold the word and a value. The value is the className I want to use to change the color when clicked
Component
const Card = ({ wordObj, updateClass, isDisabled, cardClass }) => {
const showColor = (e) => {
updateClass(wordObj);
console.log(cardClass)
};
return (
<button
onClick={(e) => showColor()}
disabled={isDisabled}
className={cardClass}>
{wordObj.word}
</button>
);
};
const mapStateToProps = (state) => ({
cardClass: state.game.cardClass,
});
export default connect(mapStateToProps, { updateClass })(Card);
Action
export const updateClass = (obj) => (dispatch) => {
console.log(obj)
dispatch({
type: UPDATE_CARD,
payload: obj,
});
};
Reducer
const initialState = {
words: [],
cardClass: 'card',
isDisabled: false,
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_WORDS: {
return {
...state,
words: payload,
};
}
case UPDATE_CARD:
return {
...state,
isDisabled: true,
cardClass: ['card', payload.value].join(' '),
};
default:
return state;
}
}```
All of your card components are consuming the same cardClass field in the state. When you modify it in this line:
cardClass: ['card', payload.value].join(' ')
All cards that are consuming this field have their classes updated. The same occurs to the isDisable field.
You need to create one object for each card in your state. Here is my implementation (was not tested):
const initialState = {
cards: []
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
// create a card object for each word
case SET_WORDS: {
return {
...state,
cards: payload.map(word => {
return { word: word, cardClass: "card", isDisabled: false }
})
};
}
case UPDATE_CARD:
// here i'm using the wordObj.word passed as payload
// to identify the card (i recommend to use an id field)
const cardIndex = state.cards.findIndex(card => card.word === payload.word);
// get the current card
const card = state.cards[cardIndex];
// create an updated card object obeying the immutability principle
const updatedCard = { ...card, isDisabled: true, cardClass: ['card', payload.value].join(' '), }
return {
...state,
cards: [
...state.cards.slice(0, cardIndex), // cards before
updatedCard,
...state.cards.slice(cardIndex + 1) // cards after
]
};
default:
return state;
}
}
Your mapStateToProps selects a string, but said string changes on any updateClass and that causes all your cards to update, because the selection of state.game.cardClass produces a different value, which triggers a new render for the connected component.
Maybe what you want, is something that identifies the selection, i.e. an id for each card, and select with that id in the mapStateToProps to avoid reading the change, because what's happening right now is the following:
Card A[className="card A"] == after dispatch ==> mapStateToProps => [className="card B"]
Card B[className="card A"] => dispatch('B') => mapStateToProps => [className="card B"]
B is updating the state of both A and B, and that's why the extra render occurs

Initial variable is not defined in reducer

I have a todos variable declare here:
const initialState = {
todos: []
};
export const todo = (state: RootState = initialState, action: Action) => {
switch (action.type) {
case TODO_ADD:
return {
todos: [...state.todos, action.payload.todo]
};
case TODO_TOGGLE_COMPLETE:
const todos = [...state.todos];
todos.forEach((todo: ToDo, index: number) => {
if (todo.id === action.payload.id) {
todos[index].isComplete = !todos[index].isComplete;
}
});
return {
todos
};
default:
return state;
}
};
But don't know why it always say not defined
Unfortunately, this can happen sometimes with compiled/transpiled code (in your case, code compiled by tsc). Source maps are good, but they're not perfect.
When you run into this, you may have to fall back to debugging the underlying generated JavaScript rather than using the source maps to make it seem like you're debugging your TypeScript code.

Redux doesn't re-render the components

I have a component which takes data from mapStateToProps() method. Component's code is:
handleClick = () => {
if (this.props.data.status) {
this.props.changeIpStatus(index, !this.props.data.status);
} else {
this.props.changeIpStatus(index, !this.props.data.status);
}
}
render() {
if (this.props.data.status) {
this.switchClasses = `switcher blocked`;
this.dotClasses = `dot blocked`;
} else {
this.switchClasses = `switcher`;
this.dotClasses = `dot`;
}
return (
<div className="block">
<div onClick={this.handleClick} className={this.switchClasses}>
<div className={this.dotClasses}></div>
</div>
</div>
)
}
}
My Redux connection looks like:
const mapStateToProps = (state) => ({
data: state.ipTable.data.clicks,
})
const mapDispatchToProps = (dispatch) => {
return {
changeIpStatus: (index, status) => {
return dispatch(changeIpStatus(index, status));
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(BlockSwitcher)
When I click switcher it should re-render because the data is changed. I see that the data is changed through my console log. But it doesn't invoke re-render. Why? My component have mapStateToProps with data that changing and action import is correct (checked).
UPDATE:
This is my reducer:
const initialState = {
data: {}
}
const ipReducer = (state = initialState, action) => {
switch (action.type) {
case `SET_CLICKS`:
return {
...state,
data: action.data
}
case `CHANGE_IP_STATUS`:
let newState = Object.assign({}, state);
newState.data.clicks[action.index].status = action.status;
return newState;
default: return state;
}
}
export default ipReducer;
You can use JSON.parse(JSON.stringify(...)) method but be aware of that if your state includes a non-serializable property then you lose it.
Here is an alternative approach. You can see this method more frequently.
// map the clicks, if index match return the new one with the new status
// if the index does not match just return the current click
const newClicks = state.data.clicks.map((click, index) => {
if (index !== action.index) return click;
return { ...click, status: action.status };
});
// Here, set your new state with your new clicks without mutating the original one
const newState = { ...state, data: { ...state.data, clicks: newClicks } };
return newState;
The second alternative would be like that. Without mapping all the clicks we can use Object.assign for the clicks mutation.
const newClicks = Object.assign([], state.data.clicks, {
[action.index]: { ...state.data.clicks[action.index], status: action.status }
});
const newState = { ...state, data: { ...state.data, clicks: newClicks } };
return newState;
The problem was with deep copy of an object. In JavaScrip for copying object without any reference between them we have to use, for example:
let newState = JSON.parse(JSON.stringify(state));
not this:
let newState = Object.assign({}, state); // <-- this is do not return a standalone new object. Do not use it in your reducer.
Thanks to #kind user!
P.S This is an article with examples why Object.assign() do not work in this case.

Categories

Resources