React avoid all child rerendering - reactjs

So I have a master component which has several children, I simplified it with the example below
const Master = ({ c1props, c2props }) => {
const [count, setCount] = useState(0)
return <div>
<div>{count}</div>
<Child {...c1props}/>
<Child {...c2props}/>
</div>
}
So here my problem is that when I update only the state "count", the components are re-rendering, which is a problem because they are pretty heavy.
I was thinking about using useMemo() inside Child as a way to avoid those uneeded re-rendering but I don't if it's the best idea.
Any idea how to address this ?
thanks

const MemoizedChild1 = React.memo(Child1, isEqual);
const MemoizedChild2 = React.memo(Child2, isEqual);
Then you use it like this:
const Master = ({ c1props, c2props }) => {
const [count, setCount] = useState(0)
return <div>
<div>{count}</div>
<MemoizedChild1 {...c1props}/>
<MemoizedChild2 {...c2props}/>
</div>
}
where isEqual is a lodash function that deeply tests the equality of props.
Generally you can use memo like this:
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
Read more on the docs.
useMemo is a hook that memoizes a value, and memo can give you control about whenever to render your component or not.

if you want to avoid the usage of memo you may consider to split your Master, and create another Counter component which contains the count state:
const Master = ({ c1props, c2props }) => (
<div>
<Counter />
<Child {...c1props}/>
<Child {...c2props}/>
</div>
)

Related

useContext - Child component loses state after parent state is changed

Here is a pseudo code example of my app structure. I am trying to share state globally with react context, but updating state at the top level is causing issues with Child components re-rendering and resetting state (I think).
// My top level where I store the state all other components need
function App() {
const [userData, setUserData] = useState()
const userContext = {
userData,
setUserData
}
return (
<App>
<Context.Provider value={userContext}>
<Child />
<Child />
<Child />
</Context.Context.Provider>
</App>
)
}
// My child component where I want to preserve state
const Child = () => {
const [childState, setChildState] = useState('default value')
// I want to keep this value
setChildState('new value')
// This is causing App.js to re-render, then Child to rerender, and I lose child state. Then on the next render my 'childState' is back to 'default value', when I want it to still be 'new value'
const userContext = useContext(...)
userContext.setUserData('some change to userdata')
return {
...
}
}
My questions:
Is this an okay app structure, or is there a problem with it? Is there something I can do to persist the state in the Child componenet, or do I need to move the shared state out of App.js somehow?
You have structured the useContext wrong. You can check in React hooks how to use it.
One example with full-functionality which it may help you is:
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [userData, setUserData]=useState('nothing here')
// any code you want to pass in the code
//e.g. a function
const randomFunction = ()=>{
//do something here
}
return (
<AppContext.Provider
value={{
userData,
setUserData,
randomFunction
}}
>
{children}
</AppContext.Provider>
);
};
export const useGlobalContext = () => {
return useContext(AppContext);
};
export { AppContext, AppProvider };
then all you have to do is to wrap all the components (children) you want, e.g. wrap <App /> so, more or less everything:
<AppProvider>
<App />
</AppProvider>
So now in this case you can use everything from your AppContext in all your code, you can pass more variables and functions if you want, and you import that by using:
import { useGlobalContext } from '/pathYouHaveIt';
function App() {
const {
userData,
setUserData,
randomFunction,
} = useGlobalContext();
// now you can use those like you have them set-up in the App()
Provide state from parent to child like props, it wood be greater than useContext.
function App() {
const [userData, setUserData] = useState()
return (
<div className="App">
<Child userData={userData}, setUserData={setUserData}/>
</div >
)
}
And the child component has the form
const Child = ({userData, setUserData}) => {
const [childState, setChildState] = useState('default value')
setChildState('new value')
setUserData('some change to userdata')
return {
...
}
}

How to change useState from inside of Child Component

I am trying to import CSVs inside a Importer Component and pass on the Data to the Parent and change useState there...
So here i am trying to call said Component and pass on the useState function.
const [database, setDatabase] = useState([]);
useEffect(() => {
<Importer setdata={(data) => setDatabase([...data])} />;
}, []);
and Child Component is importing the CSV and passing on the data to be displayed after changing the State with useState:
const importAllCsv = (props) => {
text("template.csv").then((data) => {
//console.log(data);
const psv = dsvFormat(";");
//console.log(psv.parse(data));
DATABASE = psv.parse(data);
console.log(DATABASE);
props.setdata(DATABASE);
});
};
export default function Importer(props) {
return importAllCsv(props);
}
Components must start with a capital letter, also avoid returning components in useEffect when you can return them in the return part of the parent component.
As Aliyan said, try props.setdata((prevState) => [...prevState, ...DATABASE])
As per my understanding, you want to update the parent's state through a child component, for this you can simply pass the currentState (if required) and the setState function to the child as a prop using the following method :
export default function App() { //Parent Component
const [state, setState] = useState(1);
return (
<div className="App">
<div>{state}</div>
<ChildComponent setParentState={setState} currentState={state}/>
</div>
);
}
function ChildComponent({ setParentState, currentState }) {
function UpdateState() {
setParentState(currentState+1);
}
return <button onClick={() => UpdateState()}>Update State</button>;
}
Try to:
props.setdata((prevState) => [...prevState, ...DATABASE])
and try to include it on the return statement:
return (
<Importer setdata={setDatabase} />
);
not on useEffect hook.

Is useCallback best way to get props data from child component to parent components?

Is useCallback best way to get props data from child component to parent components?
From my perspective there are two common options here:
Passing down a callback function (created using useCallback or otherwise):
...
function Parent() {
const [text, setText] = useState('');
// Can also just pass setText directly
function onClick() {
setText('new text');
}
return (
<Child onClick={onClick} />
);
}
function Child(props) {
return (
<button onClick={props.onClick}>Click Me</button>
);
}
Pros: Simple
Use the Context API and consume the state
If the child is deeply nested, to avoid prop drilling (and to make the state easily available to other components), you could use the ContextAPI:
TextProvider.js
...
const TextContext = createContext('');
export function TextProvider(children) {
const [text, setText] = useState('');
const value = {
text, setText
};
return <TextContext.Provider value={text}>
{children}
</TextContext.Provider>;
};
export function useText() {
const context = useContext(ClientContext);
if (context === undefined) {
throw new Error('useSocket must be used within a SocketProvider');
}
return context;
};
App.js (or whatever file renders the <Parent />) - wrap the Parent in the provider:
function App() {
return (
...
<TextProvider>
<Parent />
</TextProvider>
...
);
}
Child.js
function Child(props) {
const { text, setText } = useText();
function onClick() {
setText('hello'); // Will update "text" state on parent
}
return (
<button onClick={onClick}>Click Me</button>
);
}
Pros: Useful when passing props into a deeply nested component. This post here details some more pros/cons of the ContextAPI

React.memo issue with Redux

I have two components.
function Parent(props){
const handleClick = () => {
console.log(props.stateA);
};
return <div><Child text={stateB} handleClick={handleClick} /></div>
}
const mapStateToProps = (state) => {
return {
stateA: state.stateA // stateA will be changed somewhere else
stateB: state.stateB
}
};
export default connect(mapStateToProps)(Parent);
function Child(props) {
return <div onClick={props.handleClick}>{props.text}</div>
}
export default React.memo(Child,(prev, next) => {
return prev.text === next.text
});
My problem is when stateA is changed somewhere, clicking on Child will log the previous stateA. I can't access the latest stateA.
You can see, I don't want to Child re-render when stateA changes,it should re-render only when stateB changed. But I want to access the latest stateA in Parent when clicking on Child.
Is there any method to solve this problem?
If the Parent component is a functional component then you can use like this
const [valueA, setValueA] = useState('')
useEffect(() => {
setValueA(props.stateA)
},[props.stateA])
console.log(valueA) // latest Value of stateA
return <div><Child text={stateB} handleClick={handleClick} /></div>
I hope it'll work for you.
You should be able to access props.stateA no problem
const handleClick = () => {
console.log(props.stateA);
};
because you accessing parent's props in handleClick. So if props.stateA is stale then the logical conclusion is the parent doesn't receive the latest props. Can we see how you update props/state?
The problem you are experiencing has nothing to do with Redux.
The Parent component passes 2 props to the child: the text which is changed when needed and handleClick which is changed each render of the Parent component - a new function is created each time.
But the React.memo is checking only the text prop, so the child receives a stale handleClick quite often.
The correct solution is to wrap the handleClick with useCallback and check all props in React.memo (react does this by default).
function Parent(props){
const handleClick = useCallback(() => {
console.log(props.stateA);
}, []);
return <div><Child text={stateB} handleClick={handleClick} /></div>
}
const mapStateToProps = (state) => {
return {
stateA: state.stateA // stateA will be changed somewhere else
stateB: state.stateB
}
};
export default connect(mapStateToProps)(Parent);
function Child(props) {
return <div onClick={props.handleClick}>{props.text}</div>
}
export default React.memo(Child);
You can keep a ref to stateA so it is what is logged when you call handleClick. useRef ensures that the last value is used.
function Parent(props){
const stateARef = useRef(props.stateA);
useEffect(() => {
stateARef.current = props.stateA;
}, [props.stateA])
const handleClick = () => {
console.log(stateARef.current);
};
return <div><Child text={stateB} handleClick={handleClick} /></div>
}

React child component does not re-render when props passed in from parent changes

I have a simplified react structure as below where I expect MyGrandChildComponent to re-render based on changes to the 'list' property of MyParentComponent. I can see the list take new value in MyParentComponent and MyChildComponent. However, it doesnt even hit the return function of MyGrandChildComponent. Am i missing something here?
const MyGrandChildComponent = (props) => {
return (
<div>props.list.listName</div>
);
};
const MyChildComponent = (props) => {
return (
<div><MyGrandChildComponent list={props.list}/></div>
);
}
const MyParentComponent = (props) => {
const list = { listName: 'MyList' };
return (
<div><MyChildComponent list={list} /></div>
);
}
In your MyParentComponent, the list is not a state variable and as such changing it will not even cause a re-render. If you absolutely want that when ever you change the value of list it re renders, then you will want to bring state to your functional component and the way to do that is to use hooks.
In this case your parent component will be something like below
import React, {useState} from 'react'
const MyParentComponent = (props) => {
const [list, setList] = useState({ listName: 'MyList' });
return (
<div><MyChildComponent list={list} /></div>
);
}
then at the child component you render it as I suggested in the comment above.
The parent needs to hold the list as a state variable and not just as a local variable. This is because react rerenders based on a state or prop change and at the parent you can only hold it in the state. With this when the value of list changes there will be a re-render which will then propergate the change to the children and grandchildren.
Also the only way of maintaining state in a functional component is to use hooks.
const MyGrandChildComponent = (props) => {
return (
<div>{props.list.listName}</div>
);
};
You forgot the {} around props.list.listName

Resources