React Hooks - Preventing child components from rendering - javascript

As a newbie in React, it seems that re-rendering of components is the thing not to do.
Therefore, for example, if I want to create a menu following this architecture :
App is parent of Menu, which have a map function which creates the MenuItem components
menu items come from a data source (here it's const data)
when I click on a MenuItem, it updates the state with the selected MenuItem value
for now it's fine, except that all the components are re-rendered (seen in the various console.log)
Here's the code :
App
import React, { useState} from "react"
import Menu from "./menu";
function App() {
const data = ["MenuItem1", "MenuItem2", "MenuItem3", "MenuItem4", "MenuItem5", "MenuItem6"]
const [selectedItem, setMenuItem] = useState(null)
const handleMenuItem = (menuItem) => {
setMenuItem(menuItem)
}
return (
<div className="App">
<Menu items = {data} handleMenuItem = {handleMenuItem}></Menu>
<div>{selectedItem}</div>
</div>
);
}
export default App;
Menu
import React from "react";
import MenuItem from "./menuItem";
const Menu = (props) => {
return (
<>
{props.items.map((item, index) => {
return <MenuItem key = {index} handleMenuItem = {props.handleMenuItem} value = {item}></MenuItem>
})
}
{console.log("menuItem")}
</>
)
};
export default React.memo(Menu);
MenuItem
import React from "react";
const MenuItem = (props) => {
return (
<>
<div onClick={() => props.handleMenuItem(props.value)}>
<p>{props.value}</p>
</div>
{console.log("render du MenuItem")}
</>
)
};
export default React.memo(MenuItem);
as you might see, I've used the React.memo in the end of MenuItem but it does not work, as well as the PureComponent
If someone has an idea, that'd be great to have some advice.
Have a great day

Wrap your handleMenuItem function with useCallback to avoid rerendering when the function changes. This will create a single function reference that will be used in the MenuItem as props and will avoid rereading since it's the same function instance always.
I have used an empty dependency array in this case which is correct for your use case. If your function has any state references then they should be added to the array.
const handleMenuItem = useCallback((menuItem) => {
setMenuItem(menuItem);
}, []);

There's a lot to unpack here so let's get started.
The way hooks are designed to prevent re-rendering components unnecessarily is by making sure you use the same instance of any unchanged variables, most specifically for object, functions, and arrays. I say that because string, number, and boolean equality is simple 'abc' === 'abc' resolves to true, but [] === [] would be false, as those are two DIFFERENT empty arrays being compared, and equality in JS for objects and functions and arrays only returns true when the two sides being compared are the exact same item.
That said, react provides ways to cache values and only update them (by creating new instances) when they need to be updated (because their dependencies change). Let's start with your app.js
import React, {useState, useCallback} from "react"
import Menu from "./menu";
// move this out of the function so that a new copy isn't created every time
// the App component re-renders
const data = ["MenuItem1", "MenuItem2", "MenuItem3", "MenuItem4", "MenuItem5", "MenuItem6"]
function App() {
const [selectedItem, setMenuItem] = useState(null);
// cache this with useCallback. The second parameter (the dependency
// array) is an empty array because there are no items that, should they
// change, we should create a new copy. That is to say we should never
// need to make a new copy because we have no dependencies that could
// change. This will now be the same instance of the same function each
// re-render.
const handleMenuItem = useCallback((menuItem) => setMenuItem(menuItem), []);
return (
<div className="App">
<Menu items={data} handleMenuItem={handleMenuItem}></Menu>
<div>{selectedItem}</div>
</div>
);
}
export default App;
Previously, handleMenuItem was set to a new copy of that function every time the App component was re-rendered, and data was also set to a new array (with the same entries) on each re-render. This would cause the child component (Menu) to re-render each time App was re-rendered. We don't want that. We only want child components to re-render if ABSOLUTELY necessary.
Next is the Menu component. There are pretty much no changes here, although I would urge you not to put spaces around your = within your JSX (key={index} not key = {index}.
import React from "react";
import MenuItem from "./menuItem";
const Menu = (props) => {
return (
<>
{props.items.map((item, index) => {
return <MenuItem key={index} handleMenuItem={props.handleMenuItem} value={item}/>
})
}
{console.log("menuItem")}
</>
)
};
export default React.memo(Menu);
For MenuItem, let's cache that click handler.
import React from "react";
const MenuItem = (props) => {
// cache this function
const handleClick = useCallback(() => props.handleMenuItem(props.value), [props.value]);
return (
<>
<div onClick={handleClick}>
<p>{props.value}</p>
</div>
{console.log("render du MenuItem")}
</>
)
};
export default React.memo(MenuItem);

Related

How to return a list according to selected item?

I'm still a beginner in ReactJS and I'm creating a project that works with a list of pokemons. The user selects a type of pokemon, and then I must return a list according to the user's selection.
I have a list with all pokemons, but some pokemons can belong to more than one type, as shown in this example below:
Could you tell me how to create a list with only the type of pokemon that the user selected? I think I can do this using reducer(), but I have no idea how to do it.
Here's my code I put into codesandbox
import React from "react";
import { useHistory } from "react-router-dom";
import { Button } from "#material-ui/core";
import { types } from "./data";
import "./styles.css";
const App = () => {
const history = useHistory();
const handleType = (type) => {
history.push({
pathname: "/list",
state: type
});
};
return (
<div className="content">
<h3>Pokémon Types</h3>
{types.results.map((type) => (
<Button
key={type.name}
style={{
margin: "5px"
}}
variant="contained"
onClick={() => handleType(type.name)}
>
{type.name}
</Button>
))}
</div>
);
};
export default App;
import React from "react";
import { useLocation } from "react-router-dom";
import { pokemons } from "../data";
const List = () => {
const { state } = useLocation();
console.log("state: ", state);
console.log(pokemons);
return <div>List</div>;
};
export default List;
Thank you in advance for any help.
You have a lot of ways to do that, but since you are still learning and you got a nice shot of code, I will introduce useMemo for you:
you can add useMemo to memorize and process data, then get the result direct...
look at this example:
const pk = useMemo(() => {
if (!state) return "State Empty!";
let result = [];
pokemons.forEach((v, i) => {
if (v.type.includes(state)) {
result.push(<li key={v.name + i}>{v.name}</li>);
}
});
return result;
}, [pokemons, state]);
return <ul>{pk}</ul>;
By this code, I got your list, check details in a simple loop, and then retrieve the needed list...
Notes:
In key I set name and i, but it's not totally correct, but it seems there is duplication on data, and why its not totally correct?, since we need to make sure to prevent re-render when no real change, but index if order change that's mean re-render...
You can use anyway like reducer, filter, or create a separate component and put it nested of useMemo
You can enhance data style to can check or retrieve data fast by Hash table...
Demo

React - Passing callbacks from React Context consumers to providers

I have the following context
import React, { createContext, useRef } from "react";
const ExampleContext = createContext(null);
export default ExampleContext;
export function ExampleProvider({ children }) {
const myMethod = () => {
};
return (
<ExampleContext.Provider
value={{
myMethod,
}}
>
{children}
<SomeCustomComponent
/* callback={callbackPassedFromConsumer} */
/>
</ExampleContext.Provider>
);
}
As you can see, it renders a custom component which receive a method as prop. This method is defined in a specific screen, which consumes this context.
How can I pass it from the screen to the provider?
This is how I consume the context (with a HOC):
import React from "react";
import ExampleContext from "../../../contexts/ExampleContext";
const withExample = (Component) => (props) =>
(
<ExampleContext.Consumer>
{(example) => (
<Component {...props} example={example} />
)}
</ExampleContext.Consumer>
);
export default withExample;
And this is the screen where I have the method which I need to pass to the context provider
function MyScreen({example}) {
const [data, setData] = useState([]);
const myMethodThatINeedToPass = () => {
...
setData([]);
...
}
return (<View>
...
</View>);
}
export default withExample(MyScreen);
Update:
I am trying to do this because in my real provider I have a BottomSheet component which renders two buttons "Delete" and "Report". This component is reusable, so, in order to avoid repeating myself, I am using a context provider.
See: https://github.com/gorhom/react-native-bottom-sheet/issues/259
Then, as the bottom sheet component which is rendered in the provider can receive optional props "onReportButtonPress" or "onDeleteButtonPress", I need a way to pass the method which manipulates my stateful data inside the screen (the consumer) to the provider.
You can't, in React the data only flows down.
This is commonly called a “top-down” or “unidirectional” data flow. Any state is always owned by some specific component, and any data or UI derived from that state can only affect components “below” them in the tree.
Your callbacks ("onReportButtonPress", "onDeleteButtonPress") must be available at provider's scope.
<ExampleContext.Provider
value={{
onReportButtonPress,
onDeleteButtonPress,
}}
>
{children}
</ExampleContext.Provider>;
Render SomeCustomComponent in Consumer component. This is the React way of doing things :)

Using a global object in React Context that is not related to state

I want to have a global object that is available to my app where I can retrieve the value anywhere and also set a new value anywhere. Currently I have only used Context for values that are related to state i.e something needs to render again when the value changes. For example:
import React from 'react';
const TokenContext = React.createContext({
token: null,
setToken: () => {}
});
export default TokenContext;
import React, { useState } from 'react';
import './App.css';
import Title from './Title';
import TokenContext from './TokenContext';
function App() {
const [token, setToken] = useState(null);
return(
<TokenContext.Provider value={{ token, setToken }}>
<Title />
</TokenContext.Provider>
);
}
export default App;
How would I approach this if I just want to store a JS object in context (not a state) and also change the value anywhere?
The global context concept in React world was born to resolve problem with passing down props via multiple component layer. And when working with React, we want to re-render whenever "data source" changes. One way data binding in React makes this flow easier to code, debug and maintain as well.
So what is your specific purpose of store a global object and for nothing happen when that object got changes? If nothing re-render whenever it changes, so what is the main use of it?
Prevent re-render in React has multiple ways like useEffect or old shouldComponentUpdate method. I think they can help if your main idea is just prevent re-render in some very specific cases.
Use it as state management libraries like Redux.
You have a global object (store) and you query the value through context, but you also need to add forceUpdate() because mutating the object won't trigger a render as its not part of React API:
const globalObject = { counter: 0 };
const Context = React.createContext(globalObject);
const Consumer = () => {
const [, render] = useReducer(p => !p, false);
const store = useContext(Context);
const onClick = () => {
store.counter = store.counter + 1;
render();
};
return (
<>
<button onClick={onClick}>Render</button>
<div>{globalObject.counter}</div>
</>
);
};
const App = () => {
return (
<Context.Provider value={globalObject}>
<Consumer />
</Context.Provider>
);
};

React Context API with multiple values performance

I'm using the React Context API to store many global state values (around 10 and probably more will be needed) and many components are using them. Unfortunately whenever any of the values change, all components using the useContext hook have to rerender. My current solution is to use useMemo for the return value of the components and useCallback for any complex functions and inside custom hooks I have. This addresses most of my performance concerns, but having to use the useMemo and useCallback all the time is quite annoying and missing one is quite easy. Is there a more professional way to do it?
Here's an example based on my code:
GlobalStateContext.js
import React, { useState } from 'react'
const GlobalStateContext = React.createContext({ })
export const GlobalStateProvider = ({ children }) => {
const [config, setConfig] = useState({
projectInfo: ''
})
const [projectFile, setProjectFile] = useState('./test.cpp')
const [executionState, setExecutionState] = useState("NoProject")
return (
<GlobalStateContext.Provider
value={{
executionState,
config,
projectFile,
setExecutionState,
setConfig,
setProjectFile,
}}
>
{children}
</GlobalStateContext.Provider>
)
}
export default GlobalStateContext
Example.jsx
import React, { useContext } from 'react'
import GlobalStateContext from '../utils/GlobalStateContext.js'
export default Example = () => {
const {
executionState,
setExecutionState,
} = useContext(GlobalStateContext)
return useMemo(
() => (
<div>
The current execution state is: {executionState}
<br />
<button onClick={() => setExecutionState('Running')}>Running</button>
<button onClick={() => setExecutionState('Stopped')}>Stopped</button>
<button onClick={() => setExecutionState('Crashed')}>Crashed</button>
</div>
),
[
executionState,
setExecutionState,
]
)
}
Currently, this problem is unavoidable with context. There is an open RFC for context selectors to solve this, but in the meantime, some workarounds are useContextSelector and Redux, both of which prevent a subscribing component from rendering if the data it's reading did not change.

How to call a method defined in others components

Assuming we have two functional components App and Product. From the Product component you can easily call a method defined in the parent component (App) by simply passing the method as props (methodA={methodA}). Can someone please tell me how to call a method defined in the Cart component from the Product component?
//App.js
import React from "react";
import Cart from "./Cart";
import Order from "./Order";
import Product from "./Product";
const App = props => {
const { order } = props;
const methodA = props => {
console.log(methodA);
}
return (
<div className="container">
<Cart>
{order.map((products, i) => {
return (
<Order key={i}>
{products.map((items, i) => (
<Product item={items} key={i} methodA={methodA} />
))}
</Order>
);
})}
</Cart>
</div>
);
};
export default App;
//Product.js
import React from 'react';
const Product = props => {
return <div className="delete" onClick={props.methodA}></div>;
};
export default Product;
That's why Redux is developed for easier state management. You should have Redux store and actions dispatched within this store. Once you have set up your redux store and actions, you can easily dispatch the same action from multiple components.
You have a few options:
Pass the products to the cart and let it render them (the best way in this case)
Use some state management like context or redux (in this case, an context passed by the cart component would be perfect, then the product component consume and use it functions)

Categories

Resources