Modify only part of the state in a react-redux app - reactjs

As a practice exercise, I am writing a react-redux calculator app. My app's state is defined as:
const initialState = {
operator1: "", //first operand
operator2: "", //second operand
currentOp: "", // +, -, *, /
display:"", //the current calculator display
currentOperator:1 //which operand is being entered right now
}
currentOp holds the symbol of the operation currently being performed by the calculator, which, when entering the first operand, is empty. Therefore, when my calculator's numbers are pressed, I need to update the display, but without loosing my other state properties. I wrote my reducer like this:
import {NUMBER_PRESSED,OPERATION_PRESSED,EQUAL_PRESSED} from './actions';
const mainReducer = (state ={},action) =>
{
console.log("reducer called!");
console.log(action);
const newState = {};
//copy the operators to the new state. Only one will be changed. (Is this really necessary?)
newState.operator1 = state.operator1;
newState.operator2 = state.operator2;
switch(action.type)
{
case NUMBER_PRESSED:
if (state.currentOperator===1)
{
newState.operator1 = state.operator1 + action.payload;
newState.display= newState.operator1;
}
if(state.currentOperator===2)
{
newState.operator2 = state.operator2 + action.payload;
newState.display= newState.operator2;
}
//set the other properties of the state (Is this really necessary?)
newState.currentOperator = state.currentOperator;
newState.currentOp = state.currentOp;
console.log("The new state is:");
console.log(newState);
return newState;
case OPERATION_PRESSED:
break;
case EQUAL_PRESSED:
break;
default:
return state;
}
}
export default mainReducer;
Please note that I have not yet implemented the calculation operations, just updating the display. If I change the state variable directly, the calculator component does not update. Understandable, and this is expected behavior explained in the docs. However, it seems that I need to manually copy the entire state into a new variable so that it is preserved the next state (notice the "Is this really necessary?" comments in the code.
I have no problem copying all the app's state and returning an entirely new state object, but what happens on bigger applications with huge state trees? How is this managed? Is there a way to modify only part of the state in redux?

You can use things like the spread operator to duplicate entire objects without having to set each one manually:
const x = state.someArray.slice();
x[1] = "potato";
return {...state, someArray:x}
But to answer your concern, yes you do have to make an entire new duplicate of the state when changing it. It's not usually an issue, and doesn't take much time. If your state tree is HUGE then the solution should be splitting up that tree into separate reducers, that way you only have to duplicate and replace parts of the tree when changing the state.

1: If your state is decoupling you should use combineReducers It is recursion
2: If not, you should use es6 destructuring
3: What's more, you should consider about your state structure.(depends on your reducer code, i suggest...)
base on 2, for example
const mainReducer = (state = {},action) => {
switch(action.type) {
case NUMBER_PRESSED:
if (state.currentOperator===1) return {
...state,
operator1: state.operator1 + action.payload,
display: 'operator1'
}
if(state.currentOperator===2) return {
...state,
operator2: state.operator2 + action.payload,
display: 'operator2'
}
return state
default: return state;
}
}
If it's still huge with correctly program design....Product Design?

Related

Change another key's value using react-redux In React Native Expo

First, I am not good at English. I'm sorry.
I am creating a daily and weekly quest check app for a game, and in this app, Users can check daily quests for each character after registering the user's character. However, if I check one character's daily quest, all other characters are checked. This phenomenon continues after :
Creating a character until the app's restart and
Pressing the app's full initialization button until the app's restart
I tried for more than a week. And I found out that this appears in the reducer of redux(react-redux). However, I couldn't understand it at all with my skills, so I posted a question.
First, images is:
my imgur
And I thought you wouldn't understand it through pictures, so I prepared a
YouTube link
.
This is
how the checkbox arrangement of the two characters actually changes in the redox devtool.
I'm sorry that I haven't inserted the image yet.
const initialState = {
LoaData: {},
weekADay: '',
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'CHECKBOX_REDUX':
return changeCheckbox(state, action);
default:
return state;
};
The above code is the reducer of react-redux.
And the change Checkbox (state, action) function is as follows.
const changeCheckbox = (state, action) => {
let {
contentType, // in this question, i use 'daily' only
firstIndex, // this used dividing content
id, // this is character's unique id(=new Date) and checked character
value // i send checkbox array ex) [false, false, false].
} = action.payload;
let newState = Object.assign({}, state);
let character = newState.LoaData.characters[id];
let filteredContents = character.contents[contentType];
if(contentType === 'daily') { // for test, show only 'daily'
let weekADay = newState.weekADay; // Mon or Tue or Wed ...
for(let i = 0; i < filteredContents[weekADay][firstIndex].value.length; i++) {
filteredContents[weekADay][firstIndex].value[i] = value[i];
}
}
return newState;
}
And the bottom is console.log(action.payload)
{
"contentType": "daily",
"firstIndex": 0,
"id": "1632050917445",
"value": [ true, false, false ],
}
Through many tests, it has been found that a problem occurs in the for statement. I also confirmed that the desired character's nickname changes normally. However, in the for statement, it was confirmed that the boolean of the same index of 'the different character's value' was also changed for each iteration.
please help me
redux: 4.1.1
react-redux: 7.2.4
react: 16.13.1
expo: 42.0.1
It depends on how you creating and updating your characters in the rest of the store, but you might be sharing references for the characters objects between each other. You are mutating the store which you are not supposed to do in redux.
You call object.assign on the old state, and create newState. However, that only creates a new object for new state itself. All of its properties are still referring to the same objects as the old state.
Same with your assignments - let character = newState.LoaData.characters[id]; isn’t actually creating a new object at any point. You need to use object.assign for that, all the way down to the property you are changing, or use the spread operator.

Need to run .slice on React Redux Reducer state

I have an application which is using React, Redux, and Sagas. I have a reducer which has a state that is an array of objects. I want this reducer to handle a certain action by removing the first item of the array. I understand that state needs to be immutable and therefor I cannot simply call .shift.
Here is what I am trying to do now:
const flashcards = (state = [], action) => {
switch (action.type) {
case 'MAKE_FLASHCARD':
console.log(action.payload)
return action.payload;
case 'UPDATE_FLASHCARD_ARRAY':
return ({
...state.slice(1,state.length)
})
default:
return state;
}
};
export default flashcards;
The ...state.slice(1,state.length) works the first time UPDATE_FLASHCARD_ARRAY is called. However it stops working on future attempts. I discovered this is because state is not actually removing the first index with my slice method, rather it is setting the values of the first index equal to null.
Here is a stringify of the Redux stat to help illustrate this.
Prior to calling UPDATE_FLASHCARD_ARRAY :
[{"id":7,"account_id":1,"native_word":"pig","translation":"gris"},{"id":3,"account_id":1,"native_word":"cow","translation":"ku"},{"id":1,"account_id":1,"native_word":"cheese","translation":"ost"},{"id":2,"account_id":1,"native_word":"milk","translation":"melk"},{"id":8,"account_id":1,"native_word":"spider","translation":"ederkopp"}]
After calling UPDATE_FLASHCARD_ARRAY :
{"0":{"id":3,"account_id":1,"native_word":"cow","translation":"ku"},"1":{"id":1,"account_id":1,"native_word":"cheese","translation":"ost"},"2":{"id":2,"account_id":1,"native_word":"milk","translation":"melk"},"3":{"id":8,"account_id":1,"native_word":"spider","translation":"ederkopp"}}
The slice method is clearing returning a state different than the original. Could someone point out what I am doing wrong here? All I want to do is remove the first object from the state array every time UPDATE_FLASHCARD_ARRAY is dispatched.
.slice already returns a new array you don't need to spread it, you're also spreading it into an object that's why you see {"0":...:
case 'UPDATE_FLASHCARD_ARRAY':
return state.slice(1,state.length)
Have you tried to do return state.slice(1) instead return ({...state.slice(1,state.length)})? The latter creates an object, which has same keys as Array, but not actually as Array:
Array.isArray([1, 2, 3]) === true
Array.isArray({...[1, 2, 3]}) === false
({...[1, 2, 3]}).slice(1) -> TypeError

Redux key based array not triggering new props when added to 2nd time

I am dealing with Proposals and locations.
A location can have multiple proposals.
The component is passed a location object (below as passedProps) to show all the proposals for this location :
<Proposals location={ location } key={location.id}/>
Here is are my redux props :
const mapStateToProps = (state , passedProps) => {
return {
proposals : state.propertyProposals[passedProps.location.id]
};
};
when adding a new proposal, I want to store their ids by location, so in a addition to storing the proposal, I am storing their Ids, in an array keyed by location Id.
The first proposal added and refreshed with new props just fine, however even though the second one is successfully pushed to the array, this is not triggering new props, so it does not "refresh" -- If I leave the route and come back I see the new 2nd proposal (which did not show the first time)
Here is the PROPOSAL_CREATE action for a new Proposal.
type :'PROPOSAL_CREATE',
payload :
{'e7ef104e-19ed-acc8-7db5-8f13839faae3' : {
id : 'e7ef104e-19ed-acc8-7db5-8f13839faae3',
locationId: '41e9c5d8-a520-7e3b-939a-12f784d49712'
}
}
here is the case which handles it :
case 'PROPOSAL_CREATE':
{
const proposal = Object.values(action.payload)[0];
const locationId = proposal.locationId;
let newState = {...state}
if (locationId in newState) {
newState[locationId].push(proposal.id)
} else {
newState[locationId] = [proposal.id]
}
return newState
}
Is there an obvious reason I am not seeing the change in the component for the second entry?
Thanks
There is one issue here. Your store state is not immutable. You have used below line to make a copy:
let newState = {...state}
Here it does make copy of object but it's shallow copy, hence your array object in newState and state have the same reference. That's why redux doesn't identify the change in store and hence props are not updated in sequence.
You can clone your state by below methods:
let newState = JSON.parse(JSON.stringify(state));
OR if you use jQuery then:
let newState = $.extend(true, {}, state);
I think this will surely fix your issue.
Based on your reducer logic i think that you did not specify action type.
The one of the redux conventions is the action recognition based on type property.
I bet that you forgot to specify that property.
var properAction = {
type: 'PROPOSAL_CREATE',
payload: {
{
'e7ef104e-19ed-acc8-7db5-8f13839faae3': {
id: 'e7ef104e-19ed-acc8-7db5-8f13839faae3',
locationId: '41e9c5d8-a520-7e3b-939a-12f784d49712'
}
}
}
I would recommend you to write action creators it will reduce your place for typos like that.
Cheers!
2 things:
I forgot that Arrays of an original object are still by reference. So even after
let newState = {...state}
newState[locationId]
has the same reference as
state[locationId]
As a result my original statement was mutating the original state, not creating a newState
and so
newState[locationId].push(proposal.id)
needed to be
newState[locationId] = state[locationId].concat(proposal.id);
or es6
newState[locationId] = [ ...state[locationId] , proposal.id ] ;

Using spread operator from es6 in react-redux app with stage-2 loader

I am using ES6 spread operator in my react-redux app like this way
var defaultState = {val:1, items:[]};
export default function(state=defaultState, action){
switch(action.type){
case 'ADD_BOOK':
state.val = 0;
return {
...state,
items:action.payload.data.result
};
The problem is each time I get fresh data from action.payload.data.result assigned to items. I want to have concatenated data from previous state. Can someone help.
This should work.
return {
...state,
items: [
...state.items,
...aciton.payload.data.result
]
}
so what you really need to do here, is concat the existing items and those returned from your payload.
Assuming this is the case, then you need to create a new array from the old items array and new. You also want to avoid mutating the existing state.
Return a completely new state from your reducer. Do this by cloning the existing state and adding your new items array:
case 'ADD_BOOK':
var newState = Object.assign({}, state, {val:0}); // clones the existing state and adds our new val
newState.items = newState.Items.concat(action.payload.data.result);
return newState;
There are cleaner ways to do this with the spread syntax or libraries such as immutable but this example will explicitly achieve what we need to do.

redux state updating incorrectly

I was trying to study the redux flow with an example. But got stuck upon in between. Here is the plunkr link for the same.
function combineReducers(currentState, action) {
var nextState = Object.assign({}, currentState);
/*On load placeholder details for all thumbnails*/
var placeholder = {
urlPath: "http://placehold.it/640x480",
header: "PLACEHOLDER",
description: "Description text for the above image"
};
if (currentState === undefined) {
nextState = placeholder;
return nextState;
}
//Problem here i guess
nextState = {
animals : animalReducer(nextState.animals, action),
architecture : architectureReducer(nextState.architecture, action)
}
return nextState;
}
The application loads with an initial state of setting all media elements to a placeholder. ( That is working )
On individual button click, it was supposed to fetch details of each category and only populate those media element.
Problem:
When i click the Go button, both 1 and 2 elements is updating
together. Ideally i was expecting to get only Animal details on
clicking element 1, Architecture details on element 2, Nature on 3 and
People on 4.
I have not implemented 3 and 4 as i am sure if this works, then it will be more of just adding additional actions and reducers for each piece of state.
I think the problem lies in, rootReducer.js line 19, or index.js, Line 34 or 37, but not sure how to proceed! Any pointers will be of great help! Ive already pulled off a hell lot of hairs on my head today!
PS: I know doing in jquery is kind of crud, but just for learning purpose.!
Advanced thanks for the helpers!
Cheers.
https://plnkr.co/edit/WDyQHy5tftm2EX6AFQ9j?p=preview
var defaultState = {
animals: Object.assign({}, placeholder),
architecture: Object.assign({}, placeholder)
};
if (currentState === undefined) {
nextState = defaultState;
return nextState;
}
nextState = {
animals : animalReducer(nextState.animals, action),
architecture : architectureReducer(nextState.architecture, action)
}
The reducers were not returning the original state in the default case
Default state format and the combined reducer state format were different
In both animalReducer and architectureReducer you need to return currentState in the default: case, otherwise you'd null the other part each time something changes. nextState is not defined in default:.
A reducer by default does not do anything to the state, it has to keep it unchanged. Only if there is a matching action, it should create a new object with the updated state. The thing here is that you do not adher to that rule and by chance null the state by default.

Resources