In this code along, we'll discuss updating state in React and create an component that will change what it displays based on state.
- Update state in React by using
this.setState()
and passing in an object - Describe what happens when state is updated
- Explain the difference between changing state and changing props
While a React component can have initial state, the real power is in updating its state โ after all, if we didn't need to update the state, the component shouldn't have any state. State is only reserved for data that changes in our component and is visible in the UI.
Instead of directly modifying the state using this.state
, we use
this.setState()
. This is a function available to all React components that use
state, and allows us to let React know that the component state has changed.
This way the component knows it should re-render, because its state has changed
and its UI will most likely also change. Using a setter function like this is
very performant. While other frameworks like Angular.js use "dirty checking"
(continuously checking for changes in an object) to see if a property has
changed, React already knows because we use a built-in function to let it know
what changes we'd like to make!
Note: In this code-along, use the code in the
src
to follow along. To run the code, make sure tonpm install & npm start
in the terminal.
For example, let's say we have a component with a button, and a bit of text to indicate whether that button has been pressed yet:
// src/components/ClickityClick.js
import React from "react";
class ClickityClick extends React.Component {
constructor() {
super();
// Define the initial state:
this.state = {
hasBeenClicked: false,
};
}
handleClick = () => {
// Update our state here...
};
render() {
return (
<div>
<p>I have {this.state.hasBeenClicked ? null : "not"} been clicked!</p>
<button onClick={this.handleClick}>Click me!</button>
</div>
);
}
}
export default ClickityClick;
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import ClickityClick from "./components/ClickityClick";
ReactDOM.render(<ClickityClick />, document.getElementById("root"));
To update our state, we use this.setState()
and pass in an object. This object
will get merged with the current state. When the state has been updated, our
component re-renders automatically. Handy!
// src/components/ClickityClick.js
...
handleClick = () => {
this.setState({
hasBeenClicked: true
})
}
...
Update src/components/ClickityClick.js
and src/index.js
using the code
snippets above to see the rendered text change once the button is clicked.
When updating state, we don't have to pass in the entire state, just the property we want to update. For example, consider the following state for our component:
{
hasBeenClicked: false,
currentTheme: 'blue',
}
If we updated the hasBeenClicked
using this.setState()
like we did above, it
would merge the new state with the existing state, resulting in this new
state:
{
hasBeenClicked: true,
currentTheme: 'blue',
}
One super important thing to note is that it only merges things on the first level. Let's say we're working on a component that lets a user fill in an address, and the component's state is structured like this:
{
theme: 'blue',
addressInfo: {
street: null,
number: null,
city: null,
country: null
},
}
If we wanted to update the addressInfo.city
field, you might think we can
update it like this:
this.setState({
addressInfo: {
city: "New York City",
},
});
However, this would result in the following state shape:
{
theme: 'blue',
addressInfo: {
city: 'New York City',
},
}
See what happened there? It merged the state, but the other key/value pairs in
addressInfo
get overwritten, because it doesn't deeply merge the state with
the object you pass into this.setState()
. A deep merge means that the merge
will happen recursively, leaving any unchanged properties intact. For example,
consider the following code sample:
const house = {
kitchen: {
cabinets: "white",
table: {
legs: 4,
},
},
};
// Note: `deepMerge()` isn't actually a built-in function
const updatedHouse = deepMerge(house, {
kitchen: {
table: {
legs: 8,
},
},
});
Deeply merging like this would only update the legs
property with a value of
8
, but the rest of the kitchen
and house
objects' structure will remain
intact.
We can solve this using Object.assign()
by merging the addressInfo
object
with the new data ourselves:
this.setState({
addressInfo: Object.assign({}, this.state.addressInfo, {
city: "New York City",
}),
});
Or, we could do this the RECOMMENDED way, by using the spread operator in JS:
this.setState({
addressInfo: {
...this.state.addressInfo,
city: "New York City",
},
});
The spread operator syntax can be used in JavaScript to 'de-compose' objects and
arrays. When used on an object as we see above, ...this.state.addressInfo
returns all the keys and values from within that object. We're saying addressInfo
should be equal to all the keys and values that make up addressInfo
, and, in
addition, there should be city
key with the value New York City
. If there
is already a city
key inside addressInfo
, it will be overwritten. If it
doesn't exist, it will be added.
Both of these would result in the state updating to this shape:
{
theme: 'blue',
addressInfo: {
street: null,
number: null,
city: 'New York City',
country: null
},
}
Perfect! Just what we needed.
One thing to keep in mind is that setting state is not synchronous. For all
intents and purposes, it might seem that way, since our components update right
away. State updates, however, are batched internally and then executed
simultaneously whenever React feels it's appropriate. This might result in some
unexpected behavior. Going back to our ClickityClick
component above, let's
log the state after we've set it using this.setState()
:
// src/components/ClickityClick.js
...
handleClick = () => {
this.setState({
hasBeenClicked: true
})
console.log(this.state.hasBeenClicked); // prints false
}
...
The console output says false
... but we just set it to true
! What is this madness?
State changes, however instant they might appear, happen asynchronously. If we
want to access our new state after it has been updated, we can optionally add a
callback as a second argument to the this.setState()
function. This callback
will fire once the state has been updated, ensuring that this.state
is now the
new, shiny updated state. In code:
// src/components/ClickityClick.js
...
handleClick = () => {
this.setState({
hasBeenClicked: true
}, () => console.log(this.state.hasBeenClicked)) // prints true
}
...
It's important to note the difference between changes in state and changes in props. Changes in state and/or props will both trigger a re-render of our React component. However, changes in state can only happen internally due to components changing their own state. Thus, a component can trigger changes in its own state.
A component cannot change its props. Changes in props can only happen externally, meaning the parent or grandparent component changes the values it passing down to its children.
Very often when we're changing state, the change we're making is relative to the
previous state. Imagine, for instance, we wanted to use state to keep track of
the number of times a button is pressed. The component's state might start at
0
. When the button is pressed, state should change to 1
. However, if pressed
again, how exactly do we change state to 2
?
The easiest solution would be to write code that sets state to its current
value plus one. However, when we write this in code and implement setState
,
it is important to note that we should not use this.state
inside of
setState
.
As mentioned before, setState
is not synchronous โ in situations where
there are many state changes being made, multiple setState
calls may be
grouped together into one update. If we use this.state
inside a setState
, it
is possible that the values in state are changed by a different setState
just prior to our setState
.
One way to deal with this is to handle the logic that involves this.state
outside of setState
. Below is an example of a component that uses this
approach to keep track of button presses:
import React, { Component } from "react";
class ButtonCounter extends Component {
constructor() {
super();
// initial state has count set at 0
this.state = {
count: 0,
};
}
handleClick = () => {
// when handleClick is called, newCount is set to whatever this.state.count is plus 1 PRIOR to calling this.setState
let newCount = this.state.count + 1;
this.setState({
count: newCount,
});
};
render() {
return (
<div>
<h1>{this.state.count}</h1>
<button onClick={this.handleClick}>Click Me</button>
</div>
);
}
}
export default ButtonCounter;
This works, but React actually provides a built in solution. Instead of passing an object
into setState
, we can also pass a function. That function, when called inside setState
will be passed the component state from when that setState
was called. This is typically
referred to as the previous state. With this knowledge, we can rewrite the handleClick
function to:
...
handleClick = () => {
this.setState(previousState => {
return {
count: previousState.count + 1
}
})
}
...
Here, there is no need for a separate variable assignment like
let newCount = this.state.count + 1
. It is important that we still
return an object that was in the same structure as before as the
return value of this function becomes the new state.
Let's look at another example - In the ClickityClick
example earlier, we were
changing state from false
to true
with a hard-coded true
in the setState
.
What if, instead, we wanted the ability to toggle between true
and false
repeatedly?
Here again, previous state is very useful. Since we're dealing with a boolean value, to toggle from one to the other, we just need to set the state to the opposite of whatever it is.
This component, let's call it LightSwitch
, would look something like this:
import React from "react";
class LightSwitch extends React.Component {
constructor() {
super();
// Initial state is defined
this.state = {
toggled: false,
};
}
// when handleClick is called, setState will update the state so that toggle is reversed
handleClick = () => {
this.setState((previousState) => {
return {
toggled: !previousState.toggled,
};
});
};
render() {
return (
<div>
<button onClick={this.handleClick}>
{this.state.toggled ? "ON" : "OFF"}
</button>
</div>
);
}
}
export default LightSwitch;
When the code above renders, it will display a button labeled OFF
until it is
clicked. When clicked, it will display ON
. Click once more, and it is back to
OFF
.
Some additional details on setState
can be found in React's official
documentation.
To recap: Using setState
, we can update a component's state. We frequently use
events to trigger these updates. setState
is called asynchronously and merges
the existing state with whatever object is passed in. We can also pass a
function to setState
, which allows us to write state changes that are based on
the existing state values.
Being able to update state can be extremely useful in many situations. We can use state to keep track of incrementing values and toggle-able boolean values. We can also use it to keep track of things like timestamps, user inputs, and in-line style settings. State can store arrays as well, which we now have the ability to update and add to!
react-updating-state's People
Forkers
justinfrawl kwebster2 georgelgore kat-star danhjoo7 emileypalmquist adamlarosa mustafa-al-shaheri clarkj99 petersampson010 gnardinosaur assansav chrisbaptiste83 munroe1786 bonjourannie ericiscoding chriskay25 kaladajumbo conradbuys11 melindadiaz07 dickm19 billyott liamh47 mkirby artwilton ajakcyer kylefarmer85 joshua-flatiron jmlovitsch gerardmi24 keviniturralde h11z mikesap noble-webb barbarakuofie sagbeyeg i-am-lain zackcarlson014 j2b2590 stacksonstack bjf-flatiron see-bet-code tincogs dyson1602 aru120 jomariepolanco jonnyhak mkoenke sdelane2 patriciaarnedo jlorda kguerra96 lantzbuilds chippychoppy jaquan1314 techtwin alexriosdev wlytle arielvgrubbs raaynaldo gamil91 stephenvincentibanez tolentinoel jacobkagon metrio jbondeson19 inee-ader nnhk23 will-gon sarabastian jpeterson14 johncodez crrojas88 hugorod19 bryan-bot jdains27 stevenwutg jetechny iizzylca acparkgh denaweiss5 hukic-m var787 hamoose18 thequeenbeebs danajackson2 tbustama kirstybrews maryannn28 dwisecar apang20 emmafewer milanwinter racross1 lmontanari20 dangnhon mitchyg07 chinguyen21 rmiverson pete3249react-updating-state's Issues
Not a Lab
Echoing jdbean's remarks, this ReadMe/Code-Along has not been fully transitioned - there is no test folder, so it can't be completed without modification.
Grammar issue
This sentence: See what happened there? It merged the state, but any objects get overwritten, because it doesn't deeply merge the state with the object you pass into this.setState().
Corrected: See what happened there? It merged the state, but any other objects get overwritten, because it doesn't deeply merge the state with the object you pass into this.setState().
Failed to Compile- TypeError
Failed to compile.
TypeError: Cannot read property 'request' of undefined
- ExternalModuleFactoryPlugin.js:37 handleExternals
[react-updating-state-v-000]/[webpack]/lib/ExternalModuleFactoryPlugin.js:37:33
- ExternalModuleFactoryPlugin.js:46 next
[react-updating-state-v-000]/[webpack]/lib/ExternalModuleFactoryPlugin.js:46:8
- ExternalModuleFactoryPlugin.js:59 handleExternals
[react-updating-state-v-000]/[webpack]/lib/ExternalModuleFactoryPlugin.js:59:7
- ExternalModuleFactoryPlugin.js:79 ExternalModuleFactoryPlugin.<anonymous>
[react-updating-state-v-000]/[webpack]/lib/ExternalModuleFactoryPlugin.js:79:5
- NormalModuleFactory.js:246 applyPluginsAsyncWaterfall
[react-updating-state-v-000]/[react-scripts]/[webpack]/lib/NormalModuleFactory.js:246:4
- Tapable.js:204
[react-updating-state-v-000]/[react-scripts]/[tapable]/lib/Tapable.js:204:11
- IgnorePlugin.js:56 IgnorePlugin.checkIgnore
[react-updating-state-v-000]/[react-scripts]/[webpack]/lib/IgnorePlugin.js:56:10
- Tapable.js:208 NormalModuleFactory.applyPluginsAsyncWaterfall
[react-updating-state-v-000]/[react-scripts]/[tapable]/lib/Tapable.js:208:13
- NormalModuleFactory.js:230 NormalModuleFactory.create
[react-updating-state-v-000]/[react-scripts]/[webpack]/lib/NormalModuleFactory.js:230:8
- Compilation.js:382 Compilation._addModuleChain
[react-updating-state-v-000]/[react-scripts]/[webpack]/lib/Compilation.js:382:17
- Compilation.js:464 Compilation.addEntry
[react-updating-state-v-000]/[react-scripts]/[webpack]/lib/Compilation.js:464:8
- SingleEntryPlugin.js:22 SingleEntryPlugin.<anonymous>
[react-updating-state-v-000]/[webpack]/lib/SingleEntryPlugin.js:22:15
- Tapable.js:229 Compiler.applyPluginsParallel
[react-updating-state-v-000]/[react-scripts]/[tapable]/lib/Tapable.js:229:14
- Compiler.js:488
[react-updating-state-v-000]/[react-scripts]/[webpack]/lib/Compiler.js:488:8
- Tapable.js:131 Compiler.applyPluginsAsyncSeries
[react-updating-state-v-000]/[react-scripts]/[tapable]/lib/Tapable.js:131:46
- Compiler.js:481 Compiler.compile
[react-updating-state-v-000]/[react-scripts]/[webpack]/lib/Compiler.js:481:7
I was following along with the readme. Copied in code, ran npm install
and then npm start
and got error above. Tried on local env (MacOS 10.9.5) with node version 6.9.5 and 8.1.2 as well as on the Learn IDE. Error was the same throughout.
https://github.com/DanL12186/react-updating-state-v-000
e8405f3078fa40743a670c35ffd866cc2774cd79
Not a lab
This "lab" appears not be a lab but a readme. It is configured to act as a lab however. It would probably be better to modify this behavior
learn test returns a NoMethodError for NilClass
Traceback (most recent call last):
11: from /Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/bin/ruby_executable_hooks:24:in `<main>'
10: from /Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/bin/ruby_executable_hooks:24:in `eval'
9: from /Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/bin/learn-test:23:in `<main>'
8: from /Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/bin/learn-test:23:in `load'
7: from /Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/gems/learn-test-3.3.1/bin/learn-test:73:in `<top (required)>'
6: from /Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/gems/learn-test-3.3.1/lib/learn_test/runner.rb:21:in `run'
5: from /Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/gems/learn-test-3.3.1/lib/learn_test/runner.rb:21:in `fork'
4: from /Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/gems/learn-test-3.3.1/lib/learn_test/runner.rb:22:in `block in run'
3: from /Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/gems/learn-test-3.3.1/lib/learn_test/runner.rb:49:in `report_and_clean'
2: from /Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/gems/learn-test-3.3.1/lib/learn_test/reporter.rb:13:in `report'
1: from /Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/gems/learn-test-3.3.1/lib/learn_test/reporter.rb:47:in `report'
/Users/justinmclawhorn/.rvm/gems/ruby-2.6.1/gems/learn-test-3.3.1/lib/learn_test/strategies/mocha.rb:44:in `results': undefined method `[]' for nil:NilClass (NoMethodError)
This is the same error from a previous code along (here). Presumably, the lack of a test file is causing the error. Temporary workaround is to create a test directory and add an empty js file. learn test
will then auto-submit to Canvas.
Compile Error, Webpack Incompatibility
Hello! I'm reaching on on regards to a compiling error received for this code-along lesson. Before writing any code I ran npm install && npm start
as the lesson provides. Upon that, I received a Failed to compile
error in both the browser and the console.
Fortunately I was able to find a solution provided by Dan Abramov(the co-creator of React). His states that Webpack is not compatible with create-react-app
projects. I don't know if I should assume this but the lessons code-along project may have been created via create-react-app
and then the addition of Webpack was made.
I followed along with Dan's solution and at the very end of his solution ran npm install && npm start
which successfully delivered the browser for the lesson's code-along. I will include the link to his solution. I did alter my forked copy of the lesson's repository and made the changes on my copy only. I also did this with a technical coach on zoom and we ran through the solution together to see what would happen.
I included the error and the before and after of package.json
. Along with the link to the solution.
Happy coding
Malind
Update to ES6?
Or, we could do this using the proposed object spread operator in the next version of JS: RECOMMENDED
-- this should be updated, with more info/link to spread operator.
Need singular here
Need to remove the "s" from components here:
"This way the components knows it should re-render, ...."
Thank you.
THIS SCHOOL IS THE BEST AT BEING UNDERSTAFFED
I CANNOT BELIEVE THIS SCHOOL IS SO UNDERSTAFFED. IT IS WORST THAN THE ACTUAL GOVERNMENT IN THIS CRISIS. I HATE THIS PRESIDENT AND THIS ADMINISTRATION BUT I HATE THIS SCHOOL MORE BECAUSE IT DOESNT DO WHATS BEST FOR THE STUDENTS BUT INSTEAD WHATS BEST FOR YOUR POCKETS. HAVE MORE PEOPLE ANSWERING QUESTIONS IN ASK A QUESTION
Last paragraph is deprecated
Props are read-only and cannot be modified. Maybe reword the paragraph to clarify that passing props from parent to child does not modify existing properties-- it creates and renders a new instance of the child component with different properties than the previously rendered instance.
you guys are the worst at this
you should loose your teaching license since you can't teach at all. all you do is show people how to google since you are not teachers
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. ๐๐๐
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google โค๏ธ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.