Created
December 12, 2020 20:40
-
-
Save stoyan/d7f78b8461bb880f9b8df1bedbbdc4ed to your computer and use it in GitHub Desktop.
Slides from JSDay Brazil 2020
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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