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

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.

Related

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

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?

Redux return a fresh object composed out of our existing state

I am trying to learn redux and stumbled upon this article from hackernoon
Everything was going great until I encountered this example
export default function reducer(state = {posts: []}, action) {
switch (action.type) {
case 'Add_USER_POST':
return {
...state,
posts: [
...state.posts,
{
content: action.payload.content,
}
]
};
default:
return state;
}
}
For which the following explanation was given..
First, we added a default property posts to our default state and
initialised it with [] . Next, we simply added a switch-case block
that switches on action.type . Because our action had a type of
Add_USER_POST it will be intercepted by the 1st case in our reducer’s
switch-case and it will return a fresh object composed out of our
existing state and add the newly added post to the posts array.
Here I am unable to understand the following lines (first and the last lines)
First, we added a default property posts to our default state and initialised it with [] .
return a fresh object composed out of our existing state and add the newly added post to the posts array
Can someone help me in understanding the above code in much more simpler terms?
[Update] got the first point, can someone explain me the working of part of code associated with second point
return {
...state,
posts: [
...state.posts,
{
content: action.payload.content,
}
]
};
Things I understood from the above code, We are returning an object which have our current state, then an array of posts which consists of things inside our state.posts and action.payload.content which we get from here
{
type: 'Add_USER_POST',
payload: {
content: 'A quick brown fox jumped over the lazy dog',
}
}
As in our article.
This reducer function takes a state and an action parameters. If no parameters are passed to the reducer it simply returns an object containing an empty array of posts.
The default property :
state = {posts: []}
Sets a default object as the first parameter of the reducer function.
The spread operator is being used to combine the previous state passed, the previous posts in that state's array and add a new post with the content passed in the action.payload.content.
DOCS
Default Parameters
Spread Operator

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 ] ;

Reducer behavior

As I understand it, when an action is called, all reducers respond. If action exists in the switch case statement of the reducer, it executes. If it doesn't, then the case: default executes which preserves the existing state.
When the action exists in the reducer but the particular property it's trying to update does not exist, it seems to behave OK as there's nothing to update.
For example, I have an action creator that is used to set the visible property of my modals. Each modal has its own Id. My code looks like this:
export default (state = initialState, action) => {
case types.SET_MODAL_IS_VISIBLE:
return Object.assign({}, state,
{ modal22: action.value }
)}
I have the SET_MODAL_IS_VISIBLE in multiple reducers but if modal22 is not defined in a particular reducer, nothing happens and no errors.
Now, I have a scenario that is throwing an error. I have a general purpose date picker component that I built that can be used as a single and independent date picker OR it can be "linked to" another one. The second scenario is useful if I need the user to give me two dates e.g. start and end dates.
I also built a feature where if the date picker is coupled with another one, when the user sets the date in the first date picker, I disable all the dates prior to that date in the second date picker because I don't want the user to unintentionally select an end date that is prior to the start date.
I define my date pickers as below:
const initialState = {
datePickers: {
"startDatePicker": {
activeDate: "8/25/2017",
disabledBefore: "",
linkedTo: "endDatePicker"
},
"endDatePicker": {
activeDate: "",
disabledBefore: "8/25/2017" // This date is set when the user sets the active date in startDatePicker
linkedTo: ""
}
}
}
This scenario is a bit interesting because a state change in one property in my reducer is triggering a state change in another. This is not difficult to do and I have a way of controlling when I do the update.
The action for setting disabled dates looks like below:
...
case types.SET_DISABLED_DATES:
return Object.assign({}, state,
datePickers: Object.assign({}, state.datePickers, {
datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
disabledBefore: action.value
})
})
Please keep in mind that I can and should be able to set disabledBefore even if the date picker is used as an independent one. So, I need my SET_DISABLED_DATES in every reducer.
The problem I'm running into is that whenever I call SET_DISABLED_DATES, I get errors in reducers where the date picker is used as a single/independent one because the date picker Id for its pair is NOT defined in the reducer.
For example, in projectsReducer I may use the date picker as part of a pair so both startDatePicker and endDatePicker are defined and everything works fine.
But I may be using a single instance date picker in the tasksReducer which also responds to the SET_DISABLED_DATES call but it fails because it cannot find the endDatePicker. In this scenario, the tasksReducer is responding to the call I made to set the disabledDates property of endDatePicker in projectsReducer.
I've posted two questions about this already and the only real solution I'm seeing here is that I need to have a condition in my reducer that looks like this:
...
case types.SET_DISABLED_DATES:
if(typeof state.datePickers[action.datePickerId] !== "undefined") { // Making sure that what I'm trying to update exists in the reducer
return Object.assign({}, state,
datePickers: Object.assign({}, state.datePickers, {
datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
disabledBefore: action.value
})
})
} else {
return state;
}
Admittedly, this looks a bit like a kludge but I couldn't really come up with another solution here.
Again, the problem is that for as long as all reducers respond to SET_DISABLED_DATES, it's guaranteed that a particular date picker will not be there and the Object.assign() will throw an error.
Any suggestions? Is the simple condition in the reducer the way to go here? Is it a kludge?
P.S. I tried this code and it works fine and fixes the problem. On the one hand, I feel this is a bit of an anti-pattern but on the other hand, it just seems like a good idea to make sure the property I want to update in my reducer exists before attempting to update it. I'd appreciate your feedback on this. Thanks.
You are just doing basic validation in the reducer before setting the state. That is perfectly fine. I don't think it will be a good practice to check the store in the action creator to prevent dispatching actions on objects not in the store (how would you do that anyway!).
What I don't understand is, how can a datepicker be linked to another datepicker that isn't in the store? Maybe dispatch a create and teardown action on the component's didMount and willUnmount?
I don't know your full requirements but I think we can make it a lot simpler. I'd do something like this:
The store:
{
datePickers: {
id1: {
value: '',
minValue: '',
maxValue: '',
},
id2: {
value: '',
minValue: '',
maxValue: '',
}
}
}
Now, unless you are making some kind of coupled datepicker components that will always behave in pairs, I believe the cleanest approach would be to set the disabled value in the linked datepicker in the mapDispactchToProps function in your parent component.
That is where you would set ids to the components, and you know exactly which component should be disabled before another.
Something like:
dispatch => ({
setArrivalDate(value) {
dispatch(datePickerActions.setValue(arrivalDateId, value);
dispatch(datePickerActions.setMaxValue(depatureDateId, value);
},
setDepatureDate(value) {
dispatch(datePickerActions.setValue(depatureDateId, value);
dispatch(datePickerActions.setMinValue(arrivalDateId, value);
}
})
This may not be abstract enough, but is clean.
You could do the same thing if you have a paired component, but you'd still need to know which date comes before another. It'd be a hassle to make a generic abstraction around it.
Remove the bold part in your code below
...
case types.SET_DISABLED_DATES:
if(typeof state.datePickers[action.datePickerId] !== "undefined") { // Making sure that what I'm trying to update exists in the reducer
return Object.assign({}, state,
datePickers: Object.assign({}, state.datePickers, {
datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
disabledBefore: action.value
})
})
} else {
return state;
}
Also, a little bit of es6 spread and a helper switchcase function makes this code much more readable.
const newReducer = (state = defaultState, action) => switchcase({
[types.SET_DISABLED_DATES]:
state.datePickers[action.datePickerId] === undefined
? state
: ({ ...state,
datePickers: { ...state.datePickers,
[action.datePickerId]: { ...state.datePickers[action.datePickerId],
disabledBefore: action.value,
},
},
}),
})(state)(action.type);
Using lodash/fp/set, the code becomes
const reducerWithLodash = (state = defaultState, action) =>
switchcase({
[types.SET_DISABLED_DATES]:
state.datePickers[action.datePickerId] === undefined
? state
: set({...state}, `datePickers.${action.datePickerId}.disabledBefore`, action.value)
})(state)(action.type)
I haven't tested the lodash version, so please take that with a grain of salt (Dan Abramov seems to approve)

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