Create function that can pass a parameter without making a new component - javascript

My question is to do with the issue React has for binding functions in the render function.
The following is not good practice:
render() {
<div onClick={this.callFunction.bind(this)}/>
}
as each re render would add a new function to the page, eventually causing the browser to run out of memory.
The solution is to do this:
constructor() {
this.callFunction = this.callFunction.bind(this);
}
render() {
<div onClick={this.callFunction}/>
}
The problem with this is when I want to pass a value into the function.
I know I can make the div a child component, and pass the parameter in through the callBack, but this does not seem sensible if the div is only being used once in the whole application. I accept I could make this work, but this is not the scope of this question.
I also know that this solution:
render() {
<div onClick={() => this.callFunction.call(this, param)}/>
}
Is no better, as it is still creating a new function.
So my question is, how can I create a function that I can pass a parameter into without making a new component, and without binding a new funciton on each render?

You can't avoid creating a second component as you need to pass a function reference as an event handler, this will be executed by the browser when the event triggers.
So the problem is not the binding but the fact that you need to pass a reference, and references can't receive parameters.
EDIT
By the way, if you don't like the syntax and noise of binding or anonymous arrow functions you can use currying.
I posted an example in a different question if you find it interesting. this won't solve the problem though, it's just another approach to pass a new reference (which i find it to be the most terse)

You can change the declaration of callFunction to be an arrow function, which implictly binds the scope, like so:
callFunction = () => {
console.log('hi');
};
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
Then your original render function would work as expected!

Use Side effect
Side effect is something that a function use that comes from outside but not as argument. Now this mechanism is majorly used in Redux/Flux where the entire state is stored in a Store and every component fetches their state from it.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
handlerProps: {
onClick: { count: 0},
onChange: { count: 0}
}
}
}
onClickHandler = () => {
const state = this.state.handlerProps.onClick;
console.log('onClick', state.count);
}
onChangeHandler = (value) => {
const state = this.state.handlerProps.onChange;
console.log('onClick', state.count);
this.setState({value: value})
}
buttonClick = () => {
const random = Math.ceil(Math.random()* 10) % 2;
const handler = ['onClick', 'onChange'][random];
const state = this.state.handlerProps;
state[handler].count++;
console.log('Changing for event: ', handler);
this.setState({handlerProps: state});
}
render () {
return (
<div>
<input onClick={this.onClickHandler} onChange={this.onChangeHandler} />
<button onClick={ this.buttonClick }>Update Props</button>
</div>
)
}
}
ReactDOM.render(<MyComponent/>, document.querySelector('.content'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
<div class='content' />

The only way I know of is to create a new React Component which takes the value and the event handler as props.
This way, the handler as a function remains static, and since the value is passed down separately (in its own prop) you don't have any functions being re-instanciated. Because you don't bind anything nor create a new function each time.
Here's an example:
We have two buttons. The first one prints the current state variable value and the other increments it by one.
Normally, if we had done this with onClick={() => this.print(this.state.value)} we would get a new instance of this function, each time the MyApp component would re-render. In this case, it would re-render each time we increment the value with the setState() inside this.increment.
However, in this example, no new instance of this.print happens because we are only passing its reference to the button. In other words, no fat arrow and no binding.
In the <Button /> component, we have a <button> to which event handler we pass a reference to a function - just like we did in <MyApp />. However, here we know exactly what to pass to the function. As such, we have myHandler trigger this.props.handler(this.props.value).
class MyApp extends React.Component {
constructor() {
super();
this.state = {
value: 0
};
}
print = (value) => {
console.log(value);
}
increment = () => {
// This will trigger a re-render, but none of the functions will be reinstanciated!
this.setState((prevState) => ({value: prevState.value + 1}));
}
render() {
// Note that both handlers below are passed just the references to functions. No "bind" and no fat arrow.
return(
<div>
<Button handler={this.print} value={this.state.value}>Print</Button>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
class Button extends React.Component {
// Clicking the button will trigger this function, which in turn triggers the function with the known argument - both of which were passed down via props.
myHandler = () => this.props.handler(this.props.value);
render() {
// Note again that the handler below is just given the reference to a function. Again, not "bind" nor fat arrow.
return(
<button onClick={this.myHandler}>{this.props.children}</button>
);
}
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<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="app"></div>
Though quite tedious, it is an effective solution. That being said, even if you do create a new function each time you render, the performance implications are minimal. From the official docs:
The problem with this syntax is that a different callback is created each time the LoggingButton renders. In most cases, this is fine.

Related

How to change the state from two different function call?

How can i modify state from two different function call? Following code gives me error'Maximum update depth exceeded.'
class App extends Component {
// fires before component is mounted
constructor(props) {
// makes this refer to this component
super(props);
// set local state
this.state = {
date: new Date(),
myQuestions:myQuestion,
counter :0,
activeQuestion:-1,
};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
this.handleClick = this.begin.bind(this)
}
begin(){
this.setState({activeQuestion:1})
}
handleClick() {
if(this.state.activeQuestion <= myQuestion.length){
this.setState(prevState => ({
counter: this.state.counter + 1,
activeQuestion:this.state.activeQuestion+1
}));
}
render() {
return (
<div className="App">
<div id = "myQuiz">
<div class ="intro {{ (activeQuestion > -1)? 'inactive':'active' }}">
<h2>Welcome</h2>
<p>Click begin to test your knowledge</p>
<p class = "btn" onClick={this.begin('begin')}>Begin</p>
</div>
<div>
<button onClick={this.handleClick}></button>
</div>
What will be the right way to change state from different function call?
You are not passing function to cllick handler. Instead you are calling the function like
onClick={this.begin('begin')}
This line is causing an infinite loop because calling this function is updating the state, which in turn is calling the render function. Change this to
onClick={this.begin}
If you want to pass the parameter to handler then
onClick={() => this.begin('begin')}
You have multiple issues in your code.
You override this.handleClick with the bound version of begin() in your constructor and this.begin will still not be bound to this.
Where does myQuestion come from in the constructor? Is it a globally scoped variable?
In your handleClick() you do not use the prevState to calculate the next state. Instead you use this.state which may lead to bad behavior if react batches multiple calls to setState().
Why do you use an onClick handler on a paragraph? Shouldn't this be a button?
You have to use className instead of class in jsx because class is a reserved keyword in javascript.
Also the class you are trying to set will be literally the text intro {{ (activeQuestion > -1)? 'inactive':'active' }}. This is for sure not what you where trying to achieve.

React js event handler in Smart Components with arrow functions and Default params

As you might know we can create arrow functions as event handlers without binding this (with Babel Stage 2 support). In React with Redux we have Smart and Dumb Components. Hence I would like to create state modifying event handlers in the Smart Components with default params (for reusability) and pass them on to Dumb Components as props as detailed below : -
// SmartComponent.js
class SmartComponent extends Component {
state = {count: 0};
handleClick = (inc = 1) => this.setState({count: this.state.count + inc});
render = () => <DumbComponent handleClick={this.handleClick} />
}
// DumbComponent.js
function DumbComponent(props) {
return <button onClick={props.handleClick}>Submit</button>;
}
The problem is I get a console log stating that the passed arrow function is an object and not a function. It would work as expected if we create an arrow function as below, or if the initial arrow function doesn't have a default param in the DumbComponent.
// DumbComponent.js
function DumbComponent(props) {
return <button onClick={() => this.props.handleClick()}>Submit</button>;
}
Any ideas/suggestions to make it work without a second arrow function would be much appreciated.

Using setInterval in React delays component render

I have a parent component, <App>:
constructor() {
super();
this.state = {
transporterPos: 0
}
this.tick = this.tick.bind(this);
}
componentDidMount() {
this.timerId = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerId);
}
tick() {
let transporterPos = this.state.transporterPos;
transporterPos++;
if (transporterPos > 7) {
transporterPos = 0;
}
this.setState({ transporterPos: transporterPos });
}
render() {
return (
<div>
<Staves transporterPos={this.state.transporterPos}/>
</div>
);
}
The <Staves> component contains several <Stave> components, each of which contains several <Note> components. Each <Note> component is injected with a className conditional on its active property being true:
<div className="noteContainer" onClick={this.handleClick}>
<div className={"note" + (this.props.active ? ' active' : '')}></div>
</div>
handleClick() is a method that toggles a <Note>'s active property. I'm not including all the code here to make this more readable. The problem is that when clicking on a <Note>, although its active property changes immediately, the styling given by the conditional className of 'active' is not visible until the component is re-rendered at the next "tick" of the setInterval method. In other words, rendering only seems to happen once every 1000ms. I would like it to happen immediately. Am I using setInterval wrong?
Edit:
In response to comments, here is the handleClick method (in <Note>):
handleClick() {
this.props.toggleActive(this.props.pos);
}
This calls toggleActive in <Stave>:
toggleActive(pos) {
this.props.notes[pos].active = !this.props.notes[pos].active;
}
props.notes here is part of <App>'s state, which is passed down to <Stave> (and which I didn't include in this question for the sake of brevity).
toggleActive(pos) {
this.props.notes[pos].active = !this.props.notes[pos].active;
}
The reason a re-render isn't being triggered is because this.props is mutated directly instead of with setState. Move toggleActive further up to where you can use setState.
If necessary you can pass the function as a prop to the child component and call it via this.props.toggleActive()
Besides not triggering a re-render, another reason this.props should never be mutated directly is because your changes will get overwritten whenever the parent changes state and passes props to its children.

no argument props method does not get called from child component in react

I am new to React and js I need some clarifications on binding of methods in components
I have 2 components ParentComponent and ChildComponent
Parent
var ParentComponent = React.createClass({
methodArg: function (value) {
console.log("methodArg called", value);
},
methodNoArg: function () {
console.log("methodNoArg called");
},
render: function () {
return <ChildComponent m1={(value) => this.methodArg(value)} m2={() => this.methodNoArg} />
}
})
Child
var ChildComponent = React.createClass({
render: function () {
return (
<div>
<button onClick={()=>this.props.m1(100)}>Call M1</button>
<button onClick={()=>this.props.m2()}>Call M2</button>
</div>
)
}
})
When i click Call M1 button, methodArg() of parent is getting called.
But when I click Call M2 methodNoArg() is not getting called. What is the issue in this ?
When I pass methodNoArg to Child , it is getting called
<ChildComponent m1={this.methodArg()} m2={this.methodNoArg} />
But methodArg() is getting called without clicking the button , its getting called everytime when the child component is rendered.
<button onClick={()=>this.props.m1(100)}>Call M1</button>
Your above line is saying evaluate the m1 method and assign the result to onClick. So when you refresh your page you console.log statement with value you passed gets printed but it will never gets called again no matter how many times you click the button as onClick now does not have method assigned to it now.
You can achieve what you want by removing that parenthesis which is calling the method automatically without clicking.
Here is working jsfiddle code link :
http://jsfiddle.net/fp0LvkLg/
This is because of the way you assign your method to a prop
m2={() => this.methodNoArg}
is (kind of if we omit the intricacies of this) equivalent to
m2={function() {return this.methodNoArg}}
So your prop is a function, which returns a function, which in turn is never called.
You want to simply assign your function to a prop like this:
m2={this.methodNoArg}

Passing parameter in ReactJS onclick

I have this React component that has items generated from a list (with a map function). Each of those elements has a button. I want this button's onclick button to pass in a parameter to identify which list item's button was clicked.
It looks something like this.
var Component = React.createClass({
assignItem: function(item){
this.setState({item:item})
},
render: function(){
var listItems = list.map(function(item){
return <div>{item}
<button onClick={this.assignItem(item)>Click</button>
</div>
})
return <div>{listItems}</div>
}
})
Of course that doesn't work. The error message says that this.assignItem is not a function.
I know the official React documentation suggests this:
var handleClick = function(i, props) {
console.log('You clicked: ' + props.items[i]);
}
function GroceryList(props) {
return (
<div>
{props.items.map(function(item, i) {
return (
<div onClick={handleClick.bind(this, i, props)} key={i}>{item}</div>
);
})}
</div>
);
}
ReactDOM.render(
<GroceryList items={['Apple', 'Banana', 'Cranberry']} />, mountNode
);
but that works with a function outside of the component. Since I want my function to manipulate the sate, I want to keep it within the React component.
How do I do this?
Original accepted answer:
You can bind to a function on this just like the React example, however since you are rendering in a map callback you need to either pass in thisArg or use a fat arrow function:
var Component = React.createClass({
assignItem: function(item){
this.setState({item:item})
},
render: function(){
// bind to this.assignItem
var listItems = list.map(function(item){
return <div>{item}
<button onClick={this.assignItem.bind(this, item)}>Click</button>
</div>
}, this); // pass in this, or use fat arrow map callback
return <div>{listItems}</div>
}
})
Update 2017:
This is an old question using an old React API and an accordingly old answer. Today you should be using the class or functional React component API. For passing arguments to click handlers you can just write an inline fat arrow function and call through with whatever params you want. The above example ends up like this:
class MyComponent extends React.Component { // or React.PureComponent
assignItem = item => { // bound arrow function handler
this.setState({ item: item });
}
render() {
var listItems = list.map(item => {
// onClick is an arrow function that calls this.assignItem
return <div>{item}
<button onClick={e => this.assignItem(item)}>Click</button>
</div>
});
return <div>{ listItems }</div>
}
}
Note: The assignItem handler must be bound, which is done here using an arrow function as a class property.

Categories

Resources