What happens if I pass function component as state variable?
For example, would the below technically work? I tried it and it doesn't seem to work. I get the error props.children isn't a function. Any idea whether this would work?
const App = () => {
[functionComponent, setFunctionComponent] = useState((name) => <div>Hi, {name}</div>)
return <SomeContainerComponent>{functionComponent}</SomeContainerComponent>
}
const SomeContainerComponent = ({ children }) => {
return <div>{children({name: "John"})}</div>;
}
When you pass a function (whether a function that returns JSX or not) to useState, you're telling React to use lazy initialization - to call the function to compute the value for the initial state, rather than to set the state to be the function you passed in.
For what you want, you'd need to pass a function that returns a function, and also capitalize the state, since it's a component.
const App = () => {
const [FunctionComponent, setFunctionComponent] = React.useState(() => () => <div>Hello, World!</div>);
return <FunctionComponent />;
};
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
Related
I have been reading react docs to refresh my concepts. While reading, I came across this statement in context section:
The value argument passed to the function will be equal to the value
prop of the closest Provider for this context above in the tree. If
there is no Provider for this context above, the value argument will
be equal to the defaultValue that was passed to createContext().
So naturally, I created some snippets to actually test the scenario.
My entry point (App.js):
import "./styles.css";
import WearLacoste from "./wearLacoste";
import WearPrada from "./wearPrada";
import OutFitProvider from "./outfitContext";
export default function App() {
return (
<div className="App">
<h1>What to wear?</h1>
<OutFitProvider>
<WearPrada />
</OutFitProvider>
<WearLacoste />
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Notice that I have two components. These components return a string rendered on the browser, telling which dress to wear. One of the component name <WearPrada /> is wrapped by the provider, the other (<WearLacoste/>) is not, to test the defaultValue scenario.
Following is my context file (outfitContext.js):
import React, { useContext, useState } from "react";
const MyOutfitContext = React.createContext("Lacoste");
//a custom hook to consume context
export const useOutfit = () => {
return useContext(MyOutfitContext);
};
const OutfitContextProvider = ({ children }) => {
const [outfit, setOutfit] = useState("Prada");
return (
<MyOutfitContext.Provider value={{ outfit }}>
{children}
</MyOutfitContext.Provider>
);
};
export default OutfitContextProvider;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
And finally both of my components, which do the exact same thing.
wearPrada.js :
import { useOutfit } from "./outfitContext";
const WearPrada = () => {
const { outfit } = useOutfit();
console.log("Outfit expects Prada", outfit);
return <h1>Wearing {outfit} RN </h1>;
};
export default WearPrada;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
wearLacoste.js :
import { useOutfit } from "./outfitContext";
const WearLacoste = () => {
const { outfit } = useOutfit();
console.log("Outfit expects Lacoste", outfit);
return <h1>Wearing {outfit} RN </h1>;
};
export default WearLacoste;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
My problem is:
The component wrapped in the provider is behaving as expected. It gets the state value initialised in context. But as claimed in the document, the component not wrapped in the provider still gets an undefined value instead of the defaultValue which is Lacoste (see outfitContext.js). Am I doing anything wrong here? Thanks in advance for the help.
It seems that you have mixed the object { outfit: string } with a simple string. The context value is expected to be an object everywhere but when setting the default value you use a string.
I believe what you want is const MyOutfitContext = React.createContext({ outfit: "Lacoste" });
I have gone through a couple of articles on useCallback and useMemo on when to use and when not to use but I have mostly seen very contrived code. I was looking at a code at my company where I have noticed someone have done this:
const takePhoto = useCallback(() => {
launchCamera({ mediaType: "photo", cameraType: "front" }, onPickImage);
}, []);
const pickPhotoFromLibrary = async () => {
launchImageLibrary({ mediaType: "photo" }, onPickImage);
}
const onUploadPress = useCallback(() => {
Alert.alert(
"Upload Photo",
"From where would you like to take your photo?",
[
{ text: "Camera", onPress: () => takePhoto() },
{ text: "Library", onPress: () => pickPhotoFromLibrary() },
]
);
}, [pickPhotoFromLibrary, takePhoto]);
This is how onUploadPress is called:
<TouchableOpacity
style={styles.retakeButton}
onPress={onUploadPress}
>
Do you think this is the correct way of calling it? Based on my understanding from those articles, this looks in-correct. Can someone tell me when to use useCallback and also maybe explain useCallback in more human terms?
Article I read: When to useMemo and useCallback.
useCallback returns a normal JavaScript function regarding how to use it. It is the same as the one it gets as first parameter regarding what it does. The difference is that this function doesn't get recreated on a new memory reference every time the component re-renders, while a normal function does. It gets recreated on a new reference if one of the variables inside useCalback's dependency array changes.
Now, why would you wanna bother with this? Well, It's worth it whenever the normal behavior of a function is problematic for you. For example, if you have that function in the dependency array of an useEffect, or if you pass it down to a component that is memoized with memo.
The callback of an useEffect gets called on the first render and every time one of the variables inside the dependency array changes. And since normally a new version of that function is created on every render, the callback might get called infinitely. So useCallback is used to memoize it.
A memoized component with memo re-renders only if its state or props changes, not because its parent re-renders. And since normally a new version of that passed function as props is created, when the parent re-renders, the child component gets a new reference, hence it re-renders. So useCallback is used to memoize it.
To illustrate, I created the below working React application. Click on that button to trigger re-renders of the parent and watch the console. Hope it clears things up!
const MemoizedChildWithMemoizedFunctionInProps = React.memo(
({ memoizedDummyFunction }) => {
console.log("MemoizedChildWithMemoizedFunctionInProps renders");
return <div></div>;
}
);
const MemoizedChildWithNonMemoizedFunctionInProps = React.memo(
({ nonMemoizedDummyFunction }) => {
console.log("MemoizedChildWithNonMemoizedFunctionInProps renders");
return <div></div>;
}
);
const NonMemoizedChild = () => {
console.log("Non memoized child renders");
return <div></div>;
};
const Parent = () => {
const [state, setState] = React.useState(true);
const nonMemoizedFunction = () => {};
const memoizedFunction = React.useCallback(() => {}, []);
React.useEffect(() => {
console.log("useEffect callback with nonMemoizedFunction runs");
}, [nonMemoizedFunction]);
React.useEffect(() => {
console.log("useEffect callback with memoizedFunction runs");
}, [memoizedFunction]);
console.clear();
console.log("Parent renders");
return (
<div>
<button onClick={() => setState((prev) => !prev)}>Toggle state</button>
<MemoizedChildWithMemoizedFunctionInProps
memoizedFunction={memoizedFunction}
/>
<MemoizedChildWithNonMemoizedFunctionInProps
nonMemoizedFunction={nonMemoizedFunction}
/>
<NonMemoizedChild />
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
It's to know that memoizing is not free, doing it wrong is worse than not having it. In your case, using useCallback for onUploadPress is a waste because a non memoized function, pickPhotoFromLibrary, is in the dependency array. Also, it's a waste if TouchableOpacity is not memoized with memo, which I'm not sure it's.
As a side note, there is useMemo, which behaves and is used like useCallback to memoize non-function but referenced values such as objects and arrays for the same reasons, or to memoize any result of a heavy calculation that you don't wanna repeat between renders.
A great resource to understand React render process in depth to know when to memorize and how to do it well: React Render.
In simple words, useCallback is used to save the function reference somewhere outside the component render so we could use the same reference again. That reference will be changed whenever one of the variables in the dependencies array changes.
As you know React tries to minimize the re-rendering process by watching some variables' value changes, then it decides to re-render not depending on the old-value and new-value of those variables.
So, the basic usage of useCallback is to hold old-value and the new-value equally.
I will try to demonstrate it more by giving some examples in situations we must use useCalback in.
Example 1: When the function is one of the dependencies array of the useEffect.
function Component(){
const [state, setState] = useState()
// Should use `useCallback`
function handleChange(input){
setState(...)
}
useEffect(()=>{
handleChange(...)
},[handleChange])
return ...
}
Example 2: When the function is being passed to one of the children components. Especially when it is being called on their useEffect hook, it leads to an infinite loop.
function Parent(){
const [state, setState] = useState()
function handleChange(input){
setState(...)
}
return <Child onChange={handleChange} />
}
function Child({onChange}){
const [state, setState] = useState()
useEffect(()=>{
onChange(...)
},[onChange])
return "Child"
}
Example 3: When you use React Context that holds a state and returns only the state setters functions, you need the consumer of that context to not rerender every time the state update as it may harm the performance.
const Context = React.createContext();
function ContextProvider({children}){
const [state, setState] = useState([]);
// Should use `useCallback`
const addToState = (input) => {
setState(prev => [...prev, input]);
}
// Should use `useCallback`
const removeFromState = (input) => {
setState(prev => prev.filter(elem => elem.id !== input.id));
}
// Should use `useCallback` with empty []
const getState = () => {
return state;
}
const contextValue= React.useMemo(
() => ({ addToState , removeFromState , getState}),
[addToState , removeFromState , getState]
);
// if we used `useCallback`, our contextValue will never change, and all the subscribers will not re-render
<Context.Provider value={contextValue}>
{children}
</Context.Provider>
}
Example 4: If you are subscribed to the observer, timer, document events, and need to unsubscribe when the component unmounts or for any other reason. So we need to access the same reference to unsubscribe from it.
function Component(){
// should use `useCallback`
const handler = () => {...}
useEffect(() => {
element.addEventListener(eventType, handler)
return () => element.removeEventListener(eventType, handler)
}, [eventType, element])
return ...
}
That's it, there are multiple situations you can use it too, but I hope these examples demonstrated the main idea behind useCallback. And always remember you don't need to use it if the cost of the re-render is negligible.
Here is my simple React-app:
let idCounter = 0;
export default function App() {
const id = useMemo(() => {
console.log("useMemo");
return idCounter++;
}, []);
console.log("render", id);
useEffect(() => {
console.log("useEffect", id);
});
return id;
}
https://codesandbox.io/s/morning-bush-swky8
This is the console output:
useMemo
render 0
useEffect 1
Why in useEffect id is equal to 1?
Seems like the component has been rendered twice, but why useMemo and useEffect haven't been called for the second time? How id became 1?
From React Docs - Strict Mode:
Starting with React 17, React automatically modifies the console
methods like console.log() to silence the logs in the second call to
lifecycle functions. However, it may cause undesired behavior in
certain cases where a workaround can be used.
Your component is indeed rendered twice but the log statements are hidden by React during the second re-render caused by StrictMode.
You will get the expected output if you remove the StrictMode.
Another option is to use a different method on console object for logging, such as console.dir.
let idCounter = 0;
function App() {
const id = React.useMemo(() => {
console.dir("useMemo");
return idCounter++;
}, []);
console.dir("render");
console.dir(id);
React.useEffect(() => {
console.log("useEffect", id);
});
return id;
}
ReactDOM.render(
<React.StrictMode>
<App/>
</React.StrictMode>,
document.querySelector("#root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.min.js"></script>
<div id="root"></div>
I have a React component (functional) that contains a child component modifying the state of the parent component. I am using the hook useState for this.
After the state change, there is a "Next" button in the parent component that executes a function referencing the updated state. The problem is this next function uses the old state from before the state was modified by the child component.
I can't use useEffect here as the function needs to execute on the click of the "Next" button and not immediately after the state change. I did some digging about JavaScript closures, but none of the answers address my specific case.
Here's the code
const ParentComponent = () => {
const [myState, setMyState] = useState(0);
const handleNext = () => {
console.log(myState); // prints 0 which is the old value
}
return (
<ChildComponent modifyState = {setMyState} />
<Button onClick={handleNext} > Next </Button>
)
}
export default ParentComponent;
BTW there are no errors.
It's a little difficult to understand without your ChildComponent code. setMyState suggests that you need to update the increase the state by one when you click the next button, but you can't do that without also passing in the state itself.
An easier (imo) solution is to pass a handler down to the child component that is called when the next button is clicked. The handler then sets the state.
const {useState} = React;
function ChildComponent({ handleUpdate }) {
function handleClick() {
handleUpdate();
}
return <button onClick={handleClick}>Click</button>
}
function Example() {
const [myState, setMyState] = useState(0);
function handleUpdate() {
setMyState(myState + 1);
}
function handleNext() {
console.log(myState);
}
return (
<div>
<ChildComponent handleUpdate={handleUpdate} />
<button onClick={handleNext}>Next </button>
</div>
)
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
try to modify like this
<ChildComponent modifyState={(value) => setMyState(value)} />
I'm running lint with my React app, and I receive this error:
error JSX props should not use arrow functions react/jsx-no-bind
And this is where I'm running the arrow function (inside onClick):
{this.state.photos.map(tile => (
<span key={tile.img}>
<Checkbox
defaultChecked={tile.checked}
onCheck={() => this.selectPicture(tile)}
style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
/>
<GridTile
title={tile.title}
subtitle={<span>by <b>{tile.author}</b></span>}
actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
>
<img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
</GridTile>
</span>
))}
Is this a bad practice that should be avoided? And what's the best way to do it?
Why you shouldn't use inline arrow functions in JSX props
Using arrow functions or binding in JSX is a bad practice that hurts performance, because the function is recreated on each render.
Whenever a function is created, the previous function is garbage collected. Rerendering many elements might create jank in animations.
Using an inline arrow function will cause PureComponents, and components that use shallowCompare in the shouldComponentUpdate method to rerender anyway. Since the arrow function prop is recreated each time, the shallow compare will identify it as a change to a prop, and the component will rerender.
As you can see in the following 2 examples - when we use inline arrow function, the <Button> component is rerendered each time (the console shows the 'render button' text).
Example 1 - PureComponent without inline handler
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
onClick = () => this.setState((prevState) => ({
counter: prevState.counter + 1
}));
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ this.onClick } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Example 2 - PureComponent with inline handler
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ () => this.setState((prevState) => ({
counter: prevState.counter + 1
})) } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Binding methods to this without inlining arrow functions
Binding the method manually in the constructor:
class Button extends React.Component {
constructor(props, context) {
super(props, context);
this.cb = this.cb.bind(this);
}
cb() {
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
Binding a method using the proposal-class-fields with an arrow function. As this is a stage 3 proposal, you'll need to add the Stage 3 preset or the Class properties transform to your babel configuration.
class Button extends React.Component {
cb = () => { // the class property is initialized with an arrow function that binds this to the class
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
Function Components with inner callbacks
When we create an inner function (event handler for example) inside a function component, the function will be recreated every time the component is rendered. If the function is passed as props (or via context) to a child component (Button in this case), that child will re-render as well.
Example 1 - Function Component with an inner callback:
const { memo, useState } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1); // the function is recreated all the time
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
To solve this problem, we can wrap the callback with the useCallback() hook, and set the dependencies to an empty array.
Note: the useState generated function accepts an updater function, that provides the current state. In this way, we don't need to set the current state a dependency of useCallback.
Example 2 - Function Component with an inner callback wrapped with useCallback:
const { memo, useState, useCallback } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => setCounter(counter => counter + 1), []);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Using inline functions like this is perfectly fine. The linting rule is outdated.
This rule is from a time when arrow functions were not as common and people used .bind(this), which used to be slow. The performance issue has been fixed in Chrome 49.
Do pay attention that you do not pass inline functions as props to a child component.
Ryan Florence, the author of React Router, has written a great piece about this:
https://reacttraining.com/blog/react-inline-functions-and-performance/
This is because an arrow function apparently will create a new instance of the function on each render if used in a JSX property. This might create a huge strain on the garbage collector and will also hinder the browser from optimizing any "hot paths" since functions will be thrown away instead of reused.
You can see the whole explanation and some more info at https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
Why shouldn't JSX props use arrow functions or bind?
Mostly, because inline functions can break memoization of optimized components:
Traditionally, performance concerns around inline functions in React have been related to how passing new callbacks on each render breaks shouldComponentUpdate optimizations in child components. (docs)
It is less about additional function creation cost:
Performance issues with Function.prototype.bind got fixed here and arrow functions are either a native thing or are transpiled by babel to plain functions; in both cases we can assume it’s not slow. (React Training)
I believe people claiming function creation is expensive have always been misinformed (React team never said this). (Tweet)
When is the react/jsx-no-bind rule useful?
You want to ensure, that memoized components work as intended:
React.memo (for function components)
PureComponent or custom shouldComponentUpdate (for class components)
By obeying to this rule, stable function object references are passed. So above components can optimize performance by preventing re-renders, when previous props have not changed.
How to solve the ESLint error?
Classes: Define the handler as method, or class property for this binding.
Hooks: Use useCallback.
Middleground
In many cases, inline functions are very convenient to use and absolutely fine in terms of performance requirements. Unfortunately, this rule cannot be limited to only memoized component types. If you still want to use it across-the-board, you could e.g. disable it for simple DOM nodes:
rules: {
"react/jsx-no-bind": [ "error", { "ignoreDOMComponents": true } ],
}
const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
To avoid creating new functions with the same arguments, you could memoize the function bind result, here is a simple utility named memobind to do it: https://github.com/supnate/memobind
You can remove this error by wrapping the function inside useCallback.
For those wondering when you need to pass data in the callback. Ex.:
const list = todos.map((todo, index) => (
<Todo
onClick={() => { onTodoClick(todo.id, todo.title, index) }
/>
));
Solution
According to the official documentation, you should do:
Move the function arguments into the children component:
const list = todos.map((todo, index) => (
<Todo
onClick={onTodoClick}
todoId={todo.id}
todoTitle={todo.title}
indexOnList={index}
/>
));
In the children component (<Todo />), pass the arguments in the call:
function Todo(props) {
// component properties
const { onClick, todoId, todoTitle, indexOnList } = props;
// we move the call from the parent to the children
const handleClick = useCallback(() => {
onClick(todoId, todoTitle, indexOnList);
}, [todoId, todoTitle, indexOnList]);
return (
<div onClick={handleClick}>
{/* the rest of the component remains the same */}
</div>
);
}
Is this the best solution?
I dislike this solution. You end up with parent's data and logic in the children component. This makes the children component dependent on the parent component, breaking the dependency rule.
That's a big no-no for me.
What I do is just disable this rule. According to Ryan Florence (React Router author), this is not a big deal anyway:
https://medium.com/#ryanflorence/react-inline-functions-and-performance-bdff784f5578
The new (in beta, jan 2023) React tutorial uses both functions and arrow functions as JSX props. This hints strongly at this not being a major concern.
You can use arrow functions using react-cached-handler library, no need to be worried about re-rendering performance :
Note : Internally it caches your arrow functions by the specified key,
no need to be worried about re-rendering!
render() {
return (
<div>
{this.props.photos.map((photo) => (
<Photo
key={photo.url}
onClick={this.handler(photo.url, (url) => {
console.log(url);
})}
/>
))}
</div>
);
}
Other features:
Named handlers
Handle events by arrow functions
Access to the key, custom arguments and the original event
Component rendering performace
Custom context for handlers
You may also see this this error if the function you are using in your onClick handler is a regular (non-inline) function defined outside of the render method but using the function keyword.
Declare your handler function as an arrow function using const, outside of you render method, and React will stop complaining...