Imagine that I have a function which dynamically generates some of a component's props, and I want to pass them all at once without being explicit about every prop the function could generate. Normally you can do this with the spread operator, but the issue with the spread operator is that it created a new object each time. This would mean that (if I understand correctly) during the React Reconciliation, the component would have new props every time and would rerender every time, even if the props the function generated are the same.
Here's a concrete example:
const generateProps = () => ({foo: 'bar'});
const ParentComponent = () => ({
const someProps = generateProps();
return (
<SomeComponent><ChildComponent {...someProps} otherProp='hello world'/></SomeComponent>
)
})
Here ChildComponent would render every time ParentComponent would render (right?). One thing I know you could do is wrap the ChildComponent with a React.memo and do a deeper comparison of the props (passing a custom comparison function to it), but what if you don't have control over ChildComponent? Are you forced into being explicit? Or am I incorrect and ChildComponent wouldn't rerender in this example (assuming ChildComponent simply consumes the props and doesn't use any contexts or anything).
Thank you!
You have it wrong. Reconciliation doesn't look at the props. It mainly looks at the component type, e.g.
if on one render you render
<Comp1/>
and on next render, on the same place in the component tree, you render:
<Comp2/>
it will unmount Comp1 and mount Comp2 because the type of components is different. If component types are the same, it will update existing one. There are some more details but you can check them yourself.
Furthermore, the props are also compared in a shallow way by default if you use React.memo, so if on one render you pass
let y = {a:1,b:2};
....
<Comp1 {...y}/>
and on next render you pass
let x = {a:1,b:2};
...
<Comp1 {...x}/>
Default comparison of React.memo will assume that props didn't change, because a and b have same values.
You can verify here, clicking on the div doesn't re render the Test component:
let Test = React.memo(props => {
console.log(props);
return <div>{props.a}</div>;
});
function App() {
let [state, setState] = React.useState({ a: 123 });
return (
<div
onClick={() => {
setState({ a: 123 });
}}
>
<h1>Hello StackBlitz!</h1>
<Test {...state} />
<p>Start editing to see some magic happen :)</p>
</div>
);
}
ReactDOM.render(
<App />,
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>
I have a pair of functional components and want to pass one in as an argument to the other as shown below:
const compA = () => ( <div> Hello World! </div>);
const compB = (AnotherComp) => ( <AnotherComp />);
compB(compA);
The above snippet is a dumbed-down version of what I'm after, as each component has its own set of React hooks.
Google-fu turned up many guides on Higher-Order Components, but most address the difference between HOC and react hooks, or for getting props from a class component into a functional sub-component.
Is there a way to use Higher-order functional components, as shown above?
I hope I'm not misunderstanding your question, but it seems to me that for something to be considered higher order, you need a function to return a function, as in makeBlue, below.
const Button = ({ children, ...props }) => (
<button {...props}>{children}</button>
);
const makeBlue = (Component) => (props) =>
<Component style={{ background: "blue" }} {...props} />;
const BlueButton = makeBlue(Button);
Yes, they work with functional components.
I'm learning a udemy course and in it we create this which is a function that takes a component and a class name as the arguments and returns a wrapped JSX having the WrappedComponent nested inside a <div>.
This is going to be real easy but I don't understand the syntax for props => (). Why do we use the props just after return statement? I understand that inside ( ) is the JSX to return. Maybe someone can easily explain why the props is there and how it gets handled?
import React from 'react';
const withClass = (WrappedComponent,className) => {
return props => (
<div className={className}>
<WrappedComponent/>
</div>
);
};
export default withClass;
The example what you copied is a common react pattern called HOC (Higher Order Component). What happens here is the following we have a function which takes a component as an argument ( WrappedComponent ) and we are returning a definition of a new component which will wrap our WrappedComponent. You could wrote the following as well
const withClass = (WrappedComponent,className) => {
return class extends React.Component {
render() {
return(
<div className={className}>
<WrappedComponent/>
</div>
)
}
}
};
So basically the syntax props => () is just a way to define a new component. It is worth to mention that the syntax itself is used to declare an arrow function.
Its possible to transfer component in props with children method?
I have to components:
let a = (<div>
<button onClick={TContainer.METHOD}>TuCLIK</button>
</div>);
<TContainer data={ restaurantList } component={a}/>
I want to call method in childen but create element in parent. I want to pass this component on props.
If its possible i dont know what writing in TContainer.METHOD to call childen method
You are not passing a component in your props, it's an expression.
A component should be either a class the extends React.Component or a function that returns a jsx markup.
Now when we know that components are just functions, we know that we can pass parameters to them, hence we can pass a function reference as a parameter:
let A = (onClick) => <div><button onClick={onClick}>TuCLIK</button></div>;
<TContainer data={ restaurantList } component={<A onClick={TContainer.METHOD} />}/>
Note that components should be capitalized.
Edit
As a followup to your comment, sorry but i misunderstood your question i guess.
You can't pass a reference of a method from a React component like that.
We can use couple of approaches regarding this scenario, one of them
is to use this.props.children and pass the child component as a
child.
For example - <Parant><Child/></Parent>
We can pass the child component as a prop.
For example - <Parent component={Child} /> or <Parent component={<Child />} />
We can write the parent component as a HOC and wrap the child
with it.
For example - Parent(Child)
In all the above examples you can't pass directly a reference of a function that is declared inside the parent's scope (as a prop to the child).
In order to pass the child a prop within the parent's internal scope you should do it inside the render method.
For example:
render() {
return (
<div>
<this.props.component onClick={this.handleClick}/>
</div>
);
}
This is a snippet that demonstrate one of the examples above:
const Child = ({onClick}) => <div onClick={onClick}>Im a child, Click me!</div>
class Parent extends React.Component {
constructor(props){
super(props);
this.state = {
counter: 0
}
this.addCount = this.addCount.bind(this);
}
addCount(){
this.setState({counter: this.state.counter + 1});
}
render() {
return (
<div>
<div>{`Count = ${this.state.counter}`}</div>
<this.props.component onClick={this.addCount}/>
</div>
);
}
}
const App = () => <Parent component={Child} />;
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
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...