React - setting state with imported data keeps getting updated - reactjs

I am importing an array of data like
import { menuData } from '../../data/menu-data';
data being:
export const menuData = [
{ id: 'all', title: 'Select all', icon: '', selected: false },
{ id: 'overview', title: 'Overview', icon: 'user-check', selected: false },
{
id: 'about',
title: 'About',
icon: 'info-circle',
selected: false,
partSelected: false,
}
];
I then initialise some state in my parent component like:
const [downloadMenu, setMenuState] = useState([...menuData]);
I pass "downloadMenu" state to my child compionent and absorb it as a prop and then create a copy of it which gets mutated i.e.
const updatedMenu = [...downloadMenu];
i then call this function which is in the parent and is passed down as a prop to the child component
onMenuChange(updatedMenu);
const handleMenuChange = (menuState) => {
setMenuState(menuState)
}
This works but when i try and reset the state to the initial menuData it doesnt work. MenuData is getting mutated aswell. Why is this?
I am calling this again - setMenuState([...menuData]);
but menuData is the same as the downloadMenu state.

Using ... only creates a shallow copy - a new list is created, but the items inside it remain the same. The objects in the list aren't copied, but remain as a reference to the original objects in menuData.
The solution is either to create a new object when you change menuData or create a deep copy.
A deep copy can be created using JSON - JSON.parse(JSON.stringify(menuData)) or a library like lodash - _.cloneDeep(menuData).

Related

Setting Nested Array in React Context

I am trying to figure out how to update an array that is nested within a React Context. Below is the context I am working with. It consists of "lists". Each list contains an array of "items".
import React, {useState, createContext} from 'react';
export const ListerContext = createContext();
export const ListerProvider = (props) => {
const [lists, setLists] = useState([
{
id: 1,
items: [{
itemid: 1,
text: 'Hello'
},
{
itemid: 2,
text: 'world'
}]
},
{
id: 2,
items: [{
itemid: 2,
text: 'Test'
}]
}
]);
return(
<ListerContext.Provider value={[lists, setLists]}>
{ props.children }
</ListerContext.Provider>
);
}
I have been trying to change the nested arrays using the "setLists" method below but it is not working. What am I doing wrong here?
const removeListItem = (e) => {
setLists((prevList)=>{
for(var i = 0; i < prevList.length; i++){
if(prevList[i].id === parseInt(e.target.value[2])){
prevList[i].items = (prevList[i].items.filter(function(item) {
return item.itemid !== parseInt(e.target.value[0]);
}))
}
}
return prevList;
});
}
As #zero298 mentioned in their comment, you need to pass the entire list into your state update function setLists. You are initializing the state and its update function with a "list of dictionaries", so you need to keep treating it as such, you can't selectively change few properties inside the dictionary or few elements in the list without having to update the entire list.
Easy correction of your code will be to conditionally make a new copy of your list with updated values, and then setting that list as your new state using your state update function.
Cheerio!

StencilJS passing in array of objects from React

I have a stencil component that takes an array of objects to display. I am trying to pass that array in from my react app, and have been following this example to do that. If i log the component with the array passed in, i can see that it appears to be formed correctly.
The actual component I built to create the reference to handle the array is where my problem is, and I just can't figure out where I made the mistake, as there is an issue with the example. My component inside my react app, which returns the stencil component is as follows
const ActionBar: React.FC<{ pageActions: pageActionsObj }> = ({ pageActions }) => {
const elementRef = useRef(null);
useEffect(() => {
(elementRef.current).pageActionList = pageActions;
}, [pageActions]);
return <pw-actionbar ref={elementRef}></pw-actionbar>;
};
and I try to use it as a regular component as such
<ActionBar pageActions={pageActionsObj}/>
Where the array that im passing as the prop is
export const pageActionsObj = [
{
name: "Users",
page: "partner",
icon: "pw-i-filled-multiple-users",
onpage: false
},
{
name: "Billing",
page: "billing",
icon: "pw-i-filled-billing",
onpage: false
},
{
name: "Helpdesk",
page: "help-desk",
icon: "pw-i-filled-help",
onpage: false
},
{
name: "My Profile",
page: "profile",
icon: "pw-i-filled-user",
onpage: true
},
{
name: "Sign Out",
page: "sign-out",
icon: "pw-i-filled-sign-out",
onpage: false
},
];
I very strongly believe that the way the component is set up, paired with the way I am passing in the array as a prop is causing the issue. I have been stuck for hours. What am i missing?

how to setState an object of an object in react

I'm hoping someone can give me some syntax/explanation help here i'm trying to call setState on an object nested in an object (data) in my state i'm a little stumped?
I'm not sure how to actually push my object onto the specified array in the setState function?
Can someone help me out? Many thanks!
here is the state i'm working with:
state={
height: 50,
parentCount: 1,
data:
{
parentId: 0,
name: 'parent',
children: [{name: 'Child One', distToWater: 0, children: [] }, {name: 'Child Two', distToWater: 0, children: [] }]
},
}
Here's my function where I try to add a child to my children [] array that's nested inside my data object in state:
addChild = () =>{
for (let x in data.children ){
for (child in x){
let closest = 99999
if(child.distToWater < closest){
closest = child.distToWater
var newBest = child
let newChild = {
name: 'child',
distToWater: closest - 1,
children: []
}
}
this.setState({data.children[newBest]: [...newChild] }) //use setState to add a child object
}
}
}
Since it is nested deep inside, something like the following code snippet should do.
const kid = { name: 'John', distToWater: 0, children: [] } // new object to add
const newChildren = [...this.state.data.children, kid]
const newData = { ...this.state.data, children: newChildren }
const newState = { ...this.state, data: newData }
this.setState(newState)
The above code snippet uses the spread operator. In case if you have not seen it before, it is a new JavaScript operator which would come very handy. (MDN link)
I am not sure why you had added the comment //use setState to add a child when the code snippet does not use hooks. I think hooks, or any other state management tool would be beneficial if the state object is so deeply nested.
You can try something like this
this.setState(prevState => ({
...prevState,
data: {
...prevState.data,
children: [...prevState.data.children, newBest]
}
}))

react how to prevent default on setState without event

Let's say you have a defined function containing a this.setState in a react component which is not fired by an event.
How can you do preventDefault() in order to keep the current scroll on the page?
I create a sandbox to illustrate this behaviour:
https://codesandbox.io/embed/damp-night-r92m5?fontsize=14&hidenavigation=1&theme=dark
When groups are defined and something fire the renderer, the page scroll on top. This does not happend id groups are not defined or contain an empty array...
How can I prevent this scrolling
First of all, I think your issue was nothing to do with event but your component is triggering render.
Based on your sandbox that you provided, you are actually declaring a new array each time the component renders. Meaning that React will assume that your [{ id: 1, content: "group1" }, { id: 2, content: "group2" }] is a new instance, even though all the items in the array is the same.
This line is causing your issue :
groups={[{ id: 1, content: "group1" }, { id: 2, content: "group2" }]}
Method 1: Move your groups variables into state
const [groups, setGroups] = useState([{ id: 1, content: "group1" }, { id: 2, content: "group2" }]);
This way React will not rerender your App until you call setState ( In this case, setGroups )
Method 2: Move your groups outside of your App function
const groups = [{ id: 1, content: "group1" }, { id: 2, content: "group2" }];
function App() {
... App Codes
}
In this way, React will not rerender your App since groups is not declaring within App.
Method 3: Using Memoization useMemo React Hook
const groups = useMemo(() => [{ id: 1, content: "group1" }, { id: 2, content: "group2" }], []);
The second argument in your useMemo function defines your dependencies array, setting it to empty array means that the value will never change. Hence React will not rerender your App.
In your render:
groups={groups}

How to separate UI and application state in Redux

When writing a react-redux application, I need to keep both application and UI state in the global state tree. What is the best approach to design it's shape?
Lets say I have a list of todo items:
{
items: [
{ id: 1, text: 'Chew bubblegum' },
{ id: 2, text: 'Kick ass' }
]
}
Now I want to let the users select and expand the items. There are (at least) two options how to model the state shape:
{
items: [
{ id: 1, text: 'Chew bubblegum', selected: true, expanded: false },
{ id: 2, text: 'Kick ass', selected: false, expanded: false }
]
}
But this is mixing the UI state (selected and expanded) with the application state. When I save the todo list to the server, I want to save just the application state, not the UI state (in real-world app, UI state can contain state of modal dialogs, error messages, validation status etc).
Another approach is to keep another array for the UI state of the items:
{
items: [
{ id: 1, text: 'Chew bubblegum' },
{ id: 2, text: 'Kick ass' }
],
itemsState: [
{ selected: true, expanded: false },
{ selected: false, expanded: false }
]
}
Then you have to combine those two states when rendering an item. I can imagine that you can zip those two arrays in the connect function to make rendering easy:
const TodoItem = ([item, itemState]) => ...;
const TodoList = items => items.map(item => (<TodoItem item={item} />));
export default connect(state => _.zip(state.items, state.itemsState))(TodoList);
But updates to state can be painful, because items and itemsState must be kept in sync:
When removing an item, corresponding itemState must be removed.
When reordering items, itemsState must be reordered too.
When the list of todo items is updated from the server, it is necessary to keep ids in the UI state and do some reconciliation.
Is there any other option? Or is there any library that helps keeping the app state and UI state in sync?
Another approach inspired by normalizr:
{
ids: [12,11], // registry and ordering
data: {
11: {text: 'Chew bubblegum'},
12: {text: 'Kick ass'}
},
ui: {
11: { selected: true, expanded: false },
12: { selected: false, expanded: false }
}
}
I'm currently looking at this myself for a side project. I'm going to approach it similar to Rick's method above. The data{} serves as the source of truth and you use that to push local ui changes into (reflecting the most current state). You do need to merge the data and ui together before render, and I myself have tried that in a few places. I will say, as far as keeping in sync, it shouldn't be too bad. Basically, whenever you save/fetch data, you're updating data{} and clearing out ui{} to prepare for the next use case.

Resources