Skip to content

Instantly share code, notes, and snippets.

@stoyan
Created December 12, 2020 20:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stoyan/d7f78b8461bb880f9b8df1bedbbdc4ed to your computer and use it in GitHub Desktop.
Save stoyan/d7f78b8461bb880f9b8df1bedbbdc4ed to your computer and use it in GitHub Desktop.
Slides from JSDay Brazil 2020
React State Management
JSDay🇧🇷 Recife 2020
@stoyanstefanov
phpied.com
* React
// State
// Class vs Functional components
* Hooks
// State in functional components
* Reducer
// Nicer way to handle state
* Context
// Passing info around the app
In the beginning there was HTML
<p style="color: red" id="para">
I am <span>red</span> text
</p>
Then along came JavaScript (DHTML)
var para = document.getElementById('para');
para.style.color = 'green';
var span = para.getElementsByTagName('span')[0];
span.innerText = 'green';
We suffered... it was the Dark Ages.
''''
\ , /
' ,___/_\___, '
\ /o.o\ /
-= > \_/ < =-
/_\___/_\
. ` \ / ` .
/ `` \
React
// declarative "template" HTML-like
<p style={{color: color}}>I am {color} text</p>
// dynamic content
const color = this.state.color;
// changes
this.setState({color: 'green'});
Toggle back to red?
Dealing with DOM is expensive,
let React do it.
class Guitar extends React.Component {
render() {
<Instrument strings="6" tuning="eadgbe" />
}
}
e |---|---|---|---
b |---|---|---|---
g |---|---|---|---
d |---|---|---|---
a |---|---|---|---
e |---|---|---|---
class Cavaquinho extends React.Component {
render() {
<Instrument strings="4" tuning="dgbd" />
}
}
d |---|---|---|---
b |---|---|---|---
g |---|---|---|---
d |---|---|---|---
class Instrument extends React.Component {
constructor(props) {
super();
}
render() {
// `stuff` based on
// this.props.strings
// this.props.tuning
return <div>{stuff}</div>;
}
}
... and then user clicks something.
State gets involved.
class Instrument extends React.Component {
constructor(props) {
super();
this.state = {};
}
render() {
return (
<div onClick={this.setState({/****/})}>
// more stuff using
// this.props and this.state
</div>
);
}
}
State = Life = Mess
State Management = Dealing with Life
What about functional components?
function Instrument(props) {
return (
<div>
// loop props.tuning
</div>
);
}
Q: Where is the state?
A: Hooks!!!1111!
Hook
====
⚓ just a function
⚓ `use` prefix, e.g. useState(), useReducer()
⚓ built-in or custom
`useState()` hook
====================
// first
const [data, setData] =
React.useState(initialData);
// then
setData(newData);
Without hooks
=============
constructor() {
super();
this.state = {data: initialData};
}
// then
this.setState(newData)
"Cool story. Who cares. I like classes."
Wait! There is more!
Multiple hooks
==============
const [left, setLeft] = React.useState({});
const [right, setRight] = React.useState({});
Empty `left` and `right`
d |---|---|---|--- // ---
b |---|---|---|--- // ---
g |---|---|---|--- // ---
d |---|---|---|--- // ---
setLeft( [4, 2, 1, 0]);
setRight([1, 1, 1, 1]);
d o|---|---|---|--- // -x-
b |-x-|---|---|--- // -x-
g |---|-x-|---|--- // -x-
d |---|---|---|-x- // -x-
vs
this.setState({
left: [4, 2, 1, 0],
right: [1, 1, 1, 1],
});
Separation of concerns
Hooked on hooks
===============
⚓ started as a simple answer to
state in function components
⚓ unlocked a whole lot more
Lifecycle methods
==================
👶 this.componentDidMount()
💀 this.componentWillUnmount()
😵 ...
In Hooks World:
✨ `React.useEffect()`
✨ side effects
useEffect(() => {
// side effects here
return () => {
// cleanup here
};
}, [/* dependencies */]);
useEffect(() => {
function keydownHandler(e) {}
document.addEventListener(
'keydown', keydownHandler);
return () => {
document.removeEventListener(
'keydown', keydownHandler);
};
}, []);
useEffect(() => {}, []);
useEffect(() => {}, [leftHand, rightHand]);
useEffect(() => {});
Pre-hooks:
class Instrument { // ...
keydownHandler(e) {}
componentDidMount() {
document.addEventListener(
'keydown', this.keydownHandler);
// all other effects here
}
componentWillUnmount() {
document.removeEventListener(
'keydown', this.keydownHandler);
// all other cleanup here
}
}
💦 Or was it `componentWillUnMount()`?
✨ `useEffect()`
✨ `useLayoutEffect()`
* Back to state
* `useReducer()`
Reducer
=======
* an alternative to `this.setState()`
* and to `useState()`
* all in one place
* unit test
function myReducer(oldState, action) {
const newState = {};
// do something with old state and action
return newState;
}
function myReducer(mess, event) {
const order = {};
// do something with mess and event
return order;
}
`action` can be anything but think of it as an `event` with:
* a `type` (similar to e.g. `click` in DOM world)
* optionally some `payload` of other data about the event
Actions are then "dispatched".
// Now with a reducer:
const [data, dispatch] = useReducer(
myReducer, initialData);
<button onClick={
dispatch({
type: 'click',
payload: {/*...*/},
})
}>{data}</button>
function myReducer(oldState, action) {
const newState = {};
if (action.type === 'click') {
// do something with oldState and action.payload
}
if (action.type === 'pluck') {
// do something else with oldState and action.payload
}
//....
return newState;
}
(
) )
_.(--"("""--.._
/, _..-----).._,\
| `'''-----'''` |
\ /
'. .'
'--.....--'
function Soup() {
const [result, dispatch] = useReducer(
pot, // the reducer function
['water', 'carrots', 'onion'] // initial data
);
dispatch({
type: 'boil',
});
// setTimeout
dispatch({
type: 'adjust_heat',
payload: 'medium',
});
// some more timeout
dispatch({
type: 'add_ingredients',
payload: ['peper', 'salt'],
});
dispatch({
type: 'stir',
});
return <div>{result}</div>
}
Context API
How is data passed around various components?
+-----------------------------------+
| <Application /> |
| +---------------------------+ |
| | <Header /> | |
| | | |
| | +-----------------------+ | |
| | | <Avatar /> | | |
| | +-----------------------+ | |
| | | |
| | +-----------------------+ | |
| | | <ProfileForm /> | | |
| | +-----------------------+ | |
| | | |
| +---------------------------+ |
+-----------------------------------+
Option: passing down properties
======
<Application username="marlon" />
// Somewhere in Application's render()
<Header username={this.props.username} />
// ... and deeper
<Avatar username={this.props.username} />
// ....
And what if a grand-child component wants to update the username?
Another property - a callback
Option: global state
======
* With `forceUpdate()`?
* With another library?
Context API
===========
* avoids passing around too many props
* local state
* makes code refactor-able
const UserContext = React.createContext({
profile: {username: "marlon"},
setProfile: () => {},
});
// UserContext.Provider
// UserContext.Consumer (or a hook)
<ThemeContext.Provider value={colors}>
<UserContext.Provider value={profile}>
<Application>
<Header />
<SongContext.Provider value={song}>
<Cavaquinho />
</SongContext>
<Footer/>
</Application>
</UserContext>
function Footer() {
const profile = useContext(UserContext);
return <p>{profile.username}</p>
}
Separate state management from UI
What have we learned today?
⚽ React
// Build UIs like never before
// State of the app is the important part
⚽ Hooks
// You can have state in functional components
// Better organize the app
⚽ Reducers
// Nicer way to handle state in one place
// Unit tests
⚽ Context
// Passing bits of state around the app
// Only to those who care
// Refactor and move components around
⚽ useState, useReducer, useContext,
useEffect, useLayoutEffect
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment