How can I prevent re-render after state changed in React Hooks? - reactjs

I am trying to build an app but the problem is when I change a state, all the components re-render.
const App=()=>{
const [showMenu,setshowMenu]=useState(false)
return(
<>
<Header showMenu={showMenu} setShowMenu={setShowMenu}/>
<MainArea/>
{showMenu ? <Menu/> : null}
<Footer/>
</>
)
}
When I set showMenu to true by button, a menu appears but the problem is all my components (Header,MainArea,Footer) do re-render. I don't want that. How can I solve this problem?

You can use useMemo hook.
It prevents specific jsx contents from rerendering, even when the state that those jsx contents/components use get updated.
const App=()=>{
// you can only memoize parts that do not require use of the updated variable in this case. It means that Header cannot be memoized.
const mainArea = React.useMemo( () => <MainArea/>, [] );
const footer = React.useMemo( () => <Footer/>, [] );
const [showMenu,setShowMenu]=useState(false)
return(
<>
<Header showMenu={showMenu} setShowMenu={setShowMenu}/>
{mainArea}
{showMenu ? <Menu/> : null}
{footer}
</>
)
}
EDIT:
Does the really need the state of the showMenu? Or we can only pass the setShowMenu to it?
If so, then you can also memoize the Header component into memo chunk like:
const header = React.useMemo( () => , [] );

You can use React memo (or PureComponent if you use classes) on the components that you don't want to re-render (MainArea,Footer). This way when an update is forced by their parent they will first make a check if any of their props changed and if not (which is your case), re-render will be skipped.
However it's advisable to perform memoization on expensive components only instead of wrapping everything with React.memo, because memoization also introduces some overhead.

In this case, the state is only for header component. U can bring the state inside the Header component. U can read here for further explaination https://overreacted.io/before-you-memo/
Also here another good explanation How to prevent re-rendering of components that have not changed?

To prevent re-rendering of reusable component while changing other states, We can use React.memo()
const MainArea = React.useMemo(() => {
return <div />;
});
const Footer = React.useMemo(() => {
return <div />;
});
const App = () => {
const [showMenu,setshowMenu]=useState(false)
return(
<>
<Header showMenu={showMenu} setShowMenu={setShowMenu}/>
<MainArea/>
{showMenu ? <Menu/> : null}
<Footer/>
</>
)
}

Related

React Hook not update conditional component

thank for your time and sorry if this question was already asked, but honestly i didn't find any solution.
i'm new on React and i'm trying to create a website-portfolio, i finish my project but now i'm trying to implement a Spinner loader while my video background is loading.
i'm going to simplify the code only for explain better the problem.
Here's my classes
function App() {
const [isLoading, setIsLoading] = useState(true);
return isLoading ? (
<LoaderContainer>
<Loader type="Puff" color="rgb(241, 113, 27)" height={100} width={100} />
</LoaderContainer>
) : (
<Router>
<Home isLoading={isLoading} setIsLoading={setIsLoading} />
</Router>
);
}
export default App;
//HOME functional component-------------------
const Home = ({ isLoading, setIsLoading }) => {
return (
<>
//...other components
<HeroSection isLoading={isLoading} setIsLoading={setIsLoading} />
//...other components
</>
);
};
export default Home;
Now.. in HeroSection there is a video with this option:
" onLoadStart={() => setIsLoading(false)}"
this working fine but not for update the LoaderContainer with the Router one.
i really don't now why. if i try to put an useEffect inside App with a timer and try to update isLoading it's working fine!! also isLoading is used inside of HomeContainer for render a String and it's updating correctly! it seems that isLoading in the conditional statement not re-render the component but only if setIsLoading is called from the child (Home).
Thank all for the support!
Because in this case there's an endless loop: isLoading === true -> Home not render -> onLoadStart not trigger -> no chance to set isLoading false -> ...
You can't avoid rendering <HeroSection/> even in loading because HeroSection is responsible to terminate the loading. So the solution will be controlling the opacity or stacking context to make <Home/> invisible at the start instead of not rendering at all

React: passing props through a layout component

new to React here, I want to have a filter (in Nav.js) that filters content on App.js, but is nested in a Layout component. What's the best way to pass around props here? Can I keep everything as a functional component?
files here:
https://codesandbox.io/s/filter-menu-react-layout-uvppj?file=/src/Layout.js
Just pass the setFilter as props and you should be good to go.
const Layout = props => {
const { setFilter, children } = props;
return (
<div>
<Nav setFilter={setFilter} />
{children}
</div>
);
};

Custom Hook returning component rerenders all children if the individual hook state changes

Why does using a custom hook to render a component rerenders the App Component. If I toggle <Comp> or <AnotherComp> the entire App component gets rerendered same for the other component.
I tried React.memo and wrap the component again in App component but has no effect.
I read that its the way react compares and treats that functions are different betwn renders. But is there an advanced pattern people use for this purpose??
export const useCard = (params)=>{
const { ToBeWrappedComponent } = params;
const [isOpen,setIsOpen] = useState(true);
const toggle= ()=> setIsOpen(!isOpen);
const WrappedComponent = ()=>{
return (
<Collapse isOpen={isOpen} >
<button onClick= {toggle}> </button>
<ToBeWrappedComponent />
</Collapse>
)
}
return [WrappedComponent,toggle]
};
const App = ()=>{
const [Comp, toggleComp] = useCard({ToBeWrappedComponent: ()=> (<h1>Wrapped Item <h1>) });
const [AnotherComp, toggleAnotherComp] = useCard({ToBeWrappedComponent: ()=> (<h1>Another Wrapped Item <h1>) })
return (
<AnotherComp > </AnotherComp>
<Comp> </Comp>
)
}
Please note this code is just an exampple I have created to demonstrate what I am facing, I do more complex things with this apporach and just want to know about Advanced patterns to achieve it and the reason for rendering. Thanks
Because this invocation of useState is actually called within the App component's function:
const [isOpen,setIsOpen] = useState(true);
So it becomes a part of App's state, and not the wrapped component's state.
The component rerenders because every time the App is rerendered, the WrappedComponent function is created anew. It is a different function with a different address in memory, and therefore the component tree is rerendered from scratch.
This is a really weird pattern you are using. You are really overcomplicating things. Why not simply pass the render function to the wrapper component?
const TogglableComponent = ({ renderWrappedComponent, isOpen, onClick }) => {
return (
<Collapse isOpen={isOpen} >
<button onClick={onClick} />
{ renderWrappedComponent() }
</Collapse>
)
};
You then control the toggle state of each component from its parent through props. Or, if you don't care about passing the toggle state to the parent component, just store it in the wrapper:
const TogglableComponent = ({ renderWrappedComponent }) => {
const [isOpen, setIsOpen] = React.useState(false);
return (
<Collapse isOpen={isOpen} >
<button onClick={() => setIsOpen(!isOpen)} />
{ renderWrappedComponent() }
</Collapse>
)
};

How to pass a reference created by react hook of parent component to child components?

My Code:
const MyComponent: React.FC = () => {
const ParentReference = useRef(null);
return(
<Parent className="d-flex flex-row" ref={ParentReference}>
<ChildComponent
className="mr-3"
target={ParentReference.current}
/>
<AnotherChild className="mr-3" />
</Nav>
)};
As seen in the code above, I have created a reference using useRef hook and attached it to my ParentComponent.
Now am passing to ChildComponent by means of target prop and using it to do some dom manipulation inside the child.
Issue :
I am getting ParentReference as null for the first render of the component. (If I force re-render on change of the ParentReference it will update and re-render whole component then it will have value.)
How to get the ParentReference inside my child Component for initial render itself?
segFault's reference to this answer is correct. Your ref it not initialized until after your component's first render. So when you render your <ChildComponent target={ParentReference.current} />, the ref is not yet defined.
In this scenario you might consider using a useEffect to set a state variable on first render, and conditionally rendering the <ChildComponent /> once that state variable is set.
const MyComponent: React.FC = () => {
const ParentReference = useRef(null);
// define state variable defining the ref as not yet ready
const [refReady, setRefReady] = useState(false)
// On first mount, set the variable to true, as the ref is now available
useEffect( () => {
setRefReady(true)
}, [])
return(
<Parent className="d-flex flex-row" ref={ParentReference}>
{refReady && <ChildComponent
className="mr-3"
target={ParentReference.current}
/>}
<AnotherChild className="mr-3" />
</Nav>
)};

React constructor(), componentDidmount with props variable

Where I can call the constructor() and componentDidmount event with below code:
export const Home = props => (props.isAuthenticated ? (
<DashBoard {...props} />
) : (<Marketing {...props} />));
What is the meaning of the above code and how it's work?
This is a functional component, correctly formatted is probably a little easier to read:
export const Home = props => (
props.isAuthenticated ? (
<DashBoard {...props} /> // if authenticated return and render Dashboard
) : (
<Marketing {...props} /> // else return and render Marketing
)
);
In functional components use the useEffect hook with an empty dependency array to achieve the equivalent of a class-based component's componentDidMount. Hooks are called on mount and whenever a variable in its dependency array are updated.
effect hook
export const Home = props => {
useEffect(() => console.log("I just mounted!", []); // empty so called once when the component is mounted
return (
props.isAuthenticated ? (
<DashBoard {...props} /> // is authenticated return and render Dashboard
) : (
<Marketing {...props} /> // else return and render Marketing
)
);
};
You cannot use react lifecycle hooks in a functional component. Refer to react documentation below for usage of lifecycle hooks, and to convert functional components to class components.
https://reactjs.org/docs/state-and-lifecycle.html
export default class Home extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {}
render() {
const { isAuthenticated } = this.props;
return (
<>
{isAuthenticated ? <DashBoard {...this.props} /> : <Marketing {...this.props} />}
</>
);
}
}
export const Home = props => (props.isAuthenticated ? (
<DashBoard {...props} />
) : (<Marketing {...props} />));
Details
So the above code is a functional component, currently functional components can handle all the lifecycle methods that we use in class based components
So prev, before 16.8 of reactjs we can have state and life cycle methods in a functional components, It was only used for rendering the elements like as a presentational components. So at a point for complex applications we need to convert the functional components to class based components to handle a single state change
So this made the evolution of hooks, you can read more on the official docs of react js
So comming to your case if you need to call the method in componentDidMount, you can call as shown below
useEffect(() => {
// your logic same as componentDidMount in class based components
}, [])
So the second argument is the dependencies for the useEffect to trigger
if you pass it as like this it will call every time
useEffect(() => {})
If you pass it as like this it will call whenever the passed variable changes from props or state
useEffect(() => {}, [data, userName])
I hope this will give a better understanding of the problem

Resources