A presentation at React Conf in in Henderson, NV, USA by Becca Bailey
The State of React State in 2019 Becca Bailey • React Conf 2019 @beccaliz
Hi, I’m Becca! Chicago, IL @beccaliz
Why this talk?
The Newbie @beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
❔ ❔ @beccaliz ❔
The Loyalist @beccaliz
“If it ain’t broke, don’t fix it!” @beccaliz
@beccaliz
@beccaliz
redux react @beccaliz
redux react @beccaliz
redux react @beccaliz MobX
apollo redux react @beccaliz MobX
context apollo redux react @beccaliz MobX
context apollo redux react @beccaliz hooks MobX
context apollo redux react @beccaliz hooks MobX
@beccaliz
The Explorer @beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
❔ @beccaliz
❔ @beccaliz ❔
❔ ❔ @beccaliz ❔
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
State @beccaliz
@beccaliz
setState @beccaliz
setState @beccaliz useState
Local State @beccaliz
@beccaliz
You win! Yay! @beccaliz
1 class MyComponent extends React.Component { 2 state = { 3 visible: false 4 }; 5 6 showModal() { 7 this.setState(state => ({ 8 visible: true 9 })); 10 } 11 12 hideModal() { 13 this.setState(state => ({ 14 visible: false 15 })); 16 } 17 18 render() { 19 …stuff here 20 } 21 }
1 class MyComponent extends React.Component { 2 state = { 3 visible: false 4 }; 5 6 showModal() { 7 this.setState(state => ({ 8 visible: true 9 })); 10 } 11 12 hideModal() { 13 this.setState(state => ({ 14 visible: false 15 })); 16 } 17 18 render() { 19 …stuff here 20 } 21 }
1 class MyComponent extends React.Component { 2 state = { 3 visible: false 4 }; 5 6 showModal() { 7 this.setState(state => ({ 8 visible: true 9 })); 10 } 11 12 hideModal() { 13 this.setState(state => ({ 14 visible: false 15 })); 16 } 17 18 render() { 19 …stuff here 20 } 21 }
1 const MyComponent = () => { 2 const [visible, setVisible] = React.useState(false); 3 4 function showModal() { 5 setVisible(true); 6 } 7 8 function hideModal() { 9 setVisible(false); 10 } 11 12 return ( 13 …stuff here 14 ); 15 };
1 const MyComponent = () => { 2 const [visible, setVisible] = React.useState(false); 3 4 function showModal() { 5 setVisible(true); 6 } 7 8 function hideModal() { 9 setVisible(false); 10 } 11 12 return ( 13 …stuff here 14 ); 15 };
1 const MyComponent = () => { 2 const [visible, setVisible] = React.useState(false); 3 4 function showModal() { 5 setVisible(true); 6 } 7 8 function hideModal() { 9 setVisible(false); 10 } 11 12 return ( 13 …stuff here 14 ); 15 };
Prop Drilling @beccaliz
1 // App.js 2 const App = () => { 3 4 return ( 5 <Container> 6 <Game></Game> 7 </Container> 8 ); 9 };
const user = { id: 123, firstName: “Becca”, lastName: “Bailey”, email: “beccanelsonbailey@gmail.com”, marker: ” ” }
1 // App.js 2 const App = () => { 3 const [user, updateUser] = React.useState(); 4 5 React.useEffect(async () => { 6 const user = await fetchLoggedInUser(); 7 updateUser(user); 8 }, []) 9 10 return ( 11 <Container> 12 <Game user={user}></Game> 13 </Container> 14 ); 15 };
1 // Game.js 2 const Game = ({ user }) => { 3 const [board, updateBoard] = React.useState(EMPTY_BOARD 4 5 function makeMove(index) { 6 updateBoard({…board, [index]: user.marker }) 7 } 8 9 return ( 10 <React.Fragment> 11 <h1>Hello {user.name}!</h1> 12 <Board board={board} makeMove={makeMove} /> 13 </React.Fragment> 14 ); 15 };
1 // Game.js 2 const Game = ({ user }) => { 3 const [board, updateBoard] = React.useState(EMPTY_BOARD 4 5 function makeMove(index) { 6 updateBoard({…board, [index]: user.marker }) 7 } 8 9 return ( 10 <React.Fragment> 11 <Greeting user={user} /> 12 <Board board={board} makeMove={makeMove} /> 13 </React.Fragment> 14 ); 15 };
Repetition @beccaliz
You win! Yay!
⚠ Error! Oh no!
showModal modalOpen modalIsVisible modalIsOpen modalVisible @beccaliz
@beccaliz
@beccaliz
You win! Yay! @beccaliz
You win! ⚠ Error! Yay! Oh no! @beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
} @beccaliz local state
Global state @beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
} global state @beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
} semilocal state @beccaliz
Flux Architecture @beccaliz
@beccaliz
action @beccaliz
action @beccaliz
reducer action @beccaliz
reducer action @beccaliz
reducer action @beccaliz store
reducer action @beccaliz store
reducer store action view @beccaliz
reducer store action view @beccaliz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // actions.js export function makeMove(index) { return { type: “MAKE_MOVE”, payload: { index } }; } // reducers.js const game = (state = getInitialState(), action) => { switch (action.type) { case “MAKE_MOVE”: { const { index } = action.payload; return { …state, board: { …state.board, [index]: state.currentPlayer.marker, } }; } default: return state; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // actions.js export function makeMove(index) { return { type: “MAKE_MOVE”, payload: { index } }; } // reducers.js const game = (state = getInitialState(), action) => { switch (action.type) { case “MAKE_MOVE”: { const { index } = action.payload; return { …state, board: { …state.board, [index]: state.currentPlayer.marker, } }; } default: return state; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // Game.js function mapStateToProps({ board }) { return board; } function mapDispatchToProps(dispatch) { return { makeMove: (index) => { dispatch(makeMove(index)); } }; } export default connect( mapStateToProps, mapDispatchToProps )(Game);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // Game.js function mapStateToProps({ board }) { return board; } function mapDispatchToProps(dispatch) { return { makeMove: (index) => { dispatch(makeMove(index)); } }; } export default connect( mapStateToProps, mapDispatchToProps )(Game);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // Game.js function mapStateToProps({ board }) { return board; } function mapDispatchToProps(dispatch) { return { makeMove: (index) => { dispatch(makeMove(index)); } }; } export default connect( mapStateToProps, mapDispatchToProps )(Game);
@beccaliz
Becca played at spot 0 @beccaliz
Becca played at spot 0 Computer played at spot 4 @beccaliz
Becca played at spot 0 Computer played at spot 4 Becca played at spot 7 @beccaliz
Becca played at spot 0 Computer played at spot 4 Becca played at spot 7 Computer played at spot 3 @beccaliz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // actions.js export function makeMove(index) { return { type: “MAKE_MOVE”, payload: { index } }; } // reducers.js const game = (state = getInitialState(), action) => { switch (action.type) { case “MAKE_MOVE”: { const { index } = action.payload; return { …state, board: { …state.board, [index]: state.currentPlayer.marker, } }; } default: return state; }
@beccaliz
connected store dispatch view @beccaliz props
connected presentational store parent dispatch view @beccaliz props props view
Separation of Concerns @beccaliz
Too much abstraction @beccaliz
@beccaliz
// Board.test.tsx it(“handles click events”, () => { const props = { makeMove: jest.fn(), board: DEFAULT_BOARD }; const { queryAllByRole } = render(<Board {…props}></Board>); fireEvent.click(queryAllByRole(“button”)[0]); expect(props.makeMove).toHaveBeenCalledWith(0); });
// reducers.test.js it(“updates the board state”, () => { expect( reducer(initialState, { type: “MAKE_MOVE”, payload: { index: 2 } }) ).toEqual({ …initialState, board: createBoard(- - X - - - -
), }); });
1 it(“allows a player to make a move”, () => { 2 const { getByTitle } = render(<Game />); 3 const spot = getByTitle(“0”); 4 5 fireEvent.click(spot); 6 7 expect(getByTitle(“0”).textContent).toEqual(” 8 }); “);
1 it(“allows a player to make a move”, () => { 2 const { getByTitle } = render(<Game />); 3 const spot = getByTitle(“0”); 4 5 fireEvent.click(spot); 6 7 expect(getByTitle(“0”).textContent).toEqual(” 8 }); “);
1 it(“allows a player to make a move”, () => { 2 const { getByTitle } = render(<Game />); 3 const spot = getByTitle(“0”); 4 5 fireEvent.click(spot); 6 7 expect(getByTitle(“0”).textContent).toEqual(” 8 }); “);
@beccaliz
@beccaliz
Local State @beccaliz
@beccaliz
Prop Drilling @beccaliz
Prop Drilling Duplication @beccaliz
@beccaliz
✨ Higher Order Components ✨ @beccaliz
✨ Higher Order Components ✨ ✨ Render Props ✨ @beccaliz
1 <ModalManager> 2 {({ showModal, hideModal, visible }) => ( 3 <React.Fragment> 4 <Button onClick={() => showModal()}>Click me!</Button> 5 <Modal visible={visible}> 6 <h1>You win!</h1> 7 <Button onClick={() => hideModal()}>Close</Button> 8 </Modal> 9 </React.Fragment> 10 )} 11 </ModalManager>
1 <ModalManager> 2 {({ showModal, hideModal, visible }) => ( 3 <React.Fragment> 4 <Button onClick={() => showModal()}>Click me!</Button> 5 <Modal visible={visible}> 6 <h1>You win!</h1> 7 <Button onClick={() => hideModal()}>Close</Button> 8 </Modal> 9 </React.Fragment> 10 )} 11 </ModalManager>
1 const ModalManager = ({ children }) => { 2 const [visible, setVisible] = React.useState(false); 3 4 function showModal() { 5 setVisible(true) 6 }; 7 8 function hideModal { 9 setVisible(false) 10 }; 11 12 render() { 13 return ( 14 <React.Fragment> 15 {children({ visible, showModal, hideModal })} 16 </React.Fragment> 17 ) 18 } 19 }
1 const ModalManager = ({ children }) => { 2 const [visible, setVisible] = React.useState(false); 3 4 function showModal() { 5 setVisible(true) 6 }; 7 8 function hideModal { 9 setVisible(false) 10 }; 11 12 render() { 13 return ( 14 <React.Fragment> 15 {children({ visible, showModal, hideModal })} 16 </React.Fragment> 17 ) 18 } 19 }
1 <Query query={LOGGED_IN_USER}> 2 {({ loading, error, data }) => { 3 if (loading) { 4 return <Spinner />; 5 } 6 if (error) { 7 return <Error message={error} />; 8 } 9 return <Profile user={data}/>; 10 }} 11 </Query>
1 <Query query={LOGGED_IN_USER}> 2 {({ loading, error, data }) => { 3 if (loading) { 4 return <Spinner />; 5 } 6 if (error) { 7 return <Error message={error} />; 8 } 9 return <Profile user={data}/>; 10 }} 11 </Query>
1 <Query query={LOGGED_IN_USER}> 2 {({ loading, error, data }) => { 3 if (loading) { 4 return <Spinner />; 5 } 6 if (error) { 7 return <Error message={error} />; 8 } 9 return <Profile user={data}/>; 10 }} 11 </Query>
1 <Query query={LOGGED_IN_USER}> 2 {({ loading, error, data }) => { 3 if (loading) { 4 return <Spinner />; 5 } 6 if (error) { 7 return <Error message={error} />; 8 } 9 return <Profile user={data}/>; 10 }} 11 </Query>
1 <Container> 2 <Query query={LOGGED_IN_USER}> 3 {({ loading, error, data }) => { 4 if (data) { 5 return ( 6 <ModalManager> 7 {({ showModal, hideModal, visible }) => { 8 return ( 9 <React.Fragment> 10 <Button onClick={() => showModal()}>Click me!</Button> 11 {visible && ( 12 <Modal> 13 <h1>Hello {data.user.name}!</h1> 14 <Button onClick={() => hideModal()}>Close</Button> 15 </Modal> 16 )} 17 </React.Fragment> 18 ); 19 }} 20 </ModalManager> 21 ) 22 } 23 }} 24 </Query> 25 </Container>
1 <Container> 2 <Query query={LOGGED_IN_USER}> 3 {({ loading, error, data }) => { 4 if (data) { 5 return ( 6 <ModalManager> 7 {({ showModal, hideModal, visible }) => { 8 return ( 9 <React.Fragment> 10 <Button onClick={() => showModal()}>Click me!</Button> 11 {visible && ( 12 <Modal> 13 <h1>Hello {data.user.name}!</h1> 14 <Button onClick={() => hideModal()}>Close</Button> 15 </Modal> 16 )} 17 </React.Fragment> 18 ); 19 }} 20 </ModalManager> 21 ) 22 } 23 }} 24 </Query> 25 </Container>
1 <Container> 2 <Query query={LOGGED_IN_USER}> 3 {({ loading, error, data }) => { 4 if (data) { 5 return ( 6 <ModalManager> 7 {({ showModal, hideModal, visible }) => { 8 return ( 9 <React.Fragment> 10 <Button onClick={() => showModal()}>Click me!</Button> 11 {visible && ( 12 <Modal> 13 <h1>Hello {data.user.name}!</h1> 14 <Button onClick={() => hideModal()}>Close</Button> 15 </Modal> 16 )} 17 </React.Fragment> 18 ); 19 }} 20 </ModalManager> 21 ) 22 } 23 }} 24 </Query> 25 </Container>
1 <Container> 2 <Query query={LOGGED_IN_USER}> 3 {({ loading, error, data }) => { 4 if (data) { 5 return ( 6 <ModalManager> 7 {({ showModal, hideModal, visible }) => { 8 return ( 9 <React.Fragment> 10 <Button onClick={() => showModal()}>Click me!</Button> 11 {visible && ( 12 <Modal> 13 <h1>Hello {data.user.name}!</h1> 14 <Button onClick={() => hideModal()}>Close</Button> 15 </Modal> 16 )} 17 </React.Fragment> 18 ); 19 }} 20 </ModalManager> 21 ) 22 } 23 }} 24 </Query> 25 </Container>
1 <Container> 2 <Query query={LOGGED_IN_USER}> 3 {({ loading, error, data }) => { 4 if (data) { 5 return ( 6 <ModalManager> 7 {({ showModal, hideModal, visible }) => { 8 return ( 9 <React.Fragment> 10 <Button onClick={() => showModal()}>Click me!</Button> 11 {visible && ( 12 <Modal> 13 <h1>Hello {data.user.name}!</h1> 14 <Button onClick={() => hideModal()}>Close</Button> 15 </Modal> 16 )} 17 </React.Fragment> 18 ); 19 }} 20 </ModalManager> 21 ) 22 } 23 }} 24 </Query> 25 </Container>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // Game.js function mapStateToProps({ board }) { return board; } function mapDispatchToProps(dispatch) { return { makeMove: (index) => { dispatch(makeMove(index)); } }; } export default connect( mapStateToProps, mapDispatchToProps )(Game);
1 export default withRouter( 2 withTheme( 3 withSomeOtherState( 4 connect( 5 mapStateToProps, 6 mapDispatchToProps 7 )(Game) 8 ) 9 ) 10 );
1 export default withRouter( 2 withTheme( 3 withSomeOtherState( 4 connect( 5 mapStateToProps, 6 mapDispatchToProps 7 )(Game) 8 ) 9 ) 10 );
1 export default withRouter( 2 withTheme( 3 withSomeOtherState( 4 connect( 5 mapStateToProps, 6 mapDispatchToProps 7 )(Game) 8 ) 9 ) 10 );
1 export default withRouter( 2 withTheme( 3 withSomeOtherState( 4 connect( 5 mapStateToProps, 6 mapDispatchToProps 7 )(Game) 8 ) 9 ) 10 );
✨ Context ✨ @beccaliz
@beccaliz
provider state @beccaliz helpers
provider state helpers view @beccaliz
provider state helpers view consumer @beccaliz
provider state helpers view consumer @beccaliz
@beccaliz
@beccaliz
context provider @beccaliz
@beccaliz
@beccaliz
context provider @beccaliz
1 // GreetingModal.js 2 function GreetingModal() { 3 const { user } = React.useContext(LoggedInUserContext); 4 const { hideModal } = React.useContext(ModalContext); 5 6 return ( 7 <Modal id=”greeting”> 8 <h1>Hello {user.name}!</h1> 9 <Button onClick={() => hideModal()}>Close</Button> 10 </Modal> 11 ) 12 }
1 // GreetingModal.js 2 function GreetingModal() { 3 const { user } = React.useContext(LoggedInUserContext); 4 const { hideModal } = React.useContext(ModalContext); 5 6 return ( 7 <Modal id=”greeting”> 8 <h1>Hello {user.name}!</h1> 9 <Button onClick={() => hideModal()}>Close</Button> 10 </Modal> 11 ) 12 }
1 const ModalProvider = ({ children }) => { 2 const [visible, setVisible] = React.useState(false); 3 4 function showModal() { 5 setVisible(true) 6 }; 7 8 function hideModal { 9 setVisible(false) 10 }; 11 12 render() { 13 return ( 14 <ModalContext.Provider values={{ visible, showModal, hideModal }}> 15 {children} 16 </ModalContext.Provider> 17 ); 18 } 19 }
1 const ModalProvider = ({ children }) => { 2 const [visible, setVisible] = React.useState(false); 3 4 function showModal() { 5 setVisible(true) 6 }; 7 8 function hideModal { 9 setVisible(false) 10 }; 11 12 render() { 13 return ( 14 <ModalContext.Provider values={{ visible, showModal, hideModal }}> 15 {children} 16 </ModalContext.Provider> 17 ); 18 } 19 }
✨ Hooks ✨ @beccaliz
1 // reducers/game.js 2 function useGame(initialState) { 3 const [game, dispatch] = React.useReducer(gameReducer); 4 5 function makeMove(index) { 6 return dispatch({ type: “MAKE_MOVE”, payload: index }) 7 } 8 9 return { game, makeMove }; 10 }
1 // reducers/game.js 2 function useGame(initialState) { 3 const [game, dispatch] = React.useReducer(gameReducer); 4 5 function makeMove(index) { 6 return dispatch({ type: “MAKE_MOVE”, payload: index }) 7 } 8 9 return { game, makeMove }; 10 }
1 // reducers/game.js 2 function useGame(initialState) { 3 const [game, dispatch] = React.useReducer(gameReducer); 4 5 function makeMove(index) { 6 return dispatch({ type: “MAKE_MOVE”, payload: index }) 7 } 8 9 return { game, makeMove }; 10 }
1 function gameReducer(state, action) { 2 switch (action.type) { 3 case “MAKE_MOVE”: { 4 const index = action.payload; 5 const { currentPlayer, players } = state; 6 const nextPlayer = switchPlayer(currentPlayer, players); 7 return { 8 …state, 9 board: { 10 [index]: currentPlayer.marker, 11 currentPlayer: nextPlayer, 12 //…etc 13 } 14 }; 15 } 16 default: { 17 return state; 18 } 19 } 20 }
it(“allows a player to make a move”, () => { const { getByTitle } = render(<Game />); const spot = getByTitle(“0”); fireEvent.click(spot); expect(getByTitle(“0”).textContent).toEqual(” }); “);
Do you need Redux?
Complexity @beccaliz
MobX? @beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
How do we choose? @beccaliz
You don’t have to choose just one. @beccaliz
What is the scope of the state? @beccaliz
Is there a commonly-used library that can help? @beccaliz
Am I repeating myself? @beccaliz
Incremental Changes @beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
@beccaliz
The Newbie @beccaliz
The Loyalist @beccaliz
The Explorer @beccaliz
✨ Thank you!! ✨ @beccaliz becca.is Slides:
Redux? Render props? Context? Apollo? And what are these hooks that everyone is talking about? If you have ever gotten lost in the multitude of ways to manage state in your React application in 2019, you’re not alone. Let’s compare the options, and talk about the state of state management today.
Here’s what was said about this presentation on social media.
It's not about the library you choose for state management but how you think about the problem you're trying to solve to choose what works best for you. @beccaliz gave a great talk about it at #ReactConf2019, including some examples about how to do it using only React APIs pic.twitter.com/B6iSyUrFD5
— Rubén Norte (@RubenNorte) October 25, 2019
Booyeah! 👋 React @TestingLib! 🐐
— Kent C. Dodds 🧢 (@kentcdodds) October 24, 2019
As seen in @beccaliz's talk at #ReactConf2019 👍👍 pic.twitter.com/FMZRXTPSBO
LOVED the simplicity and clarity of @beccaliz 's talk on state management and how to manage the decisions (and conflicts, code & otherwise :) ) around them!! Great all-level talk #reactconf
— Michelle C. Funk 💗💜💙 (@michellecohrene) October 25, 2019
@beccaliz hit the nail on the head when it comes to state management strategies in React apps 👏👏 #reactconf #reactconf2019
— Ashley Narcisse @ ReactConf ⚛️ (@_darkfadr) October 25, 2019
At #ReactConf @beccaliz: Redux? Render props? Context? Apollo? Compares the options, and talk about the state of state management today.
— Leonardo Zizzamia @ ReactConf (@Zizzamia) October 25, 2019
One of my favorite talk so far, and the video it is going to teach and help a tons of devs. 🙌 pic.twitter.com/dcUvzJFgC2