React useState vs raw variable - reactjs

For example i have a some data which need to be rendered. Items will be always the same but they are coming from props.
const items = props.data.values.map(value => ({
id: value.id
name: value.name,
rating: Number(value.rating)
}));
return (
{items.map(item => (
<div key={item.id}....
)}
);
May i use useState for items variable like that:
const [items] = useState(data.values.map(value => ({
id: value.id
name: value.name,
rating: Number(value.rating)
})));
Does it's help me to get rid of redundant "mapping" during next rerender or not?

No, it doesn't help.
I think it's better to completely get rid of the first map and do whatever you want in the second one. but if you think that is necessary for your app, you could use useMemo hook.
This hook provides you a memoized value that re-calculates only if some parameters change.
For example:
const items = useMemo(() => {
return data.values.map(...)
}, [data])
This example calculates items only if the value of data changes. otherwise, it returns the memoized version and doesn't re-calculate anything.
But what about useState? it used whenever we have some variable that whenever it changes, we want to re-render our component and show some new contents. for example, we have a Counter that whenever its value changes, we want to re-render component and show the new Value. so we use something like this:
const Counter = (props) => {
const [value, setValue] = useState(0)
return (
<div>
<p>{value}</p>
<button onClick={() => setValue(value + 1)}>Increment</button>
<button onClick={() => setValue(value - 1)}>Decrement</button>
</div>
)
}
So whenever we call setValue, the component re-renders and the new value will be shown to the user.

I think what you're looking for is Pure Components. They use shouldComponentUpdate to determine if the component needs to re-render. In your case if the props are the same, the component won't re-render if you use a Pure Component.

Related

onCallback React Hook - best practice query

This is more of a best practice question than anything. But I'm wondering if any inline functions within a React component should typically be wrapped with an onCallback for performance? Under what circumstances would I not wrap a function like this with onCallback?
For example:
const ToolSearch = (props: ToolsSearchProps) => {
const handleOnClick = () => {
alert('do something');
}
return (
<NoCardOverflow>
<ToolSearchCollapse openState={filtersOpen} onClick={handleOnClick} />
</NoCardOverflow>
);
};
In this example should I be doing this:
const ToolSearch = (props: ToolsSearchProps) => {
const handleOnClick = useCallback(() => {
alert('do something');
},[]);
return (
<NoCardOverflow>
<ToolSearchCollapse openState={filtersOpen} onClick={handleOnClick} />
</NoCardOverflow>
);
};
I will try to explain the useCallBack with an example. We all know the definition of useCallBack, but when to use is the trick part here. So, let me take an example.
const RenderText = React.memo(({ text }) => {
console.log(`Render ${text}`);
return (<div>{text}</div>);
});
const App = () => {
const [count, updateCount] = React.useState(0);
const [lists, updateLists] = React.useState(["list 1"]);
const addListItem = () => {
updateLists([...lists, "random"]);
};
return (
<div>
{`Current Count is ${count}`}
<button onClick={() => updateCount((prev) => prev + 1)}>Update Count</button>
{lists.map((item: string, index: number) => (
<RenderText key={index} text={item} />
))}
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
jsFiddle: https://jsfiddle.net/enyctuLp/1/
In the above, there are two states.
count - Number
lists - Array
The RenderText just renders the text passed to it. (which is the item in the list). If you click on the Update Count button, the RenderText will not re-render because it is independent from the main component (App) and during the updateCount, only the App component will re-render, since it needs to update the count value.
Now, pass the addListItem into RenderText component and click on the Update Count button and see what happens.
jsFiddle: https://jsfiddle.net/enyctuLp/2/
You can see that the RenderText will re-render even though there is no change in the list array and this is BECAUSE :
when the count is updated, the App will re-render
which, will re-render the addListItem
which causes the RenderText to re-render.
To avoid this, we should use useCallBack hook.
jsFiddle: https://jsfiddle.net/enyctuLp/4/
Now, the addListItem function has been memoized and it will only change when the dependency are changed.
ToolSearchCollapse is your child component, and so wrapping your handleClick function makes sense. Coz every time the parent component re-renders a new reference of handleclick will be passed to ToolSearchCollapse, and your child will re-render every time a parent state changes, (from states that aren't passed to the child also). Using useCallback will not allow creating new references, and thus you can control when your child should render.
If you don't pass this fn to the child, I don't see any reason to use useCallback.

React change state from another component without passing setState method

I have a custom table component and it has two props: items and columns. I want to implement a sorting feature. Sorting is not a big deal. I am sorting the items inside the table component but when items are sorted, also the state that stores the items must be changed which is outside of my table component. I don't want to pass setState method cause of my component is generic. It would be very useless if I pass setState method everywhere.
How do popular libraries solve this problem without need of pass a state-altering method? Do they copy the state to an internal state and then modify it or something? I hope I could explain my problem.
It might help to think in terms of controlled vs uncontrolled components. You may be familiar with this from core elements like <input>s, where you can either pass in a defaultValue prop, and let the input handle everything ("uncontrolled"), or you can pass in value and onChange and handle things yourself ("controlled"). You can design your table component as either a controlled component or uncontrolled component too.
Doing it as an uncontrolled component, you might pass in a prop that sets the initial sorting, but afterwards everything is handled by the table. The parent won't get notified and won't update its state:
const Parent = () => {
const [items, setItems] = useState(/* some array */);
return <MyTable items={items} defaultSort="asc" />
}
const MyTable = ({ items, defaultSort }) => {
const [sort, setSort] = useState(defaultSort ?? 'asc');
const sortedItems = useMemo(() => {
if (sort === 'asc') {
return [...items].sort(/* insert sort function here */)
} else {
return [...items].sort(/* insert sort function here */)
}
}, [items, sort]);
return (
<>
<button onClick={() => setSort(sort === 'asc' ? 'dsc' : 'asc')}>
Change Sort
</button>
{sortedItems.map(() => /* etc */)}
</>
)
}
If instead you do a controlled component, then the parent is in charge of the state, and the child just notifies the parent of relevant changes
const Parent = () => {
const [items, setItems] = useState(/* some array */);
const [sort, setSort] = useState('asc');
const sortedItems = useMemo(() => {
if (sort === 'asc') {
return [...items].sort(/* insert sort function here */)
} else {
return [...items].sort(/* insert sort function here */)
}
}, [items, sort]);
return <MyTable items={sortedItems} onSortToggled={() => setSort(sort === 'asc' ? 'dsc' : 'asc')} />
}
const MyTable = ({ items, onSortToggled}) => {
return (
<>
<button onClick={onSortToggled}>
Change Sort
</button>
{items.map(() => /* etc */)}
</>
)
}
If you add in some extra code to check for undefineds, it's possible to make your table support both controlled and uncontrolled modes, based on which set of props it is passed. But it should just be one or the other; you shouldn't try to have both components be managing state simultaneously, as this just adds opportunities for the states to get out of sync and bugs to be introduced.
the state that stores the items must be changed which is outside of my table component
If this is one of your requirements, then you're basically doing the controlled component version, and thus you must accept a function from the parent component which describes how to do so. The parent component is the only one who knows what state they have and how to update it.

Prevent useState value from being reset when props change

I have a component that looks something like this:
//#flow
import React, { useState } from "react";
type Props = {
likes: int,
toggleLike: () => void,
};
const Foo = (props: Props) => {
const [open, setOpen] = useState(false);
const style = `item${open ? " open": ""}`;
return (
<div className={style} onMouseOver={() => setOpen(true)} onFocus={() => setOpen(true)} onMouseOut={() => setOpen(false)} onBlur={() => setOpen(false)}>
<button onClick={props.toggleLike}>Toggle like</button>
</div>
);
};
export default Foo;
The open state is used to apply the "open" class when moused over. The problem comes if I call the toggleLike() prop function, since this updates the props and the component is rerendered with open reset to false. As the style uses a transition, this results in the animation rerunning as it changes back to false, then to true due to the mouse being over it.
So, how can I prevent open being reset back to false on each subsequent render? It seems like it should be straightforward, but after going through https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state I can't seem to apply it in my case.
State does not reset when props change. State is on a per component basis and is preserved throughout re-renders, hence being called "state".
As Dennis Vash already mentioned, the problem is most likely caused by the component being unmounted or replaced by an identical component. You can verify this easily by adding this to your component:
useEffect(() => {
console.log("Mounted")
}, [])
You should see multiple "Mounted" in the console.
If there's no way to prevent the component from being replaced or unmounted, consider putting the state into a context and consume that context inside your component, as you can also wrap each of your components into its own context to give it a unique, non-global, state.

Update state from children component

Take a look at the app. There is a list of buttons. It's really hard for me to explain the issue to you guys so take a look at the app yourself and consider this as a challenge. It's really a small app to demonstrate an issue that I am facing while working with react. This is not a basic stuff...
https://codesandbox.io/s/cranky-faraday-bxufk?file=/src/App.js
When clicking on the button, it is expected that the boolean next to it turn to false. It seems basic to you at first but when you take a look at the code, this is not easy to achieve. My real application is more complicated so I make a simple version of it just to demonstrate the issue
Expect:
Click on button 1 => boolean next to it turns to false
Click on button 2 => boolean next to it turns to false
Click on button 3 => boolean next to it turns to false
Reality:
Click on button 1 => screen goes blank
So, This is another way to solve this problem.
But first of all I will pin-down the problem in existing code, and try to explain why it is not working.
I had placed two console.log in App.js one is in useEffect() and another just before the return statement:
export default function App() {
const [state, setState] = React.useState([]);
// Set pannels state
React.useEffect(() => {
console.log("useEffect executing");
setState(
data.map((item) => ({
id: item.id,
isSaved: true,
content: <Button id={item.id} handleButton={handleButton} />
}))
);
}, []);
const handleButton = (id) => {
....
....
}
console.log("App Rendering");
return (
<div className="App">
<button onClick={handleClick}>Try this</button>
{state ? <Tab pannels={state} /> : null}
</div>
);
}
And below is the console after execution:
Now it is clearly can be seen that when you are executing setState inside useEffect the state value is an empty array []. Because first render cycle of App is already completed and call to useState initialized state to empty array.
In useEffect you are assigning property content as:
...
content: <Button id={item.id} handleButton={handleButton} />
...
Now if you closely observe {handleButton} is an function bound to the first render of 'App' (which is of course a function in itself) and {handleButton} has access to const state in it's parent scope, which is an empty array [] in first render cycle of 'App';
Thus you are in the middle of pushing the {handleButton} in state which has still a value [] (empty array), while useEffect is executing.
As soon as setState completes inside useEffect another render cycle for App is executed (due to local state update react re-renders the component). A new instances of App and all of it's nested members are created, except the state and setState which are managed by the react.
After this second render of App your state now contains the reference of old instance of {handleButton} (where you assign property content to Button) of previous render of App thus these {handleButton} still have state value as [] (empty array closures).
Now when you click any of the Button it's onClick is bound to the old reference of handleButton which has state value as empty array, and thus when {handleButton} finishes execution it calls setState (function provided by react framework, not affected by re-render) it once again sets the state to an empty array. And you eventually get you buttons disappeared.
Now to solve this, you need to rebind new instance of {handleButton} each time a re-render happens for App. You can not memoize {handleButton} because it depends on the state. Thus you can delegate rendering of Button component to Tab which re-renders itself after every state update.
So you can do something like this:
App.js
export default function App() {
const [state, setState] = React.useState([]);
// Set pannels state
React.useEffect(() => {
setState(
data.map((item) => ({
id: item.id,
isSaved: true
}))
);
}, []);
const handleButton = (id) => {
console.log("On button click: ", id);
console.log(state);
//Update isSaved of a corresponding button to false
const updatedState = [...state];
const updatedIndex = updatedState.findIndex((item) => item.id === id);
updatedState[updatedIndex] = {
...updatedState[updatedIndex],
isSaved: false
};
setState(updatedState);
};
return (
<div className="App">
<button onClick={() => handleButton(2)}>Try this</button>
{state ? <Tab pannels={state} handleButton={handleButton} /> : null}
</div>
);
}
Note: I have even used the same event handler handleButton for try this button too, to show that the same function instance works well if used on the component itself or passed to children.
Tab.js
const Tab = ({ pannels, handleButton }) => {
return (
<div>
List of pannels
{pannels.map((item) => (
<div key={item.id}>
<Button id={item.id} handleButton={handleButton} />
<span>{item.isSaved ? "true" : "false"}</span>
</div>
))}
</div>
);
};
codesandbox reference: https://codesandbox.io/s/zen-pascal-o6ify
Thanks
The problem in your code is related to state update. The handle button need to handle state as:
const handleButton = (id) => {
console.log("On button click: ", id);
setState((state) => {
// Update isSaved of a corresponding button to false
const updatedState = [...state];
const updatedIndex = updatedState.findIndex((item) => item.id === id);
updatedState[updatedIndex] = {
...updatedState[updatedIndex],
isSaved: false
};
return updatedState;
});
};
The solution is actually in the setState variant which uses a callback like setState((latestState) => {// do something }) and guarantees to give latest state for every execution.
Thanks for giving a beautiful problem to solve.

Updating one State with two fields?

I am trying to update my state data based on the users input in two fields and I'm not sure if Im going about it the right way.
The parent component Encounter.js holds the state I will try and limit the amount of code I add here so my issue is clear. So in ComponentDidUpdate I set the state with an object and create an update function to update the state. I pass the two values inside my state to another component PatientInfo along with the update state function:
componentDidUpdate(prevProps) {
if (this.props.details && this.props.details.medicalIntake && !prevProps.details.medicalIntake) {
this.setState({ pertinentMedications: {
covid19Protocol: this.props.details.medicalIntake.pertinentMedications.covid19Protocol,
note: "" || this.props.details.medicalIntake.pertinentMedications.notes
}})
}
}
pertinentMedicationsChange = (newValues) => {
this.props.setIdleTime();
this.props.setState({pertinentMedications: newValues});
}
return (
<PatientInfo
covid19Protocol={this.state.pertinentMedications.covid19Protocol}
pertinentMedicationsNote={this.state.pertinentMedications.note}
pertinentMedicationsChange={this.pertinentMedicationsChange}
/>
)
PatientInfo.js simply passes the props down.
<PertinentMedications
covid19Protocol={this.props.covid19Protocol}
pertinentMedicationsNote={this.props.pertinentMdicationsNote}
pertinentMedicationsChange={this.props.pertinentMedicationsChange}
/>
PertinentMedications.js is where the user input will be collected:
const PertinentMedications = ({
covid19Protocol,
pertinentMedicationsNote,
pertinentMedicationsChange
}) => {
const [isChecked, setIsChecked] = useState(covid19Protocol)
const onClick = (field, value) => {
setIsChecked(!isChecked)
pertinentMedicationsChange( {[field]: value})
}
const onNoteChange = (field, value) => {
pertinentMedicationsChange( {[field]: value})
}
return(
<ContentBlock title="Pertinent Medications and Supplements">
<CheckToggle onChange={() => onClick("covid19Protocol", !covid19Protocol)} checked={isChecked}>
<p>Patient has been receiving the standard supportive care and supplements as per COVID-19 protocol.</p>
</CheckToggle>
<Input
type="textarea"
name="pertinentMedications"
onChange={e => onNoteChange("notes" ,e.target.value)}
value={pertinentMedicationsNote}
/>
</ContentBlock>
)
}
export default PertinentMedications;
My true question lies within the pertinentMedicationsChange function as Im not sure how to take the data im getting from the PertinentMedications component and format it to be placed in the state. First Im not sure if I can update the state the way im trying to with these two independent fields that send their data to this function to change the state? And If it is possible Im not sure how to properly setup the key value pairs when i call setState. Can anyone help?
it seems that you are calling this.props.setState instead of this.setState. Second, this.setState also accepts a function which first param is the previous state. In this way you can use it to prevent its key values saved from pertinentMedications to be overwritten. fwiw, it's better to be consistent, not mixing hooks with react component based.
pertinentMedicationsChange = (newValues) => {
this.props.setIdleTime();
this.setState((state) => ({
// you create a new object with previous values, while newValues updates the proper keys, but not removing other keys
pertinentMedications: { ...state.pertinentMedications,...newValues}
});
)};

Resources