Set a child array state object from props value - javascript

I am creating a react application using hooks but I'm getting a weird behavior on my code, I'm not going to put all my code here but I will give you an example what it's going on.
const Child = props => {
const {data} = props;
const [myData, setMyData] = useState(data);
const items => myData.map( r => <li> r </li> );
return ( <ul> { items } </ul> );
}
const Parent = () => {
return (<div>
<Child data={ [1, 2, 3] }
</div> );
}
I am making changes to the array that the Parent component sends to the Child component.
When I add a new element to the array, the Child component re-render so MyData is equals to the array ( this makes sense because the Child components is re-render by the props change ).
If I delete an element from the array the Child component is re-render but myData doesn't change and the element I deleted from the array is still there in myData.
Why the useState sets the array to myData when I add elements to the array but when I delete elements it seems like this doesn't work,even though the Child component is re-render.
I know it seems a little dumb, you can ask me, why you don't just use the props value on the Child component instead of a state value, well the idea is on the Child component there is a search control so I can make some kind of searching over MyData and not over the props value ( but maybe there is another way ).

I think the issue is how you're using props.
When Child renders, it gets props.data from Parent. It then copies data to its component state as the variable myData.
From here on, changes to props can trigger the Child to re-render, but myData won't be redefined again via useState. That happens only once.
If you're writing code that does hot-reloading (you save the file and the application reloads in the browser), it might seem like changing the props sent to Child updates myData, but that's not happening in a production environment.
My suggestion: if you're going to fork props into the state of Child, think of props as an initial value, and not something that Parent can update.
The React docs explain why this is an anti-pattern:
The problem is that it’s both unnecessary (you can use this.props.color directly instead), and creates bugs (updates to the color prop won’t be reflected in the state).
Only use this pattern if you intentionally want to ignore prop updates.
React constructor docs

useEffect(() => {
setMyData(data)
},[data])
your data will be load to state only once. but you can handle changes on data by parent component with useEffect, useEffect will updates when data will updated
there is full code
const Child = props => {
const {data} = props;
const [myData, setMyData] = useState(data);
useEffect(() => {
setMyData(data)
},[data])
const items => myData.map( r => <li> r </li> );
return ( <ul> { items } </ul> );
}
const Parent = () => {
return (<div>
<Child data={ [1, 2, 3] }
</div> );
}
you can read about React use effect

Related

setting state with useState affects all useState hooks

I am trying to build an ecommerce website, and I hit a problem I cannot seem to resolve. I am very new to react and JS so have some patience please :)
I declared 4 useStates in my app.js:
const [elementeDinState, setElementeDinState] = useState([]);
const [currentCategorie, setCurrentCategorie] = useState("Acasa");
const [subCategorie, setSubcategorie] = useState([]);
const [cartContents, setCartContents] = useState([]);
const fetchData = useCallback(async () => {
const data = await getCategories();
setElementeDinState(data);
}, []);
useEffect(() => {
fetchData().catch(console.error);
}, [fetchData]);
const changeHeader = (dataFromMenuItem) => {
setCurrentCategorie(dataFromMenuItem);
};
const changeCopiiContent = (data1FromThere) => {
setSubcategorie(data1FromThere);
};
const changeCart = (dataFromCart) => {
setCartContents(dataFromCart);
};
I am passing the functions to change those states to different child components as props. my problem is, when I add items to cart it triggers a re render of my component (products listing component) that should not be affected by cartContents and that resets the state of said component to the initial value that changes the items being shown. does useState hook create a single global state comprised of all those states?
If these useState are defined in the app.js and then passed down, when a child will use them chasing the state will happen in the app.js so all the children of <App /> will be re-rendered.
I guess that your app.js looks similar:
function App() {
const [elementeDinState, setElementeDinState] = useState([]);
// ...and the other hooks and methods
return (
<cartContents setElementDinState={setElementeDinState} />
<ProductList />
)
}
In this case the state is in the component so when <CartContents /> changes it, it will trigger a re-render of the and all its children <ProductList /> included.
To avoid this problem think better when each piece of state needs to be and put the state as near as possibile to that component. For example, if the state of the cart does not influence the Product list. Move the useState in the <Cart /> component.
From what I understand, your problem is that you're simply resetting the cartContents state every time you call the changeCart function, correct?
What you probably want, is to add (or remove ?) the item to the cart, like this?
const changeCart = (dataFromCart) => {
setCartContents(oldContents => [...oldContents, dataFromCart]);
};
Here is a description of useState from the oficial site:
useState is a Hook (...). We call it inside a function component to add some local state to it
So it creates just a local state.
About your problem, We need more information, but I believe that some parent component of that widget is trying to render other component instead of your the component that you wanted (let's call it "ProblemComponent") and rendering you ProblemComponent from scratch again, before you can see it.
it's something like that:
function ParentComponent(props: any) {
const isLoading = useState(false);
// Some logic...
if(isLoading) {
return <LoadingComponent/>;
}
return <ProblemComponent/>;
}
If that doesn't work you can also try to use React.memo() to prevent the ProblemComponent to update when it props change.
well, seems like I wanted to change the way react works so I figured out a work around, based on what you guys told me. I declared the state of the productsComponent in the parent component and adding to cart now doesn't force a refresh of the items being shown. thank you!

Update list display in Next.js when passing data from children

I have a Page component in Next.js that looks somewhat like this:
export default function Index({ containers }) {
const [containerListState, setContainerListState] = useState(containers);
const updateContainerList = (container) => {
containers.push(container);
setContainerListState(containers);
};
return (
<>
<FormCreateContainer updateContainerList={updateContainerList} />
<ContainerList containers={containerListState} />
</>
);
}
FormCreateContainer allows for the creation of a container. When that API call resolves, I call updateContainerList and pass the newly created container to the parent. This works.
However, I also want to pass this container to ContainerList (which is a simple dynamic list using containers.map()) as part of the containers prop. While pushing to containers works as intended, Next does not live-update my list of containers; the newly created container only shows up when I reload the page.
I thought that including useEffect and changing updateContainerList as follows might work, but alas it did not.
const updateContainerList = (container) => {
containers.push(container);
};
useEffect(() => {
setContainerListState(containers);
}, [containers]);
How do I correctly pass data from a child to a parent component, therethrough passing it to a different child component and updating a dynamic list without reloading the page myself?
I really do appreciate any help as I have done extensive research that did not help me achieve my goal.
First and foremost, you should never mutate the value of a prop.
I think you should just use setContainerListState to update the data without mutating the prop like this:
const updateContainerList = (container) => {
setContainerListState(containers => [...containers, container]);
};
This will re-render your component and all its children automatically.

Pass data from grandchild to parent in React

Hello i have an array called info[] in a grandchild component and i want my parent component when a button is clicked to access the array. I also want a sibling component to have access to it. How is this possible .. i am a bit confused.
Should I use use-context ?
Thank you!
If I have understand what you are asking it could be something like this.
const GrandChild = ({ setParentInfo }) => {
const info = [1, 2, 3];
const handleClick = () => {
setParentInfo(info);
};
return <button onClick={handleClick}>Set parent info</button>;
};
const Sibling = ({ parentInfo }) => {
return <div>{parentInfo.length}</div>; // Do whatever you need with parentInfo
};
const Parent = () => {
const [parentInfo, setParentInfo] = useState([]);
return (
<div>
<GrandChild setParentInfo={setParentInfo} />
<Sibling parentInfo={parentInfo} />
</div>
);
};
Here you don't need context because you don't have that much layers but if you need to drill down the props than use a context.
If you want to share state among many different components in your application, and you believe that passing State as a prop is "a very long journey" to move around I'm probably you should consider something like use context hook.
Either way what you just described seems like a simple use case witch will not need context.
What you should do is:
On the parent you have [state, setState]
On the current component pass setStat as a prop to child component and then from child component pass setState as a prop to grandchild component.
Then on grandchild component you can do something like:
props.setState(array).
So now on the parent component the variable state will have been updated with the value array from the grandchild component.
If you want to pass state to siblings component, and by sibling I assume you mean sibling of the parent,
Then you should move the state from parent one level up let's say the parent of the parent.. and do what I've described above.
So create useState high in your component tree,
And pass State and setState as well to children as props, setState will be passed as function, so you can call it on the grandchild component or any other component

React gives wrong index to Child with React.memo

I've got a problem in my project which is pretty large therefore I can't post every part of the code here, but I'll try to summarize the main parts.
I've got a parent component which state is managed through a useReducer and which render returns a mapping of this state.
Each child has to take both the value and the index of the mapping. I'm also using the Context API to pass the dispatcher
on some of the childs.
function ParentComponent(props) {
const [state, dispatch] = useReducer(initialData, reducer);
return (
<div>
<MyContext.Provider value={dispatch}>
{state.myArray.map((value, index) => (
<ChildComponent value={value} index={index} />
))}
</MyContext.Provider>
</div>
);
}
/* Other file */
function ChildComponent({value, index}) {
const dispatch = useContext(MyContext);
return <div>
{/* Uses value and index to display some data */}
</div>
}
export default React.memo(ChildComponent, (prevProps, nextProps) => !_.isEqual(prevProps, nextProps));
Some of the childs in the tree components have to use React.memo to avoid useless re-renders, ChildComponent is one of them.
I'm using the lodash functions _.isEqual to compare the props to know when the component has to re-render.
The problem is the following. One of my components in the component tree adds items to the myArray attribute of the ParentComponent state.
When I add an item, anyway, each ChildComponent rendered from the ParentComponent mapping receives the same index of 0, creating a lot of problems.
The ParentComponent has the right indices (when I log them inside the mapping they are the right ones) but it's like the ChildComponent isn't getting it.
Is there any way to solve this? Maybe it has something to do with React.memo?
React.memo() takes a callback that should determine whether prevProps and nextProps are equal, but you're returning the negation of that.
Also using the index as a key should be considered a last resort, since this will fail to behave correctly when elements in the array are re-ordered relative to each other. You should always source the key from the data whenever possible.

Hook state one behind prop state

I'm having an issue with how props interact with state as created by hooks.
In the example below, the state as held in the hook is always one behind that as defined in the props.
I think I understand why this is happening. The lifecycle seems to be something like this:
Component renders
handleClick is defined with access to the scope based on this render
Button is clicked and calls handleClick
handleClick still has access to only the scope as it was defined in the render, and as such, items is still empty
add is called; the state in the parent component is updated, but setHookItems will use the state which was defined in the props when handleClick was defined
hookItems gets set to items, which is empty
This is for the first render, but I think the principle applies for all others; the click handler only has access to the scope before the new item is added, and as such, sets the hook state based on what it has as the state for items.
I may be wrong in my assumptions about how this transpires but it seems to be consistent with what I'm seeing.
So, to the actual question; how can I update an item in hook state based on the latest props, from some kind of handler, rather than that of the props at the time of the last render?
const { useState } = React;
const App = ({items, add}) => {
const [hookItems, setHookItems] = React.useState([]);
const handleClick = () => {
add(Math.floor(Math.random() * 10));
setHookItems(items);
}
return (
<div>
<p><span class="title">All Items: </span>{items.map(x => (<span>{x}</span>))}</p>
<p><span class="title">Hook Items: </span>{hookItems.map(x => (<span>{x}</span>))}</p>
<button onClick={handleClick}>Add</button>
</div>
);
}
const Root = () => {
const [items, setItems] = useState([]);
return (
<div>
<App items={items} add={item => setItems([...items, item])} />
</div>
)
}
ReactDOM.render(
<Root />,
document.getElementById('app')
);
.title { display: inline-block; width: 100px; text-align: right; margin-right: 20px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="app"></div>
The issue is that every time a component is rendered it gets a set of values for its state and props - but those values don't change until the next render.
So if I have state like:
const [count, setCount] = useState(0);
If I do setCount(count + 1), it queues up a new render where the count variable will be 1, but it doesn't immediately cause the count variable to become 1. (It can't - const variables can't be changed) So if I logged count immediately after setting, it'd still be zero.
In your example there's a level of indirection, where the setter is in the parent component, triggered by the add function, and the value is passed as a prop to the child, but that doesn't change the behavior.
In terms of design, the problem here is that you're using the "props to state" pattern, which is usually an anti-pattern. Your components generally shouldn't save props as state, you should use the props directly. Otherwise you run into issues where the props and state get out of sync.
The react blog has an article You Probably Don't Need Derived State which goes into this in more detail.

Categories