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

  1. Clarify @beccaliz the problem

  1. Clarify the problem 2. Explore some solutions @beccaliz

  1. Clarify the problem 2. Explore some solutions 3. Make decisions @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

} semi­local 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: