Hooks equivalent for componentWilLReceiveProps to update state - reactjs

So I have a component where I conditionally update state on props change. If current state's CurrentPage is not equal to next props CurrentPage, I update state with next props' CurrentPage:
public componentWillReceiveProps(nextProps) {
if (this.state.CurrentPage !== nextProps.CurrentPage) {
this.setState({ CurrentPage: nextProps.CurrentPage });
}
}
I'm in the middle of refactoring the component do use hooks. I have a useState hook setup for CurrentPage when the component first loads:
const [currentPage, setCurrentPage] = useState(props.CurrentPage ? props.CurrentPage : 1);
What would be the hook equivalent of the componentWillReceiveProps logic in this case? Thanks!

use the "useEffect" hook for such purposes.
useEffect(() => {
if(props.yourproperty){
//execute your code.
}
console.log('property changed', props.yourproperty);
},[props.yourproperty])
This will only be called in case props.yourproperty is changed so no need to check with old props.
UseEffect Hook docs

You'll utilize the useEffect() hook. For an explanation of why, please read my guide to understand the hook and how you can leverage it: React Hooks Guide - useEffect (give it some time to load, codesandbox is a bit slow).
Your example is a bit arbitrary, as your props will already contain the current page, but nevertheless, here's a working example...
Working example (props updating another component's state):
components/Navigation
import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { Link, withRouter } from "react-router-dom";
const style = {
marginRight: 5
};
const Navigation = ({ location: { pathname }, children }) => {
// initializing "currentPage" with the current "pathname" prop
// initializing "setPage" function to update "currentPage"
const [currentPage, setPage] = useState(pathname);
// utilizing useEffect to keep track of "pathname" changes
// that, upon change, will update the "currentPage" state
useEffect(() => {
setPage(pathname);
}, [pathname]);
return (
<div className="container">
<div className="nav">
<Link style={style} to="/">
Home
</Link>
<Link style={style} to="/page1">
Page 1
</Link>
<Link style={style} to="/page2">
Page 2
</Link>
</div>
<div className="page">
<p>
(current page: <strong>{currentPage}</strong> )
</p>
</div>
{children}
</div>
);
};
Navigation.propTypes = {
pathname: PropTypes.string
};
export default withRouter(Navigation);

Related

Why does React re-render children when the parent changes?

If a parent re-renders, children in React are also re-rendered, no matter if the passed props changed or not.
Why is React doing that? What would be the issue if React wouldn't re-render children (without changed props) when the parent renders?
Update: I am talking about this in the React Devtools profiler:
Sample code:
App.tsx:
import React, { useMemo, useState } from "react";
import "./App.css";
import { Item, MyList } from "./MyList";
function App() {
console.log("render App (=render parent)");
const [val, setVal] = useState(true);
const initialList = useMemo(() => [{ id: 1, text: "hello world" }], []); // leads to "The parent component rendered"
//const initialList: Item[] = []; // leads to "Props changed: (initialList)"
return (
<div style={{ border: "10px solid red" }}>
<button type="button" onClick={() => setVal(!val)}>
re-render parent
</button>
<MyList initialList={initialList} />
</div>
);
}
export default App;
MyList.tsx:
import { FC, useState } from "react";
export interface Item {
id: number;
text: string;
}
interface Props {
initialList: Item[];
//onItemsChanged: (newItems: Item[]) => void;
}
export const MyList: FC<Props> = ({
initialList,
//onItemsChanged,
}) => {
console.log("render MyList");
const [items, setItems] = useState(initialList);
return (
<div style={{ border: "5px solid blue" }}>
<ul>
{items.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
<button type="button">add list item (to be implemented)</button>
</div>
);
};
React achieves a fast and responsive UI by re-rendering components on every state change (using setState) or from changes of props, followed by React’s reconciliation diffing algorithm that diffs previous renders with current render output to determine if React should commit changes to the component tree (e.g. DOM) with the new updates.
However, unnecessary component re-renders will happen and can be expensive, It’s been a common performance pitfall in every single React project that I’ve been working on. SOURCE
Solution for this issue :
A component can re-render even if its props don’t change. More often than not this is due to a parent component re-rendering causing the child to re-render.
To avoid this, we can wrap the child component in React.memo() to ensure it only re-renders if props have changed:
function SubComponent({ text }) {
return (
<div>
SubComponent: { text }
</div>
);
}
const MemoizedSubComponent = React.memo(SubComponent);
SOURCE
Memoization generates an additional cost corresponding to cache-related computations, this is why React re-renders components even when the props are referentially the same, unless you choose to memoize things using React.memo for instance.
If you consider for example a component that re-renders with different props very often, and if memoization was an internal implementation detail, then React would have to do 2 jobs on every re-rendering:
Check if the old and current props are referentially the same.
Because props comparison almost always returns false, React would then perform the diff of previous and current render results.
which means that you might end up with worse performance.
Wrapp your component with React.memo and it will not re-render
export const MyList: FC<Props> = React.memo(({
initialList,
//onItemsChanged,
}) => {
console.log("render MyList");
const [items, setItems] = useState(initialList);
return (
<div style={{ border: "5px solid blue" }}>
<ul>
{items.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
<button type="button">add list item (to be implemented)</button>
</div>
);
})
If you are looking at reason, please see this
Well, the component only re-renders if shouldComponentUpdate() returns true, which it does by default. It is usually not much of a problem because the DOM will still not update if there are no changes. As an optimization, you can still add logic to keep your child components from re-rendering if its state and props have not changed, as follows:
shouldComponentUpdate(nextProps, nextState) {
return (this.props.someProp !== nextProps.someProp && this.state.someState !== nextState.someState);
}
Do remember though this is just a performance optimization, when in doubt stick to the default behaviour. Here is a link to the official documentation.
Here is a little analogy that should help
Let's say you have a box, and a box within that box. If you want to replace the outer box with a new box, then you must also replace the box inside it.
Your parent component is like the outer box, and the child component like the inner box. If you clear away the first render to make room for a new render, that new render will re-render new child components inside it.
I hope this helps clear things up for you.
Edit:
If you were not to re-render the child component inside the parent then any props that might be passed to the child component would not update within it and therefore, the child would be forever without dynamic props, and the whole purpose of ReactJS would be lost. You wouldn't be here to ask this question in the first place anyway if that was the case.
When Parent Component render will not render child components,for using memo and useCallback.
Child Component:
Using memo will cause React to skip rendering a component if its props have not changed.
import { React, memo } from "react";
import { Typography, TextField } from "#mui/material";
function PasswordComponent(props) {
console.log("PasswordComponenmt");
return (
<div>
<Typography style={{ fontWeight: "900", fontSize: 16 }}>
Password
</Typography>
<TextField
size="small"
type="password"
variant="filled"
value={props.password}
onChange={(e) => {
props.onChangePassword(e.target.value);
}}
/>
</div>
);
}
export default memo(PasswordComponent);
Parent Component:
The React useCallback Hook returns a memoized callback function.
The useCallback Hook only runs when one of its dependencies update.
This can improve performance.
import { React, useState, useCallback } from "react";
export default function ParentComponent() {
const [password, setpassword] = useState("");
const onChangePassword = useCallback((value) => {
setpassword(value);
}, []);
return <PasswordComponent onChangePassword={onChangePassword} />;
}

Pass state onto {children} React hook

So I have a component that looks like this:
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
And then I have another component that looks like this:
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container ;
Now, the thing is the {children} of the 1st component is sometimes the 2nd component, and sometimes it's not. Therefore I can't just put the CSS and HTML from the 2ndcomponent into the 1st component - which in turn would fix my problem.
But as you might be able to see, there is an onClick event in the first component. I would like it so that when that is clicked, the state from the click is send to the 2nd component and toggles the className-toggle.
Can this be achieved by doing this, or do I have to set everything up differently ?
And yes, I am quite new to React, so please don't be harsh.
Css
I would look into better methods of applying styling with css. Not sure about your project scope/tools but typically all the css files are imported in the dom root and loaded in there. This avoids creating css files for every component.
Here's 9 ways of implementing css for react.
Passing HTML
In react if you want to render component in another component instead of passing it as a child you should import it as follows.
// replace container path with actual path of Container file
// ex './Container.js'
import Container from 'container_path.js';
Now Rendering the Component is as simple as including it in the html code.
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
<Container/>
</div>
</>
);
Here's a Stack Overflow post of users importing components using react + es6 + webpack. More information on importing components is available there.
State management
In react if you have a state that is being accessed by multiple components the standard is to keep the state in the parent component.
This way you can pass the state as a prop to any children components. You can also create a function which updates this state and pass that function as a prop to the children.
ex:
import React, { useState } from "react";
import Container from "./Container.js";
import Navigation from "./Navigation.js"
const Parent = props => {
const [toggle, toggleState] = useState(false);
return (
<div>
<Container toggleState={toggleState} toggle={toggle} />
<Navigation toggleState={toggleState} toggle={toggle} />
</div>
)
}
Before continuing working on your project I would recommend researching functional components vs class components. Here's a helpful article.
Try to wrap second component to function with state from first component as argument.
Wrapper for your second component and using for first component
const putInnerComponent = (stateFromOuterComponent) => <Container toggle={stateFromOuterComponent}/>;
<Navigation children={putInnerComponent}/>
Your first component
import React, { memo, useState } from "react";
import styles from "./navigation.styles.scss";
const Navigation = ({ children }) => {
const [toggle, toggleState] = useState(false);
return (
<>
<div onClick={() => toggleState(!toggle)}>
<p>Test</p>
</div>
{children(toggle)}
<style jsx>{styles}</style>
</>
);
};
export default memo(Navigation);
Your second component
import React, { memo, useState } from "react";
import styles from "./container.styles.scss";
const Container = ({ children, toggle }) => {
//const [toggle, toggleState] = useState(false);
return (
<>
<div className={toggle ? "dark-bg" : "dark-bg active"}>
{children}
</div>
<style jsx>{styles}</style>
</>
);
};
export default Container;

Why is React rendering the children in this scenario? (State is the same, using useState)

Code Sandbox
import React, { useState, useEffect } from "react";
const Foo = () => {
console.log("render foo");
return <div> foo</div>;
};
const App = () => {
const [value, setValue] = useState(1);
useEffect(() => {
console.log("effect", value);
}, [value]);
console.log("rendering");
return (
<div>
{" "}
<Foo /> <button onClick={() => setValue(value)}>Click To Render</button>
</div>
);
};
export default App;
Now according to the React Documentation
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (emphasis mine)
(React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
In the example I've given, we can see that the useEffect hook doesn't fire, as described by the documentation, but my Foo component is rendering.
Why is this?
I thought that maybe the inline function causes a render - but if I change that to a memoized function using useCallback the same behaviour happens:
const handleClick = useCallback(() => setValue(value), [value]);
console.log("rendering");
return (
<div>
{" "}
<Foo /> <button onClick={handleClick}>Click To Render</button>
</div>
The bail out logic was implemented in v16.8.0 of react-dom in which react also introduced hooks, whereas your demo uses the alpha version of hooks which is why you still see a re-render being triggered even when you update with the same state
According to v16.8.0 release notes
Bail out of rendering on identical values for useState and useReducer Hooks. (#acdlite in #14569)

how to stop re-rendering of list items in context consumer when state changes

I have a react project and I use the context api to manage the app's state.
I have a list of items with an onClick event that updates the state in the context.
The problem is that when the state changes all items re-renders which causes a lag.
My question is how to stop other items from re-rendering if not clicked on.
item.jsx
import React, { useContext } from "react";
import { MainContext } from "../../_context/PresentContext";
const Item = ({item}) => (
<MainContext.Consumer>
{({handleSelectItem}) => (
<div>
<button onClick={() => handleSelectItem(item)}>{item.name}</button>
</div>
)
}
</MainContext.Consumer>
)
items.jsx
import React from "react";
import Item from "./Item";
const itemsList = [
{id: 1, name: 'a'},
{id: 2, name: 'b'},
{id: 3, name: 'c'},
{id: 4, name: 'd'}
]
const Items = () => (
<div>
{itemsList.map(i => (
<Item item={item}/>
)
)}
</div>
)
the handleSelectItem functions just updates selectedItem on the context state and then i use it in a different compnent
this is just a simple example do demonstrate the problem. the real itemsList has about 200 item.
You can extend React.PureComponent if your Item component props are all serializable or you extends React.Component and implement shouldComponentUpdate.
Option 1.
import React, { Component } from "react";
import isEqual from 'lodash.isequal';
export class Item extends Component {
shouldComponentUpdate(nextProps){
return !isEqual(this.props.item, nextProps.item);
}
render() {
const { item } = this.props;
return (
<MainContext.Consumer>
{({ handleSelectItem }) => (
<div>
<button onClick={() => handleSelectItem(item)}>{item.name}</button>
</div>
)}
</MainContext.Consumer>
);
}
}
Option 2
export class Item extends PureComponent {
render() {
const { item } = this.props;
return (
<MainContext.Consumer>
{({ handleSelectItem }) => (
<div>
<button onClick={() => handleSelectItem(item)}>{item.name}</button>
</div>
)}
</MainContext.Consumer>
);
}
}
What about using Pure Components and making decision in life cycle method shouldComponentUpdate?
If you prefer to stick with hooks here is an official documentation how to implement mentioned above life cycle method with hooks approach.
Use React.memo / PureComponent / shouldComponentUpdate to re-render the components only when their state/props change in an immutable manner, instead of being re-rendered just because the parent component re-rendered.
use key attribute in your mapped JSX elements so that React can map between your DOM elements and only re-render them if the key's value change, instead of re-rendering the entire list every time the component is being rendered
arrow functions and object literals return a new reference every time they're called, so its better to define them outside of your render function and use useCallback or useMemo hooks to memoize them. so that they don't break your PureComponent
Consider using react-window https://github.com/bvaughn/react-window to implement virtual scrolling instead of rendering 200 items that might be hidden for the user and dont need to be in the DOM slowing things down
item.jsx
import React, {memo, useContext } from "react";
import { MainContext } from "../../_context/PresentContext";
const Item = memo(({item}) => (
const handleClick => useCallback(handleSelectItem(item), [item])
const renderConsumer = ({handleSelectItem}) => (
<div>
<button onClick={handleClick}>{item.name}</button>
</div>
)
<MainContext.Consumer>
{renderConsumer()}
</MainContext.Consumer>
))
items.jsx
import React, {memo} from "react";
import Item from "./Item";
const Items = memo(() => (
<div>
{itemsList.map(i => (
<Item key={i} item={item}/>
)
)}
</div>
))

How to call functional component from another functionnal component in react js

I have to call a functional component from another functional component So how can I call child functional component from a functional component in react js.
import React from "react";
import TestFunctional from "./TestFucntional";
const TestListing = props => {
const { classes, theme } = props;
const handleClickTestOpen = () => {
return <TestFunctional />;
};
return (
<div>
<EditIcon
className={classes.icon}
onClick={handleClickTestOpen}
/>
</div>
);
};
export default TestListing;
I am trying to call or render TestFucntional component on EditIcon clicked but it is not called. So How can I call component?
Thanks.
You just use it in your jsx as a component as usual. You can see here
const ItemComponent = ({item}) => (
<li>{item.name}</li>)
const Component1 = ({list}) => (
<div>
MainComponent
<ul>
{list && list.map(item =><ItemComponent item={item} key={item.id}/>)}
</ul>
</div>)
const list = [{ id: 1, name: 'aaa'}, { id: 2, name: 'bbb'}]
ReactDOM.render(
<Component1 list={list}/>
, document.querySelector('.container')
);
From the above conversation, I guess you want conditional rendering, i.e. after any event you want to render the child component. To do so in the parent component, it should maintain a state. If you want to use functional parent component, you can use hooks. Or you can use some prop for the conditional rendering as well. Please provide a code snippet.
This is for reference: https://reactjs.org/docs/conditional-rendering.html

Resources