This depends on how you get the parameter. There will be times you can't avoid using a .bind
or an arrow function easily but most of the times you can get the parameter somehow. As you can see in @CertainPerformance's answer if you can use this argument in the constructor, you can prefer this way. But there can be other approaches.
For example, assume that you have a list in the state. Instead of mapping this list directly and using a .bind
or an arrow function there, you can pass the list elements to a child component and then use a callback handler there.
class App extends React.Component {
state = {
list: [ "foo", "bar" ],
};
handleClick(el) { console.log( el ) }
render() {
return (
<div>
{this.state.list.map( el => (
<Child key={el} el={el} onClick={this.handleClick} />
) )}
</div>
);
}
}
const Child = ( props ) => {
const handleClick = () => props.onClick( props.el );
return (
<div>
{props.el}
<button onClick={handleClick}>Click</button>
</div>
);
};
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 am updating my answer with a demonstration how using an inline arrow function, or binding it, or using a curried function causes a recreation here.
Assume that you have a component and this component has a child component written as React.PureComponent
. Normally, if this child's props does not change it won't re-render. Cool. We have a class method in our parent component and want to pass this as a handler to our child component. Let's see what is going on here.
First, I don't pass the handler and when you increment the counter in the parent, child component does not rerender again (except the initial render). This is because we defined it as a PureComponent
. We don't want it to be rerendered unless its props changes.
class App extends React.Component {
state = {
counter: 0,
};
increment = () =>
this.setState( currentState => ( {
counter: currentState.counter + 1,
} ) );
handleClick(param) { console.log( param ) }
render() {
return (
<div>
<button onClick={this.increment}>Increment</button>
Counter is: {this.state.counter}
<Child />
</div>
);
}
}
class Child extends React.PureComponent {
render() {
console.log( "child rendered" );
return (
<div>
<button onClick={this.props.onClick}>Click</button>
</div>
);
}
}
ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
As you can see child component isn't rerendered. Now lets do this with our class method, using an inline arrow function.
class App extends React.Component {
state = {
counter: 0,
};
increment = () =>
this.setState( currentState => ( {
counter: currentState.counter + 1,
} ) );
handleClick( param ) { console.log( param ) }
render() {
return (
<div>
<button onClick={this.increment}>Increment</button>
Counter is: {this.state.counter}
<Child onClick={() => this.handleClick( "some param" )} />
</div>
);
}
}
class Child extends React.PureComponent {
render() {
console.log( "child rendered" );
return (
<div>
<button onClick={this.props.onClick}>Click</button>
</div>
);
}
}
ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Ooops, child is rendered when we increment the counter. But, it does not have any relationship to the counter state, we don't want this. So why is it rerendering? This is because we are using an inline arrow function in the onClick
prop it is getting. Since this function is recreated in every render of the parent, its reference changes to a different function and child thinks that it gets a new prop! But in reality it does not get it. We can use the parameter with our handler but there is unnecessary rendering.
Now with the .bind
. I don't use this
in the bind since we don't use this
in our simple method. It just logs a parameter.
class App extends React.Component {
state = {
counter: 0,
};
increment = () =>
this.setState( currentState => ( {
counter: currentState.counter + 1,
} ) );
handleClick( param ) { console.log( param ) }
render() {
return (
<div>
<button onClick={this.increment}>Increment</button>
Counter is: {this.state.counter}
<Child onClick={this.handleClick.bind( null, "some param" )} />
</div>
);
}
}
class Child extends React.PureComponent {
render() {
console.log( "child rendered" );
return (
<div>
<button onClick={this.props.onClick}>Click</button>
</div>
);
}
}
ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Same here, we can use the parameter but there is unnecessary rendering. Now with a curried function.
class App extends React.Component {
state = {
counter: 0,
};
increment = () =>
this.setState( currentState => ( {
counter: currentState.counter + 1,
} ) );
handleClick( param ) {
return function() {
console.log( param )
}
}
render() {
return (
<div>
<button onClick={this.increment}>Increment</button>
Counter is: {this.state.counter}
<Child onClick={this.handleClick( "some param" )} />
</div>
);
}
}
class Child extends React.PureComponent {
render() {
console.log( "child rendered" );
return (
<div>
<button onClick={this.props.onClick}>Click</button>
</div>
);
}
}
ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Surprise! Again unnecessary rendering. Now, for one component this is not so important. But what if your app has hundereds of components like this child?
Now, lets assume I'm getting the param somehow. I am mimicking it with a hardcoded string here.
class App extends React.Component {
state = {
counter: 0,
};
increment = () =>
this.setState( currentState => ( {
counter: currentState.counter + 1,
} ) );
handleClick() { console.log( "some param" ) }
render() {
return (
<div>
<button onClick={this.increment}>Increment</button>
Counter is: {this.state.counter}
<Child onClick={this.handleClick} />
</div>
);
}
}
class Child extends React.PureComponent {
render() {
console.log( "child rendered" );
return (
<div>
<button onClick={this.props.onClick}>Click</button>
</div>
);
}
}
ReactDOM.render( <App />, document.getElementById( "root" ) );
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Duh! No unnecessary rerendering as expected since we used the function reference. I can use the param but life is not so easy and OP actually asking how can we use the parameter without using an inline arrow function, or binding or with a curried function. All the fuss is about this.
Even though we don't pass this handler to a component down, it is still recreated in every render of the parent as we see here. If you have a list of items, lets say 500 of them, and you are mapping them into the buttons in the parent component and use an arrow function, etc here, this means they will be recreated (500 times) in every render!
So, there isn't any easy way of doing this. If our parameter is not coming from the event object then either we use @CertainPerformance's solution or try to change our logic like I do here.