How update state in parent component from child, child component? - reactjs

(sory for my english)
Hi, this code working half to half. I can delete product but i must go to another page , and back , to rerender. i dont know why ?
Code:
Parent Component:
function ShoppingCard() {
const shoppingCard = useContext<any>(ShoppingCardContext);
const [shoppingCard2, setShoppingCard2] = useState<any>(shoppingCard);
useEffect(() => {
setShoppingCard2(shoppingCard);
}, [shoppingCard, shoppingCard2]);
const removeProduct = (shopCart: any) => {
let index = shoppingCard.findIndex(
(oneProduct: any) => oneProduct.idProduct === shopCart.idProduct
);
shoppingCard.splice(index, 1);
setShoppingCard2(shoppingCard);
};
// passes this function to the next component through the next component.
Parent component 1
child component 2
child Component 3
And i use this fuction in a button to remove element. In console i see that shoppingCard and shoppingCard2 is update but only in child 3. And to see the efect (rerender) i must go to another page and back. In app i use React-Router.
Ps. This is my first post

Issue
You are mutating the state when deleting from the shoppingCard2 array, and then never creating a new array reference for React so reconciliation works. In other words, React doesn't "see" that the shoppingCard2 array is a new array so it doesn't trigger a rerender. You see the mutation when navigating away and back to the page.
const removeProduct = (shopCart: any) => {
let index = shoppingCard.findIndex(
(oneProduct: any) => oneProduct.idProduct === shopCart.idProduct
);
shoppingCard.splice(index, 1); // <-- mutation!!
setShoppingCard2(shoppingCard); // <-- same array reference
};
Solution
Shallow copy the shoppingCard2 array and remove the element. Use Array.prototype.filter to accomplish this.
Example:
const removeProduct = (shopCart: any) => {
setShoppingCard2(shoppingCard => shoppingCard.filter(
(oneProduct: any) => oneProduct.idProduct !== shopCart.idProduct
));
};
Suggestion
An improvement would be to expose a state updater function from the ShoppingCardContext so it can maintain the state invariant instead of offloading this to consumers. There's really no reason to "duplicate" the shopping card state locally if it can be helped.

The best will be to use a global state. You can read more about
React Context
Redux
another state manager

Related

Basis of two props wanted to update single state value without useEffect

What is the best way to update single state value, if specific two props changes, without useEffect, since performance point of view we should not be setting state inside useEffect.
For Example:
const NestedList = ({childItems, parentItems, selected, ...rest}) => {
const [currentItems, setCurrentItems] = useState({}); //assume this could be hash object
// Wrong X
useEffect(() => setCurrentItems(parentItems), [parentItems])
// Wrong X
useEffect(() => setCurrentItems(childItems), [childItems])
return ...
React component updates on both State change and Prop change. So you don't have to do anything additonal
Try below in single use effect. You can also compare previous props with new props.
useEffect(() => {
setCurrentItems(whateverthevalueis)
}, [prop1,prop2]);
What i try to figure out is the why in this case.
Why do you need a state if you always want to change it?
Why do the two props replace each other in the state?
There is nothing wrong with setting the state in a useEffect as alot of people already have said. However useEffect should be used for side effects.
There is no golden solution for all cases.
Use a state if you set the intial state with props and then the component update the state itself.
const processItems = (childItems, parentItems, ) => {
// Do what you want to create one state
return childItems
}
const NestedList = ({childItems, parentItems, selected, ...rest}) => {
// use props to set the inital state
const [currentItems, setCurrentItems] = useState(processItems(childItems, parentItems))
// We can later mutate the state, however if the props change we do not care
If the parent always pass the state as a prop, you do not need a state since the state is handled higher up.
const NestedList = ({childItems, parentItems, selected, ...rest}) => {
// No need for a state here, we can just use the props directly and
// the parent will pass the updated props when the state changes
If the parent pass several props and you need to calculate stuff based on the props, use memo hook.
const NestedList = ({childItems, parentItems, selected, ...rest}) => {
const currentItems = useMemo(() => {
// Add the condition of what items to show here
if(parentItems.length > 0) return parentItems
return childItems
}, [childItems,parentItems])
return ...

Why in one function state is updated immidietly and in other isn't

so I know that setState() is asonchrynus but I don't understand when It is. I always run into this problem when mapping lists in react. Specifically when I delete list element.
This function updates state instantly and rerenders array.map() function:
const handleHistoryDel = (index) => {
setHistory(history.filter((_, i) => i !== index));
};
And this one updates state but doesn't rerender:
const handleHistoryDel = (index) => {
let temp=history;
temp.splice(index,1)
setHistory(temp);
};
What is the difference? Should second function use some kind of callback? If so how would you implement one?
This happens because temp has the same memory address reference, when you run array.filter you get a new array, allocated in another space.
when you write let temp = history; you get the same reference, React will compare the previous state with the current one, if they have the same reference, it will not re-render.
try yourself:
const handleHistoryDel = (index) => {
let temp = [...history];
temp.splice(index,1)
setHistory(temp);
};
Maybe pass a function inside the setState like this,
setHistory(prev => // Do some changes here and return it // )
setHistory(prev => [...prev])
Also refer to this answer that explains about immutablity
How does React useState hook work with mutable objects
You should create a new array without reference to the state's array.
And then mutate the newly created array.
const handleHistoryDel = (index) => {
let temp=[...history];
temp.splice(index,1)
setHistory(temp);
};

Why component in react dont re-rendering

Hello why component in react was not rendering? State was updated and i see it in developer tools but content was not changing
FULL CODE: https://pastebin.com/bxNUAieV
import React, {useState} from 'react'
const List = (props:any) => {
const [actToDo, changeActTodo] = useState(props.actToDo)
const [ToDoLists, changeActToDoLists] = useState(props.ToDoLists)
return (
<>
{ToDoLists[actToDo].list.map((e:any, index:any) => (
<div className={'Todo__element'}>
<li key={index}>{e}</li><i className="fas fa-check"></i><i className="fas fa-recycle" onClick={() => props.removeElement(index)}></i>
</div>))}
</>
)
}
export default List
ToDoLists[number].list save the list
actToDo save number
const removeElement = (index:any) => {
console.log(index);
let FullList = ToDoLists
//#ts-ignore
FullList[actToDo].list.splice(index,1)
changeToDoLists(FullList)
console.log(ToDoLists);
}
you are mutating the array, that will not work, also the console.log will display the wrong value as setState is async.
https://dev.to/il3ven/common-error-accidentally-mutating-state-in-react-4ndg
const removeElement = (index:any) => {
console.log(index);
let FullList = {
...ToDoLists,
[actToDo]: {
...ToDoLists[actToDo],
list: ToDoLists[actToDo].list.splice(index,1)
}
}
console.log(FullList);
changeToDoLists(FullList);
}
by the way, saving props to state is a bad behavior, as if the prop change nothing will happen.
What I can see in your code is that you are directly modifying a state variable, which we should never do in React. To modify a state variable first make its copy in a new variable, and after making changes , pass that new variable in setState function :
So instead of :
let FullList = ToDoLists
do like this :
let FullList = _.cloneDeep(objects); //(using Lodash deep clone method here)
changeToDoLists(FullList);
Also , if you want to console your new list, then you should use useContext hook, since useState acts asynchronously and will not update value immediately, so console on very next line will not give updated value :
useEffect(() => {
console.log('Here is my new list after updation', ToDoLists);
}, [ToDoLists]);
This is happening because, you are directly trying to update the same state array.
React compares the previous state with the updated state to decide if the component needs to be re-rendered. Modifying the state directly will disturb this process.
While updating:
Since you are updating the data inplace using splice at the previous state's address i.e. mutating the same array and the updating state, and the state TodoLists is a complex object (nested array), so, React cant track the update and thus not detecting the state change.
While creating new item
This is not happening at the time of new item creation as you are not directly appending/ mutating the old state, therefore, the state got updating and react detects state change.
For removing the items, you can do is
const removeElement = (index) => {
console.log(index);
let FullList = [...ToDoLists]; //<-- create copy of the list
//#ts-ignore
FullList[actToDo].list = [...FullList[actToDo]].list.filter( //<-- filter the list by removing toBeRemoved Object
(_, i) => i !== index
);
console.log(FullList[actToDo]);
changeToDoLists(FullList); //update the state
};
Note: Whi;e updating a complex object, always keep in mind to use ... operator, which creates a shallow copy of your object for updating. It works both for array and object.

How can I prevent unnecessary re-renders on the child components in React with Hooks & Context?

I'm working on some code, and this code is huge. We have so many child components(nearly 300) and each one of them are using & manipulating values from the parent component's state via React Context.
Note: I didn't wrote this code from scratch. Which also means the design I'm about to show is not what I would come up with.
The problem: Since every component is using the same state, there are so many unnecessary re-renders happening. Every small state change is causing every component to re-render. And it makes the web app laggy. Literally, there is lag when you enter some input in a field.
I think, when state change happens the functions get rebuilt and that's why every child gets updated, because the value provided by context is changed after state change happened.
Additionally, I tried to use useReducer instead of useState that didn't went well. Besides that, I tried to use React.memo on every child component but the compare function didn't get triggered no matter what I tried. compare function only got triggered on the parent component which has the state as props. At this point, I'm not even really sure what is the problem :D
To give more specific details on the design, here is how we define and pass the callbacks to child components.
Definitions:
const [formItemState, setFormState] = React.useState<FormItemState>({} as FormItemState);
const getAppState = useCallback(() => ({ state: props.state as AppState }), [props.state]);
const getAppAction = useCallback(() => ({ action: props.action as AppAction }), [props.action]);
const getFormItemError = useCallback((key: string) => formItemErrorState[key], [
formItemErrorState,
]);
const getFormItem = useCallback((key: string) => formItemState[key], [formItemState]);
const updateFormItem = useCallback(
(name: string, formItemData: FormItemData): void => {
const previousState = getFormItem(name);
if (!isEqual(previousState, formItemData)) {
formItemState[name] = formItemData;
setFormState((state) => ({
...state,
...formItemState,
}));
}
},
[formItemState, setFormState]
);
Passing them to Context.Provider:
return (
<FormContext.Provider
value={{
getAppAction,
getAppState,
getFormItem,
updateFormItem
}}
>
<SomeComponent>
{props.children} // This children contains more than 250 nested components, and each one of them are using these provided functions to interact with the state.
</SomeComponent>
</FormContext.Provider>
);
Last note: Please ask me if more info is needed. Thanks!
Why dont you just rewrite your state management to redux and pull only the necessary state to be used on each component. React.memo only picks up changes from props

How to remove a component React has rendered via a function

Please reference this Codesandbox for my example.
I am having problems deleting components that get rendered via a function. Since components should not be stored in state, I store their type in an array in the parent component's state instead. A function iterates through that array to render the components. When I attempt to delete a specific component by index, React does not update the props (the index) in the component I want to keep (ie: changing the component at index 2 down to index 1). Instead, it renders the component already at that index. My Codesandbox example demonstrates this behavior.
How can I delete a component that is rendered in this way (via a function that uses the parent's state to populate props)? Is there another way I could be rendering these components?
Like #MichaelRovinsky said:
Give each component a unique identifier. When you delete a component,
find it by id and remove from the array. Don't use index because it's
not persistent
So, you need to use a unique identifier.
For that, you can use react-uuid.
First, I've redefined your step as:
export class Step {
Id;
Type;
Value;
}
So, your default data should be like:
const defaultSteps = [
{ Type: ALPHA, Id: uuid(), Value: 0 },
{ Type: ALPHA, Id: uuid(), Value: 0 },
{ Type: ALPHA, Id: uuid(), Value: 0 }
];
I've also changed Alpha component's index prop with id.
Inside your renderSteps function:
case ALPHA:
return (
<Alpha
id={step.Id}
onChange={onValueChange}
onDelete={index > 0 ? () => onDelete(step) : null}
key={step.Id}
/>
);
See, now both the id prop and key are the same and unique.
onValueChange event:
const onValueChange = (id, newValue) => {
let currentSteps = [...steps];
const step = currentSteps.filter((f) => f.Id === id)[0];
step.Value = newValue;
setSteps(currentSteps);
setValues(currentSteps.map((m) => m.Value));
};
So, setting your values becomes much simpler without tracing indexes.
onDelete event:
const onDelete = (step) => {
let currentSteps = [...steps];
const index = currentSteps.indexOf(step);
currentSteps.splice(index, 1);
setSteps(currentSteps);
setValues(currentSteps.map((m) => m.Value));
};
As all of your required properties are now inside single object, it's much easier to handle both steps and values state.
Working demo at CodeSandbox.
It is kind of hard for me to make sense of your code but the live demo looks clear enough.
Your state is an array and each item can be rendered as an isolated component. What you want to do is let the state flow down into your components, and wire them up to update it properly (when you click the delete key, an array item needs to be removed).
Here's how you can achieve this in a much simpler way: CodeSandbox demo.

Resources