Trying to figure out the basics of React.
Looking at the second example on this page: https://facebook.github.io/react/
I see that the tick() function sets the state of the Timer class, incrementing the previous value by one.
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {secondsElapsed: 0};
}
tick() {
this.setState((prevState) => ({
secondsElapsed: prevState.secondsElapsed + 1
}));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
);
}
}
ReactDOM.render(<Timer />, mountNode);
However, when I tried to implement my own simple Counter class, it failed and I got a console error saying Cannot read property setState of undefined.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
}
increment(prevState) {
this.setState((prevState) => ({
count: prevState.count + 1
}));
}
render() {
return (
<div className="main">
<button onClick={this.increment}>{this.state.count}</button>
</div>
)
}
}
Some Googling reveals that I have to bind this to the increment function. But why was that not required in the first example that I saw? I copied the code to CodePen and it ran fine with React 15.3.1 I cannot find anything resembling binding in that example. Only after I added binding code in the constructor did things start working in my example.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
// THIS ONE LINE FIXED THE ISSUE
this.increment = this.increment.bind(this);
}
increment(prevState) {
this.setState((prevState) => ({
count: prevState.count + 1
}));
}
render() {
return (
<div className="main">
<button onClick={this.increment}>{this.state.count}</button>
</div>
)
}
}
Answering your question: the first example uses arrow functions, that automatically performs context binding. From the docs:
An arrow function does not create its own this context, so this has
its original meaning from the enclosing context.
Indeed there are some ways of binding in React:
1) you can bind all functions in your constructor, like you said:
constructor(props) {
/* ... */
this.increment = this.increment.bind(this);
}
2) invoke your callbacks with arrow functions:
<button onClick={(e) => this.increment(e)}>
3) append .bind at the end of your method reference each time you set it as a callback, like this:
<button onClick={this.increment.bind(this)}>
4) In your class, define the method with arrow functions:
increment = (e) => {
/* your class function defined as ES6 arrow function */
}
/* ... */
<button onClick={this.increment}>
In order to use this syntax with babel, you have to enable this plugin or use stage-2 preset.
If you look closely at the way the tick() function has been called in your fist example, you will understand that binding has been specified to it when it is called using the arrow functions. If you do the same for the increment function it will also work. These are just different ways of binding the functions.
So as asked, its not that in the first example no binding is specified while the second it requires, rather in both your cases you are binding just that the way of binding is different for both the cases.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
}
increment(prevState) {
this.setState((prevState) => ({
count: prevState.count + 1
}));
}
render() {
return (
<div className="main">
<button onClick={() => this.increment()}>{this.state.count}</button>
</div>
)
}
}
ReactDOM.render(<Counter/>, 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>
Related
import React, { Component } from 'react'
class TestState extends Component{
constructor(props){
super(props)
this.state = {
count:1
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
console.log(this.state)
this.setState (state=>{
return state.count++
})
}
render(){
return <div>
<button onClick={this.handleClick} >Click</button>
{this.state.count}
</div>
}
}
export default TestState
In the above code, when I click the button the counter increases by double value every time I click it .. e.g. On clicking the button the count will increase by 1, 3, 5, 7?
But state.count should only increase once because of the ++ operator.
you should update the state like this
handleClick(){
console.log(this.state)
this.setState (state=>{
return { count: state.count + 1}
})
}
because you return a object that you wish update not a integer
The first answer is a solution, but the lifecycle method can be executed another way:
In my experience, ES6 fashion lends a DRY approach to updating state. Use of anonymous functions and implicit returns allow omission of the return keyword even though they aren't required if you don't rely on the value for another calculation ( Not recommended due to #setState's async nature ).
handleClick() {
console.log( this.state )
this.setState( state => ({
counter: state.count + 1
}))
}
For more on the matter see: https://reactjs.org/docs/state-and-lifecycle.html
Mutating state directly, as you do in your example, can lead to odd behavior and is never advisable. Here is a quote from the React docs:
NEVER mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
By doing this state.count++ you throw React for a loop and as such see unexpected results.
I would rewrite your code like this: (https://codesandbox.io/s/red-lake-yk9gy?file=/src/App.js):
import React, { Component } from "react";
class TestState extends Component {
constructor(props) {
super(props);
this.state = { count: 1 };
}
handleClick = count => {
count++;
this.setState({count});
};
render() {
const { count } = this.state;
return (
<div>
<button onClick={() => this.handleClick(count)}>Click</button>
{count}
</div>
);
}
}
export default TestState;
I am a React newbie, and am working on examples from Adam Freeman's book.
I am starting with simple event handling and am unable to figure out why the regular-looking version of handleClick() below does not work. The method using the arrow notation (from the book) works as expected, but I am trying to translate it to the standard method notation, and am unable to figure out what is missing.
import React, { Component } from "react";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
count: 4
}
}
isEven(val) { return val % 2 === 0 ? "Even" : "Odd"; }
// the following works
//handleClick = () => this.setState({ count: this.state.count + 1 });
// this gives an error: TypeError: Cannot read property 'setState' of undefined
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render = () =>
<h4>
<button onClick={this.handleClick}>Click me!</button>
Number of items: {this.isEven(this.state.count)}
</h4>
}
What changes are needed for handleclick() to work?
You can bind this using one of the below,
In Constructor,
constructor(props) {
super(props);
this.state = {
count: 4
}
this.handleClick = this.handleClick.bind(this);
}
Or you can directly bind this as,
<button onClick={this.handleClick.bind(this)}>Click me!</button>
Or simply using fat arrow syntax,
<button onClick={() => this.handleClick()}>Click me!</button>
Ref
I'm trying to change children Component to another component by using state. This injects new Component correctly, however, if I want to change its props dynamically, nothing is changing. componentWillReceiveProps isn't triggered.
In my scenario, I'll have many components like TestComponent (nearly 20-30 components) and they all have different HTML layout (also they have sub components, too). I switch between those components by selecting some value from some list.
Loading all those components initially doesn't seem a good idea I think. On the other hand, I haven't found anything about injecting a Component inside main Component dynamically.
Here is a very basic example of what I want to achieve. When clicking on the button, I insert TestComponent inside App. After that, on every one second, I increment a state value which I try to bind TestComponent but, the component value is not updating.
If I use commented snippet inside setInterval function instead of uncommented, it works but I have to write 20-30 switch case for finding the right component in my real code (which I also wrote when selecting a value from list) so, I want to avoid using that. Also, I'm not sure about the performance.
So, is this the correct approach, if so, how can I solve this problem? If it is wrong, what else can I try?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
component: <p>Initial div</p>,
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
this.setState({
component: <TestComponent currentValue={this.state.componentData} />
});
setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
})
// This will update TestComponent if used instead of above
/*this.setState({
componentData: this.state.componentData + 1,
component: <TestComponent currentValue={this.state.componentData} />
});*/
}, 1000)
}
render() {
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{this.state.component}
</div>
)
}
}
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: this.props.currentValue
};
}
componentWillReceiveProps(nextProps) {
this.setState({
currentValue: nextProps.currentValue
});
}
render() {
return (
<p>Current value: {this.state.currentValue}</p>
)
}
}
ReactDOM.render(
<App />
,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" style="width: 200px; height: 200px;"></div>
To dynamically render the child components you can use React.createElement method in parent, which results in invoking different components, this can be used as, below is sample code, hope it helps.
getChildComponent = (childComponentName) => {
const childComponents = {
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
},
componentProps = Object.assign({}, this.props,this.state, {
styles: undefined
});
if (childComponents[childComponentName]) {
return React.createElement(
childComponents[childComponentName],
componentProps);
}
return null;
}
render(){
this.getChildComponents(this.state.childComponentName);
}
Here in the render function, pass the component name, and child will render dynalicaaly. Other way of doing this can be, make childComponents object as array , look below fora sample
const childComponents = [
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
]
Note: You have to import all child components here in parent, these
are not strings.
That's because as Facebook mentions in their React documentation.
When you call setState(), React merges the object you provide into the current state.
The merging is shallow
For further information read the documentation
So for this case the only modified value will be componentData and component won't trigger any updates
Solution
A better case to solve this issue is using Higher-Order components (HOC) so the App component doesn't care which component you are trying to render instead It just receives a component as a prop so you can pass props to this component base on the App state.
Also, you don't need a state in TestComponent since you get the value as a prop and it's handled by App.
I also added a condition to prevent adding multiples setInterval
class App extends React.Component {
interval;
constructor(props) {
super(props);
this.state = {
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
if (!this.interval) {
this.setState({
componentData: this.state.componentData + 1
});
this.interval = setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
});
}, 1000);
}
}
render() {
let Timer = this.props.timer;
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{!this.state.componentData ? <p>Initial div</p> : <Timer currentValue={this.state.componentData} />}
</div>
)
}
}
class TestComponent extends React.Component {
render() {
const { currentValue } = this.props;
return (
<p>Current value: {currentValue}</p>
)
}
}
ReactDOM.render(<App timer={TestComponent} /> ,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>
Im novice to React js, i don't know whats wrong with below code, but i'm getting setState is not a function error.Please help me to fix this.
class AppBarLayout extends React.Component {
constructor(props) {
super(props);
this.state = {
visibleSideBar:true,
slide:""
}
}
showProfile(){
this.setState({
slide:'slide'
});
console.log(this.state.slide);
}
render(){
return(
<div>
<header>
<NavBar show={this.showProfile}/>
<Profile slide={this.state.slide} />
</header>
</div>
);
}
}
export default AppBarLayout;
You need to bind this.showProfile in the component constructor
this.showProfile = this.showProfile.bind(this)
More detail about this on the Handling Events page of the React doc : https://facebook.github.io/react/docs/handling-events.html
Expanding on Delapouite's answer if you don't like to bind every function in the constructor you can use arrow functions to automatically bind to the correct context.
For example:
class AppBarLayout extends React.Component {
constructor(props) {
super(props);
this.state = {
visibleSideBar:true,
slide:""
}
}
// Now showProfile is an arrow function
showProfile = () => {
this.setState({
slide:'slide'
});
console.log(this.state.slide);
}
render(){
return(
<div>
<header>
<NavBar show={this.showProfile}/>
<Profile slide={this.state.slide}/>
</header>
</div>
);
}
}
export default AppBarLayout;
In my case, I solved the problem without binding.
Declaring the method like this was generating the error:
async onSubmit(e) {
event.preventDefault();
this.setState({ shopEthereumAddress: e.target.id });
}
The CORRECT declaration which will not generate the error is this:
onSubmit = async event => {
event.preventDefault();
this.setState({ shopEthereumAddress: event.target.id });
}
This works.
toggleSwitch() {
this.setState({
name: 'Ram ji'
});
}
Using an arrow function keeps the context of this set to the parent scope. The main benifit of arrow functions apart from being more concise is
Main benefit: No binding of ‘this’
// use this arrow function instead of
toggleSwitch = () => {
this.setState({
name: 'Ram ji' //It's working
});
}
I am learning React. In a test app I'm writing, I am rendering some buttons with onClick methods. When they are rendered like this, they work and call the selectMode function as expected when clicked.
constructor(props) {
super(props);
this.state = { mode: 'commits', commits: [], forks: [], pulls: [] };
}
...
selectMode(mode) {
this.setState({ mode });
}
render() {
...
return (<div>
<button onClick={this.selectMode.bind(this, 'commits')}>Show Commits</button><br/>
<button onClick={this.selectMode.bind(this, 'forks')}>Show Forks</button><br/>
<button onClick={this.selectMode.bind(this, 'pulls')}>Show Pulls</button>
</div>
)
}
But when I tried the suggested best practices way shown below, by binding in the constructor, the selectMode function is called three times when the component is rendered. Why are the onClick event handlers being called then? What do I have wrong?
constructor(props) {
super(props);
this.state = { mode: 'commits', commits: [], forks: [], pulls: [] };
this.selectMode = this.selectMode.bind(this)
}
...
selectMode(mode) {
this.setState({ mode });
}
render() {
...
return (<div>
<button onClick={this.selectMode('commits')}>Show Commits</button><br/>
<button onClick={this.selectMode('forks')}>Show Forks</button><br/>
<button onClick={this.selectMode('pulls')}>Show Pulls</button>
</div>
)
}
your this.selectMode(...) is executed IMMEDIATELY whenever your component is rendered.
<.. onClick={this.selectMode('commits')}..../> <-- function is called immediately
You can use arrow function to create an anonymous function in which you can call your method. In this way, you this.selectMode method only get called when the click event occurs :
<.. onClick={() => this.selectMode('commits')}..../>
If you don't want to create anonymous functions everytime you render the component, you can store an value to an attribute of the element. Like this :
constructor(props) {
super(props);
this.state = { mode: 'commits', commits: [], forks: [], pulls: [] };
this.selectMode = this.selectMode.bind(this)
}
selectMode(event){
this.setState({mode: e.target.name});
}
render(){
....
<.. onClick={this.selectMode} name='commits' ..../>
..}
I'm not sure, but I think it's because you call upon each onClick function by adding the parentheses. If you use ES6 you could try doing this:
onClick = () => { this.selectMode('commits') }
onClick = () => { this.selectMode('forks') }
onClick = () => { this.selectMode('pulls') }