How to update state from the components in React? - reactjs

I'm working on the "Orders list" page.
I have a hook:
const [orders, setOrders] = useState([])
What happens currently:
when page is loaded - I fetch orders with Apollo useQuery
in the useEffect: once I get orders, I set them with setOrders
then, I return them with:
{orders.map((order) => (
<OrderComponent order={order}/>
))}
This works fine for displaying data, but I would like to be able to change order data (just state) from OrderComponent.
So let's say we have a notes input in the OrderComponent:
<input type="text" name="order_notes" value={order.notes}/>
The question is:
How to handle such updates from the child component?
What I tried:
On the parent page, I created handler:
const handleOrderNotes = ({ value, orderIndex }) => {
const ordersCopy = [...orders]
ordersCopy[ordersIndex].notes = value
setOrders(ordersCopy)
}
then I passed it to the OrderComponent along with orderIndex:
{orders.map((order, index) => (
<OrderComponent order={order} orderIndex={index} handleOrderNotes={handleOrderNotes}/>
))}
And added the handler to the input:
<input
type="text"
name="order_notes"
value={order.notes}
onChange={(value) => {
handleOrderNotes({value, orderIndex})
}}
/>
And it worked just fine. But after I started adding more and more stuff, to the point where I would like to use components in OrderComponent. I started to wonder if there is a better way of doing such things?
During the devlopment I was often getting errors like:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
So I believe that I'm missing something crucial here.
Just getting started with React, so would apprentice any input. Thanks :)

There are two ways I can think of to solve this. You would have to chose which one suits you based on how your app is organized/designed.
If using redux or some kind of state management for app, then you don't need to tell parent component but as you have index of the order with you in the child component itself, you can simply update that order in the app store/state itself.
You may want to wrap each component with useMemo and use useCallback for passing any callback to these child components. This second step you should do even if you are have state management done for your app to avoid unnecessary re-renders

Please change this
{orders.map((order, index) => ( <OrderComponent order={order} orderIndex={index} handleOrderNotes={handleOrderNotes}/> ))}
to
{orders.map((order, index) => ( <OrderComponent order={order} orderIndex={index} handleOrderNotes={({value,index})=>handleOrderNotes({value,index})}/> ))}

Related

React Hooks does not allow reordering of two sub-components?

This is somewhat of an in-depth question about React Hooks:
There is some writing about, the setState() returned from React Hook useState(), when we call it, it "sets an internal index to 0", so that next time, when that component is re-rendered, useState() know which is the first useState(), and which is the second useState(), etc.
Is that how it works? Because, what if Main Component has 2 sub-components, and sub-component 1 calls setState(), vs sub-component 2 calls setState(), then I don't think Main Component is called to re-rendered, and each setState() in sub-component 1 or 2 just set the internal index to 0? Then how can useState() know which is which?
To see that it is the case, we can see https://codesandbox.io/s/wizardly-wind-3f210?file=/src/App.js
So if we click on "Count 1", we can see in the console log that only Count 1 is re-rendered, and likewise, "Count 2" as well.
So then, it looks like the two sub-components can work independently (But how?)
However, if that is true, if we reverse the order of sub-component 1 and 2 if count 0 is odd or even, every thing should work fine, and the React Hooks doc does not say we can't do that.
This is how they are swapped:
{count0 % 2 === 0 ? (
<div>
<Count1 />
<Count2 />
</div>
) : (
<div>
<Count2 />
<Count1 />
</div>
)}
However, we can see in this demo: https://codesandbox.io/s/amazing-wescoff-ih3n3?file=/src/App.js
If we click on "Count 1" to increment the count to 6, and then now we click on "Count 0" to make it even and odd and even and odd, the two counters switch positions, and they are reset to 0.
So how does it work? It does not seem like it is as simple as setting the internal index to 0 when setState() is called. Or is it true that sub-component 1 knows its setState() should set the internal index to 1, and sub-component 2's setState() knows it should set the internal index to 2? Is that how it works? But then in that case, if we switch the two sub components, they should simply show swapped values instead of showing 0. How does it really work?
In a nutshell: React has a handful of 'native' hooks that are internally wired to React's bookkeeping. But React keeps a separate book for each and every component instance you happen to create. So you can't confuse React be switching the order of elements. You could mess up the bookkeeping though by changing the order of hooks used inside one single component, hence the "rules of hooks".
So by doing something like this you could probably produce a mess:
const HorribleExperiment = () => {
var firstState, secondState;
if (Math.random() > 0.5) {
const firstState = useState('first');
const secondState = useState('second');
} else {
const secondState = useState('second');
const firstState = useState('first');
}
const firstValue = firstState[0];
const setFirst = firstState[1];
const secondValue = secondState[0];
const setsecond = secondState[1];
return (
<div>
<div>the first value is {firstValue}</div>
<div>the second value is {secondValue}</div>
<input onChange={e => setFirst(e.target.value)} />
<input onChange={e => setSecond(e.target.value)} />
</div>
);
}
The rules of hooks regarding the order of hooks are applicable within a single component. A renderer is responsible for keeping track of hook calls, and since it also responsible for rendering components, it can track which hook calls correspond to which component instance.
The example isn't specific to hooks and their order in any way. It would be the same with class components as well. When a parent component is re-rendered, child components are remounted because they appear to be different component hierarchy, so their states are initial states on each remount.
If the order changes, they need to be tracked with a key:
{count0 % 2 === 0 ? (
<div>
<Count1 key={1} />
<Count2 key={2} />
</div>
) : (
<div>
<Count2 key={2} />
<Count1 key={1} />
</div>
)}

Advantages of rendering a component vs a function returning JSX [React]

I understand that, within your React component, it's always good to render a component rather than use a function that renders some JSX.
What I mean is,
Doing this :
export default function App() {
const [count, setCount] = useState(0);
const someRenderFunction = () => <p>Hey</p>;
return (
<div className="App">
{someRenderFunction()}
<button
onClick={() => {
setCount((c) => c + 1);
}}
>
Increment{" "}
</button>
<p>{count}</p>
</div>
);
}
is NOT encouraged. The render function should be exported into it's own Component, like this :
const HeyComponent = () => <p>Hey</p>
export default function App() {
const [count, setCount] = useState(0);
return (
<div className="App">
<HeyComponent />
<button
onClick={() => {
setCount((c) => c + 1);
}}
>
Increment{" "}
</button>
<p>{count}</p>
</div>
);
}
But I never really understood the benefits of this refactor. I tried placing checking the re-render behaviours, unmount behaviours. Still didn't see any difference.
Can anyone explain in detail why this refactor is necessary/beneficial ?
This is all about components, so components should be reusable and should follow the DRY principle, in your case that seems to be so simple and just as you said will prevent the someRenderFunction() to be re-rendered if there aren't any changes to that component in the virtual dom so the best practices are to use <X /> syntax always or for some cases const Y = <X /> also is more readable. testing is another reason to create more components and make them more decoupled. imagine here you need to pass props to your someRenderFunction() so you will lose the jsx feature for passing props as <X prop={PROP}/>.
The actual difference between the two is that the function returning JSX is not a component, so it does not have its own state. When you use useState in the function, the state change will cause the App to be re-rendered.
Two reasons why the second way is better than the first way are:
The function which defines the <p>Hey</p> JSX (named someRenderFunction in the first way, and HeyComponent in the second) is moved outside of the component App in the second way. As of now, there's no benefit, but if you later wanted to extend someRenderFunction/HeyComponent to have normal component lifecycle that can be hooked into, moving it outside of App would be essential. For example, if you wanted the function/component to keep track of its own state, the first way would cause someRenderFunction's state to get reset every time App renders. More info on this problem can be found here: https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unstable-nested-components.md, and here: https://stackoverflow.com/a/64211013/693855.
HeyComponent would be seen as a valid component whereas someRenderFunction would not, because component names need to start with a capital letter (this is mentioned here https://reactjs.org/docs/components-and-props.html#rendering-a-component in the Note at the bottom of the section). If you used hooks in your function, and it was not seen as a valid component by React, it would be breaking the "Rules of Hooks" set by React (https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions). I'm not sure if this would cause any bugs, but it is frowned upon by React -- if you have the "react-hooks/rules-of-hooks" ESLint rule enabled (from https://www.npmjs.com/package/eslint-plugin-react-hooks), you would see an error like this: ESLint: React Hook "useState" is called in function "someRenderFunction" that is neither a React function component nor a custom React Hook function.(react-hooks/rules-of-hooks)
The code which renders a component is followed as best practice.
Using Components we can pass state values to child component and can be used to display data. The same component can be re-used in other places.
If you have to just display a small function it can be used too if it shows constant information.

Reducing React table rerendering

I have a table with two columns, each of which are editable. The table also includes the ability to add new entries. The problem I'm running into is that whenever a single row is edited, each row is re-rendered. For larger tables this causes noticeable keystroke delays when editing a single row. Any ideas as to how this can be prevented?
I haven't been able to replicate the performance problems in a "small" example, but https://codesandbox.io/s/zen-williams-r8pii?file=/src/App.js is the basic functionality. I've tried dropping React.memo in a few places to no avail (I'm a newbie at this stuff)
Thanks for any help!
There were some things that were making the row rerender:
First, the row component needed to be wrapped in React.memo
const ThingRow = React.memo(({ thing, onUpdate }) => {
...
})
Then the onUpdate needed to be memoized as well with React.useCallback, and to reduce the dependencies in useCallback to only setThings(see note), you can use the callback version of setThings and .map to update the item
const ListOfThings = ({ things, setThings }) => {
const onUpdate = React.useCallback(
(thing) => {
setThings((prevThings) =>
prevThings.map((t) => (t.id === thing.id ? thing : t))
);
},
[setThings]
);
return (
<table>
<tbody>
{things.map((thing) => (
<ThingRow key={thing.id} thing={thing} onUpdate={onUpdate} />
))}
</tbody>
</table>
);
};
When having this kind of issue you need to look for the way to memoize props you are passing down, especially callbacks, and reduce dependencies in hooks
this is the codesandbox fork https://codesandbox.io/s/table-row-memoization-xs1d2?file=/src/App.js
note: according react docs
React guarantees that setState function identity is stable and won’t change on re-renders
There are a couple of optimizations you can try.
First of all, if you are dealing with a lot of data, the core problem could be that you are rendering too many things to the DOM.
If this is the case, I would start by adding virtualization or pagination to the table.
You could easily add virtualization with a library, e.g. using FixedSizeList from react-window:
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>Row {index}</div>
);
const Example = () => (
<List
height={150}
itemCount={1000}
itemSize={35}
width={300}
>
{Row}
</List>
);
This should already cut down on any performance problems, but if you only want to optimize rerenders:
The default behavior for react is to render the current component where a useState setter was called, and all child components, even without any props changing.
so you should wrap both ListOfThings and ThingRow in memo to opt out of this behavior:
const ListOfThings = memo({ things, setThings }) => {
...
});
const ThingRow = memo(({ thing, onUpdate }) => {
...
});
If you don't do this, changing the unrelated title input will lead to rerendering everything.
Lastly, you are passing an inline event handler (onUpdate) to ThingRow.
This will create a new function object for each rendering of ListOfThings, which would bypass the memo optimization we did earlier.
To optimize this, you could create a persistent reference to the function with useCallback:
onUpdate={useCallback(
thing => setThings(prev => prev.map(t => t.id === thing.id ? thing : t)),
[setThings])}
Note that it's considered best to use the function update style in useState whenever you are relying on a previous value for the update.
Small disclaimer: If you are having performance problems, I would go only with the virtualization / pagination solution, and not try at all to optimize for rerenders. React's default rendering behavior is very fast, and changing it could potentially introduce hard to find bugs related to things not updating.

useEffect not triggering

I think my useEffect is broken.
I have on the parent component
<div className={redactKategori}>
<select className="form-control form-control-sm mb-3 mr-2" name="betalt" defaultValue={'välj kategori'} onChange={e => { e.preventDefault(); setKate(e.target.value); setRedactKategoris('d-block') }}>
<option >valj kategori</option>
{ Kategori ?
Object.keys(Kategori).map(key => {
return (
<option key={key} value={key}>{key}</option>
)
}) :
null
}
</select>
<div className={redactKategoris}>
<EditKat aa={kate} />
</div>
and on the child component
function EditKat({ aa }) {
let defaultValues = 0
useEffect(() => {
defaultValues = 2
}, [aa])
console.log(defaultValues)
}
So, as far as I understood, everytime ${kate} would get a value on the parent component, the child component would trigger the useEffect hook. However it is not working. Am I doing something wrong?
The reason for the experienced behavior is not that useEffect isn't working. It's because of the way function components work.
If you look at your child component, if useEffect is executed and the component rerenders, defaultValues would be set to 0 again, because the code inside of the function is executed on each render cycle.
To work around that, you would need to use the useState to keep your local state consistent across renders.
This would look something like this:
function EditKat({ aa }) {
// Data stored in useState is kept across render cycles
let [defaultValues, setDefaultValues] = useState(0)
useEffect(() => {
setDefaultValues(2) // setDefaultValues will trigger a re-render
}, [aa])
console.log(defaultValues)
}
Try adding a key prop on your component when it is created in the parent code
<yourcomponent key="uniquevalue" />
This is because in most cases, when your component is reused, based on the way it is created, it may usually re-render it with some changes instead of recreating it again when you reuse it, Hence the useEffect is not going to be called. eg in SwitchRoute, loops, conditionals...
So adding a key will prevent this from happening. If it is in a loop make sure each element is unique, maybe by including the index i in the key if you can't find any better unique key.
I faced the same problem, I debugged it and i found that, i am mutating the state directly instead of cloning it and using it. So, that's why useEffect is not triggered.

Using Redux and React Hooks, Flatlist will not re-render nor will the individual components inside of it

I'm using React Native, React Hooks, Redux and Function components. I am using useSelector to get a large array from my state from Redux and I iterate through it using a Flatlist. In my Flatlist I render multiple child components, including children of my child. I pasted the main child at the bottom of this post. When I am on a separate screen and the FlatList is inactive and a property changes inside of an object in my array trackingBoardList, I expect that Flatlist will rerender when I come back to the screen, but it doesn't. Furthermore, even if I try to memoize the child component AvatarListItem using React.memo(), the child by itself also will also not re-render. I cannot get the full list to re-render, nor can I get the children by themselves to re-render. Is Flatlist incompatible with React Hooks and arrays, with nested objects? In my reducer I am indeed returning that array immutably when it changes, and I'm doing that using Immer draft.
I get my state with useSelector. It's a large array with lots of deep nested objects:
const trackingBoardList = useSelector((state: iAppState) => {
return state.trackingBoardAndSchedule.trackingBoardList;
});
My Flatlist looks like this:
<FlatList
data={trackingBoardList}
extraData={trackingBoardList}
keyExtractor={() => uuid()}
renderItem={({ item, index }) => {
return (
<AvatarListItem
patientID={item.patient.id}
patientFirstName={item.patient.name_first}
patientLastName={item.patient.name_last}
patientMiddleName={item.patient.name_middle}
patientSex={item.patient.gender}
patientAge={calculateAge(item.patient.birth_dttm)}
visitReason={item.reason_for_visit}
time={item.created_dttm}
inProgress={false}
patientAvatarURI={item.patient.profile_image_name}
status={item.evisit_status}
/>
);
}}
/>
My child component looks like this:
const AvatarListItem = (props: iAvatarListItem) => {
return (
<>
<ListItemContainer {...props}>
<Avatar
avatarSize={42}
avatarType='patient'
avatarUserID={props.patientID}
avatarURI={props.patientAvatarURI}
/>
<NameAgeColumn>
<ColumnTitle>
{props.patientFirstName}
{props.patientMiddleName ? ` ${props.patientMiddleName}` : ''}
{` ${props.patientLastName}`}
</ColumnTitle>
<ColumnSmallText>
{props.patientSex === 'M' ? 'Male, ' : ''}
{props.patientSex === 'F' ? 'Female, ' : ''}
{props.patientAge} Years
</ColumnSmallText>
</NameAgeColumn>
<ReasonColumn>
<ColumnTitle>Reason</ColumnTitle>
<ColumnSmallText>{props.visitReason}</ColumnSmallText>
</ReasonColumn>
<LongBadge>
<LongBadgeText>{props.status}</LongBadgeText>
</LongBadge>
</ListItemContainer>
</>
);
};
export default AvatarListItem;
To answer my own problem, I discovered the bug immediately after I posted this. It was a mutability issue. I was assured that my state was immutable using ImmerJS however instead of using my draft state Immer was somehow automajically using my baseState, which worked almost everywhere throughout the app but finally caused an issue on this particular screen. If you are having a very strange re-rendering issue where a component just refuses to re-render no matter what you do, I highly recommend focusing on your reducer and trying a few different ways of returning your state.

Resources