Project Falcon
Up to now, we've learned enough that we could build what is effectively a static website out of small, cohesive building blocks.
That's pretty cool, but we aren't here to build websites that are static.
We want to build applications that are interactive and change over time.
this.stateReact Components have a state object that's used to control how the component renders and behaves.
A Component can never change its props, but Components can change their state.
state is what allows us to create components with behavior.
Let's build a basic counter component. It will:
What should the state look like for this component?
What number should our count start at?
Thats our default state.
We set our default state our component using the React.useState() hook, like so:
import React from 'react'; function App() { const [count, setCount] = React.useState(0); return <main>TODO</main>; } export default App;
The first thing we notice is that React.useState() is giving us back an array containing whatever our initial state is (in this case 0) as well as a setter for that value.
You can use your state inside your Components by simply accessing the variable name.
Let's print out our count by grabbing it from our destructured array:
import React from 'react'; function App() { const [count, setCount] = React.useState(0); return ( <main> <section className="counter-container"> <h2 className="counter-container__count">{count}</h2> </section> </main> ); } export default App;
setCountWe will now add an + button that adds 1 to count via the setCount() method.
import React from 'react'; function App() { const [count, setCount] = React.useState(0); const increment = () => { setCount(count + 1); }; return ( <main> <section className="counter-container"> <h2 className="counter-container__count">{count}</h2> <button onClick={increment}>+</button> </section> </main> ); } export default App;
Our button has an onClick attribute, similar to regular HTML, except instead of a string we have {increment}.
We are actually passing a reference to the increment function that's defined here.
The decrement functionality is very similar. Give it a shot on your own.
The app works, but taking a look at the App component thru the lens of the Single Responsibility Principle it seems to have multiple responsibilities:
import React from 'react'; function App() { const [count, setCount] = React.useState(0); const decrement = () => { setCount(count - 1); }; const increment = () => { setCount(count + 1); }; return ( <main> <section className="counter-container"> <h2 className="counter-container__count">{count}</h2> <button onClick={decrement}>-</button> <button onClick={increment}>+</button> </section> </main> ); } export default App;
State Management & Presentation
It's a common pattern to split your components into one of two categories:
stateprops.state.props.When you find you have a component that is responsible for both presentation and state, you have two options to split those responsibilities.
Let's try pushing the presentation down. Take a look at the render function:
return ( <main> <section className="counter-container"> <h2 className="counter-container__count">{count}</h2> <button onClick={decrement}>-</button> <button onClick={increment}>+</button> </section> </main> );
I spy three things in there that are related to state and transitions here:
count: This is using state.decrement: This is behavior that triggers a state transition.increment: This is behavior that triggers a state transition.When we push the presentation down, these three things should come from the parent component via props.
This allows the Container component to own the responsibility for state & transitions.
Take a shot at extracting the presentation component. It should:
decrement, count, increment).// Counter.js import React from 'react'; function Counter({ count, decrement, increment }) { return ( <main> <section className="counter-container"> <h2 className="counter-container__count">{count}</h2> <button onClick={decrement}>-</button> <button onClick={increment}>+</button> </section> </main> ); } export default Counter;
Notice that the component is only concerned with how things look, not how they work.
Now that we have our presentational concerns isolated in the Counter component, we need to refactor the App class so that it uses this new component.
Give it a shot. You'll need to:
import the new Counter.Counter component.Counter component.Everything that's state related lives inside this container component, and none of the presentation is defined here.
That's a nice, clean, separation of concerns.
import React from 'react'; import Counter from './Counter'; function App() { const [count, setCount] = React.useState(0); const decrement = () => { setCount(count - 1); }; const increment = () => { setCount(count + 1); }; return ( <Counter count={count} decrement={decrement} increment={this.increment} /> ); } export default App;
It can be really helpful to take a few minutes and sketch out what your state object should look like before you start coding.
The current behavior and appearance of your UI is calculated by applying rendered content and changes to your state.
As you continue to develop your React skills, you'll hit a point where managing state within a container component starts to get really difficult.
When you cross that line (you'll know it when you're there), learning Redux or another state management tool is a great idea.
You can do a lot with the state management that's built into React, and you'll do yourself a huge disservice if you head down this path too quickly. Learn from my mistake and cross that bridge when you need to, and no sooner.