Component don't re-render after redux modification - javascript

I'm using redux in my app and at first, it works as I want. When I add something into my state through the reducer, my component is re-rendering as I want. But when I only modify the value of a property inside the state, my component isn't re-rending.
If you need code I can upload it (or some parts) but I think that the problem is more of the way of thinking.
Exemple of my state
state = {
"BaliseSeen": {
"Marseille": [
{
"id": "5566",
"nom": "Notre dame de La Garde",
"type": "Monument",
"vue": true,
},
],
"Toulon": [
{
"id": "1122",
"nom": "Isen Yncrea Méditerranée",
"type": "Monument",
"vue": true,
},
{
"id": "3344",
"nom": "Appartement n°69",
"type": "Monument",
"vue": true,
},
{
"id": "6677",
"nom": "Mairie",
"type": "Info",
"vue": false,
},
],
},
"Medailles": [
{
"ville": "Toulon",
},
],
"User": [],
}
When I add something new like that, it work and re-rendered :
nextState = {...state,
BaliseSeen: {
...state.BaliseSeen, [city]: [...state.BaliseSeen[city], { id: action.value.id, type: action.value.type, nom: action.value.nom, vue: action.value.vue }]
}
}
But when I want to only change the property vue from false to true like that, it work (when I check the state in the app the modification is applied but my component isn't re-rendered :
BaliseSeenIndex = state.BaliseSeen[city].findIndex(item => item.id === action.value.id)
nextState = state
nextState.BaliseSeen[city][BaliseSeenIndex].vue = true
(I also tried to delete the element from my state and add it after, but same as before, it work without render my component)
So I don't know how to say that the state is modified

As commented; you should not mutate, do the following instead
//not sure where cityp comes from but I assume it is from the action
return {
...state,
BaliseSeen: {
...state.BaliseSeen,
[city]: state.BaliseSeen[city].map((item) =>
item.id !== action.value.id
? item//not the item we want to edit, return unchanged
: {//item we are looking for, return changed copy
...item,
vue: true,
}
),
},
};

Related

How do I patch values back to an object in Angular 13?

I have an issue with patchValue and setValue in Angular 13.
I have a form I created using formBuilder called manageUsers. In it, I have a key called userRoles that contains an array of booleans.
The manageUsers form contains checkboxes for the user roles. The array of booleans turns those checkboxes into checked/unchecked based on the boolean values.
When you first visit the page, the userRoles array in the manageUsers form, by default, look like this...
this.manageUsers.value.userRoles = [false, false, false, false, false]
When a user checks or unchecks a checkbox, the form recognizes a change in state and the "Save Changes" button enables. If someone clicks the "Save Changes" button, then the manageUsers form gets sent to an endpoint where it is saved in a database.
When you select a user, for example "Anthony", the information for that user checks against a list of 5 user roles that looks like this...
[
{
"id": 1,
"name": "AnalyticalAdmin"
},
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
},
{
"id": 5,
"name": "SystemAdministrator"
},
{
"id": 6,
"name": "CaseworkSTRTechLeader"
}
]
to see what roles are assigned to Anthony. Let's say "AdminReviewer" is assigned. Then the resulting userRoles array would look like this...
this.manageUsers.value.userRoles = [false, false, true, false, false]
That means the third checkbox ("AdminReviewer") would be appear checked in the form and the others would not be checked. So let's say then you were to check the second checkbox ("Analyst"). The updated userRoles in the manageUsers form would look like this...
this.manageUsers.value.userRoles = [false, true, true, false, false]
What I've done is written code to compare that array with the list of 5 roles above.
The resulting array looks like this...
[
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
}
]
So far so good.
Here's my problem...
When I go to patch that array of objects BACK INTO the manageUsers form, the result looks like this...
[
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
},
true,
false,
false
]
Angular, for some reason, wants to add booleans to make up for the three missing elements. But here's the thing, I ONLY WANT the this.manageUsers.value.userRoles form object to contain the two objects. I don't need the superfluous booleans.
I just want the this.manageUsers.value.userRoles object (that I'm sending back to the database) to look like this...
[
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
}
]
NOT LIKE THIS...
[
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
},
true,
false,
false
]
I've tried using BOTH .patchValue and .setValue methods, but they don't work.
How do I patch an array of objects and tell Angular to NOT include booleans for the roles I didn't want to account for?
*UPDATED...
Here is the code I use set the userRoles array...
this.manageUsers = this.formBuilder.group({
id: null,
firstName: ['', Validators.required],
lastName: ['', Validators.required],
userInitial: ['', Validators.required],
netUserName: ['', Validators.required],
workPhone: [null, [Validators.required, Validators.pattern("[0-9 ]{10}")]],
email: ['', Validators.required],
userTitle: ['', Validators.required],
userRoles: new FormArray([], Validators.required),
incidents: new FormArray([], Validators.required)
})
Then, I created a property called userRolesArray...
public get userRolesArray() {
return this.manageUsers.controls.userRoles as FormArray;
}
Then, I created a property called addCheckboxesToManageUsers() and call that in the ngInit() method.
private addCheckboxesToManageUsers(): void {
this.listOfUserRoles.forEach(() => this.userRolesArray.push(new FormControl(false)));
}
after getting value from this.manageUsers.value.userRoles use reducer to remove all boolean type of value.
const data = [
{
"id": 2,
"name": "Analyst"
},
{
"id": 4,
"name": "AdminReviewer"
},
true,
false,
false
]
const newArr = data.reduce((prev, curr) => {
if (typeof curr !== 'boolean') prev.push(curr);
return prev;
}, []);
console.log(newArr)
updated one-liner
data.filter(data => typeof data != 'boolean')

Statsig - How to get true value from feature gate?

I am trying to implement StatSig(Refer: https://docs.statsig.com/) feature into my react(Nextjs) application. I have created new feature gate changeComponentUI and Rule has been added as Environment Tier -> Any of - staging, development. From client side I am trying retrieve the data as const statsigFeatureOn = useGate('changecomponentui').value, here I am always getting false value even if it is ON.
In app.js I have initialized like,
<StatsigProvider
sdkKey={"client-PsdasdASQW6556aIOPASASsqwewqeGSsad"}
user={{ userId, }}
options={{ environment: { tier: 'staging' } }}
waitForInitialization={true}
>
<Component {...args} />
</StatsigProvider>
In browser's Network tab I am getting -
Request Payload as,
{"user":{"userID":"","statsigEnvironment":{"tier":"staging"}},"statsigMetadata":{"sessionID":"433h67a3-416c-4914-82fd-e3c2b12f7n05","sdkType":"react-client","sdkVersion":"0.5.1","stableID":"8d122318-6d18-2322-889a-83c10e44e46"}}
Output is (Preview),
{
"gates": {
"+ZpxDXVQ/Rbhf02jl1Yv91VU+X+c0Gq/DZM+CmjPJgc=": false,
"uwglh6w2Nas8RufxC82qrsVAohod9gsGpvaT6/1l7ts=": false,
"sKSndsyjj+9MYUFlHPcdavbtA38g1+PjhofnyTDSxU8=": false,
"w9AtEJ/+vqrqb1kh8KPvhN2Rd32mkwfR+gxvlesY4ac=": true,
"gUZn3VlwEVdqBs7NcXGWHpBueNz0rlZGTufpLeB8Fug=": false
},
"feature_gates": {
"+ZpxDXVQ/Rbhf02jl1Yv91VU+X+c0Gq/DZM+CmjPJgc=": {
"name": "+ZpxDXVQ/Rbhf02jl1Yv91VU+X+c0Gq/DZM+CmjPJgc=",
"value": false,
"rule_id": "",
"secondary_exposures": []
},
"uwglh6w2Nas8RufxC82qrsVAohod9gsGpvaT6/1l7ts=": {
"name": "uwglh6w2Nas8RufxC82qrsVAohod9gsGpvaT6/1l7ts=",
"value": false,
"rule_id": "",
"secondary_exposures": []
},
"sKSndsyjj+9MYUFlHPcdavbtA38g1+PjhofnyTDSxU8=": {
"name": "sKSndsyjj+9MYUFlHPcdavbtA38g1+PjhofnyTDSxU8=",
"value": false,
"rule_id": "default",
"secondary_exposures": []
},
"w9AtEJ/+vqrqb1kh8KPvhN2Rd32mkwfR+gxvlesY4ac=": {
"name": "w9AtEJ/+vqrqb1kh8KPvhN2Rd32mkwfR+gxvlesY4ac=",
"value": true,
"rule_id": "6ZcQ0LOgAi2kSd5QgbtJzJ",
"secondary_exposures": []
},
"gUZn3VlwEVdqBs7NcXGWHpBueNz0rlZGTufpLeB8Fug=": {
"name": "gUZn3VlwEVdqBs7NcXGWHpBueNz0rlZGTufpLeB8Fug=",
"value": false,
"rule_id": "default",
"secondary_exposures": []
}
},
"configs": {},
"dynamic_configs": {},
"sdkParams": {},
"has_updates": true,
"time": 1631164754145
}
How can I get true value here? Also in output there is one object with true value but I am not getting it is for which feature gate's data.
Please help me to solve this issue.
Make sure you are using the exact ID from the console. In this case, you might need to do const statsigFeatureOn = useGate('changecomponentui').value if the feature name is "ChangeComponentUI" but the id is "changecomponentui". I believe all ID's are currently lowercase, but maybe camel case is a good reason not to do that (or to be case-insensitive)
(Edit) The following code snippet works for me in next.js after creating a gate by the same steps:
import React from "react";
import { StatsigProvider, useGate } from "statsig-react";
export default function App(): JSX.Element {
return (
<StatsigProvider
sdkKey={"client-(redacted)"}
user={{ userID: "" }} // Fixed from userId, but since userID didn't matter both worked in this case
options={{ environment: { tier: "staging" } }}
waitForInitialization={true}
>
<TestComponent />
</StatsigProvider>
);
}
function TestComponent() {
const statsigFeatureOn = useGate("changecomponentui").value;
return <div>{String(statsigFeatureOn)}</div>;
}

map objects in multidimensional array javascript react native based on column index

I have a two history objects that are podcasts and articles, i want to display both in the same screen in descending order by which time they were clicked,
Here are the variables of original article and podcast from DB
var { articles, articlesInHistory, podcastsInHistory, podcasts } = this.props.stores.appStore;
Here is my article Object from history: console.log("dataItem", articlesInHistory)
dataItem Array [
Object {
"currentTime": 1585439646,
"id": "156701",
Symbol(mobx administration): ObservableObjectAdministration {
"defaultEnhancer": [Function deepEnhancer],
"keysAtom": Atom {
"diffValue": 0,
"isBeingObserved": true,
"isPendingUnobservation": false,
"lastAccessedBy": 26,
"lowestObserverState": 2,
"name": "appStore#1.articlesInHistory[..].keys",
"observers": Set {},
},
"name": "appStore#1.articlesInHistory[..]",
"pendingKeys": Map {
Symbol(Symbol.toStringTag) => false,
"hasOwnProperty" => false,
"toJSON" => false,
},
"proxy": [Circular],
"target": Object {
"currentTime": 1585439646,
"id": "156701",
Symbol(mobx administration): [Circular],
},
"values": Map {
"id" => "156701",
"currentTime" => 1585439646,
},
},
},
,]
And podcast from history: console.log("dataItem", podcastsInHistory)
dataItem Array [
Object {
"currentTime": 1585439636,
"id": "4",
Symbol(mobx administration): ObservableObjectAdministration {
"defaultEnhancer": [Function deepEnhancer],
"keysAtom": Atom {
"diffValue": 0,
"isBeingObserved": true,
"isPendingUnobservation": false,
"lastAccessedBy": 26,
"lowestObserverState": 2,
"name": "appStore#1.podcastsInHistory[..].keys",
"observers": Set {},
},
"name": "appStore#1.podcastsInHistory[..]",
"pendingKeys": Map {
Symbol(Symbol.toStringTag) => false,
"hasOwnProperty" => false,
"toJSON" => false,
},
"proxy": [Circular],
"target": Object {
"currentTime": 1585439636,
"id": "4",
Symbol(mobx administration): [Circular],
},
"values": Map {
"id" => "4",
"currentTime" => 1585439636,
},
},
},
]
now i want to order the two components using currentTime in condition
for example if this podcast was first then i should return
<PodcastList navigate={navigate} podcast={podcast} key={index} />)
Or if the article is first then show
<SmallArticle key={index} article={article} />
i need them mixed not like articles on top and podcast bottom, i been searching arrays sort but couldn't solve it.
I want a condition based on currentTime and using an id to identify or match objects thanks.
As you don´t know from the data if its an article or a podcast (there is no attribute in your objects that tells you that), you can´t put them in the same list-array.
The only way to know if you should render an Article or a Podcast component is based on what list you are reading.
Keep two indexes, articleIndex = 0 and podcastIndex = 0 (you can keep that in your state), and read the actual article and podcast for respective list, and compare the current time. Then you will know what component to render, and advance the corresponding list index.
In pseudo code:
while articleIndex < articlesList.length && podcastIndex < podcastList.length do:
if articlesList[articleIndex].currentTime < podcastList[pocastIndex].currentTime do:
render <SmallArticle article={articlesList[articleIndex]} key ={articleIndex}/>//render SmallArticle
articleIndex += 1; //advance index
else do:
render <PodcastList podcast={podcastList[postcastIndex]} key={podcastIndex} />
podcastIndex +=1;
when the while statement finishes, is because one of the list has been fully traversed. You need to traverse the rest of the other and render the respect component.
If you show me some of your components code can help you with code in more detail, but I don´t know the context.

Object.assign is modifying original array object

I'm trying to modify a property value of a specific index in my state this property is the post_comments But my problem is the state is being modified even though i am only modifying the copy of it.. The code works how i want it to be but i'm modifying the state so it's probably bad how do i fix this?
socket.on('statusComment', (data) => {
const mutator = Object.assign([], this.state.getStatus);
const index = mutator.findIndex(i => i._id === data._id);
mutator[index].post_comments = data.post_comments; // Replace old post_comments with data.post_comments
console.log(mutator) // Got the post_comments
console.log(this.state.getStatus) // Also modified
// Commented out setState
// this.setState({
// getStatus: mutator
// })
});
Here is a sample data detected by socket
const data = {
post_id: "5b0689f03fb2fd1404f1854d",
post_comments: [{text: 'what'}]
}
This is what my state looks like
const data_arr = [
{
"post_img": [],
"post_date": "2018-05-24T09:46:24.948Z",
"post_comments": [
{
"comment_posted": "2018-05-24T09:46:31.015Z",
"_id": "5b0689f73fb2fd1404f1854e",
"comment_from": {
"photo_url": "png",
"_id": "5af16d60f3957c11e46500ae",
"display_name": "Lumpo"
},
"comment_text": "kaka2"
},
{
"comment_posted": "2018-05-24T09:47:42.752Z",
"_id": "5b068a3e2fdd6f141d5ba995",
"comment_from": {
"photo_url": "png",
"_id": "5af16d60f3957c11e46500ae",
"display_name": "Lumpo"
},
"comment_text": "kaka!"
}
],
"_id": "5b0689f03fb2fd1404f1854d",
"post_description": "get out\r\n",
"post_by": {
"photo_url": "png",
"_id": "5af16d60f3957c11e46500ae",
"display_name": "Lumpo"
},
"__v": 2
}
]
Spread operator is not working logs the same thing with the Object.assign method
// console.log(mutator)
[
{
"post_img": [],
"_id": "5b0694cc7925c914e4d95dda",
"post_description": "test",
"post_by": {
"_id": "5af16d60f3957c11e46500ae",
"display_name": "Lumpo",
"photo_url": "png"
},
"post_comments": [
{
"_id": "5b0694d67925c914e4d95ddb",
"comment_from": {
"photo_url": "png",
"_id": "5af16d60f3957c11e46500ae",
"display_name": "Lumpo"
},
"comment_text": "This comment should only be in the mutator ",
"comment_posted": "2018-05-24T10:32:54.937Z"
}
],
"post_date": "2018-05-24T10:32:44.613Z",
"__v": 0
}
]
// console.log(this.state.getStatus);
[
{
"post_img": [],
"_id": "5b0694cc7925c914e4d95dda",
"post_description": "test",
"post_by": {
"_id": "5af16d60f3957c11e46500ae",
"display_name": "Lumpo",
"photo_url": "png"
},
"post_comments": [
{
"_id": "5b0694d67925c914e4d95ddb",
"comment_from": {
"photo_url": "png",
"_id": "5af16d60f3957c11e46500ae",
"display_name": "Lumpo"
},
"comment_text": "This comment should only be in the mutator ",
"comment_posted": "2018-05-24T10:32:54.937Z"
}
],
"post_date": "2018-05-24T10:32:44.613Z",
"__v": 0
}
]
const mutator = Object.assign([], this.state.getStatus);
its doing shallow/reference copy of array.
So,original array is copied as it is using reference.
Use spread operator to create new copy of array and then do JSON.stringify followed by JSON.parse.U need a deep copy.
let mutator = [...this.state.getStatus];
mutator = JSON.parse(JSON.stringify(mutator));
you can copy your array something like this :
const mutator = [...this.state.getStatus];
Object.assign([], this.state.getStatus)
[] is an array, not an object. This is likely causing a problem.
Edit: See Josh’s comment, it is an object, but also an array. But the behaviour will be different to if it were an object object.
The quickest way to make a copy of an existing array without copying a reference is the following:
const mutator = this.state.getStatus.slice(0);
as described here https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
Your state is an object containing an array of objects.
First you copy state and reset the array getStatus with getStatus mapped. When the status item is found that needs to change you copy that item but set post_comments with another value (see code below).
this.setState({
...this.state,//copy state
getStatus: this.state.getStatus.map(//getStatus is a new array
(item,index)=>
(item._id===data._id)//if item._id is data._id
? {...item,post_comments:data.post_comments}//copy item but reset post_comments
: item//not the item we are looking for, return item (not changed copy)
)
})
If you need more help the please let me know.
Using JSON.parse(JSON.stringify(state))) will cause all components to re render even if their part of the state did not change (deep copy versus shallow copy). You can use shouldComponentUpdate to see if the state actually changed and tell react not to re render components where this did not happen. However; since you are deep copying everything (not only the items that changed) you cannot do this.
Here is an example of a base component that checks if the state passed to it actually changed reference and should re render:
import React from 'react';
class OnlyIfChanged extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.state !== this.props.state;
}
}
export default OnlyIfChanged;

Immutable, update inside map not returning correct object

I am trying to write a function to update an immutable object I have. I am using return myitem.updateIn so i can chain another update ontop of this one that is already working. So far, I have this :
memo.updateIn(['Topics', 'filters'], function(topic) {
topic.map(function(singleTopic) {
singleTopic.update('filters', function(subTopicFilters) {
subTopicFilters.map(function(singleSubTopic) {
if(singleSubTopic.get('checked')){
console.log("check", singleTopic.toJS());
return singleTopic.set('checked', true);
}
})
})
})
});
The console log inside is hitting the correct part, however this is does not seem to be updating the immutable map as I assumed it would have. The checked value in psycological disorders should be set to true. See fiddle here for example https://jsfiddle.net/alexjm/c5edxaue/27/ .
For some context, this is being used in a return where a couple of separate .update will be run on the memo in order like this
returnModifiedData(memo) {
return memo.update (....
).update( .....
.update();
This function is the first step in this process, the other 2 are already working. I am not sure what I am doing wrong to not get this to update correctly, possibly how I am trying to .set the singletopic inside? The basic logic is check if the topic has and sub topics with checked inside, and if so, check off the topic. Any help would be greatly appreciated. Thanks!
EDIT : forgot to add what the memo itself looks like :
const memo = {
"Topics": {
"filters": {
"Psychological disorders": {
"checked": false,
"filters": {
"Anxiety disorders": {
"filters": {},
"checked": true
}
}
},
"test": {
"checked": false,
"filters": {
"test": {
"filters": {},
"checked": false
}
}
}
},
"isOpen": false
}
};
It'd be better if you can explain what's the logic you want to achieve.
I'll guess it here:
Iterate through and update items in Topics->filters.
For each singleTopic iterated, further iterate through it's filters.
If any of its singleSubTopic have checked to be true, update the singleTopic's checked to be true.
And below is what you may expect:
const map = {
"Topics": {
"filters": {
"Psychological disorders": {
"checked": false,
"filters": {
"Anxiety disorders": {
"filters": {},
"checked": true
}
}
},
"test": {
"checked": false,
"filters": {
"test": {
"filters": {},
"checked": false
}
}
}
},
"isOpen": false
}
};
let memo = Immutable.fromJS(map);
memo = memo.updateIn(['Topics', 'filters'], function(topics) {
// Remember to return the updated topics.
return topics.map(function(singleTopic) {
// If singleTopic has any of its singleSubTopic under filters have value checked=== true
// update the singleTopic's checked, otherwise return unaltered.
if (singleTopic.get('filters').some(function(singleSubTopic) {
return singleSubTopic.get('checked');
})) {
return singleTopic.set('checked', true);
}
return singleTopic;
});
});
console.log(memo.toJS());
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>

Categories

Resources