This is the presentation I gave to my local JavaScript North West meetup group in Manchester, UK on 16th October 2018. The presentation demostrated local state vs Context API vs Redux, and when was appropriate to use each tool and some best practices around all three.
2. Hello!
• Jon Preece, front-end developer
• Currently specializing in React
• Active on GitHub: jpreecedev
• Email: jonpreece@hotmail.co.uk
(I’m not on social media, sorry!)
3. Assumptions
• Intermediate knowledge of JavaScript and React
• This is a presentation about React
• React 16.3 or higher
• (This was when the updated version of Context API was first released)
• Feel free to ask questions
4. What is state?
• Allows you to create components that are
dynamic and interactive
• Allows your application to respond to;
• User actions, network responses, anything else
5. What tools are at your disposal?
• Local state
• Context API
• Redux
• (And many, many more)
7. Which one should I choose?
• It’s not quite that simple
• Local state works well with forms
• Context API is hierarchical, helps with
*prop drilling*
• Redux works well when sharing
8. Demo 1: Local state
• Form gets passed an initial state
• Container/presentation component relationship,
form data is passed up
• Form can be reset after user has made changes
• We call setState instead of mutating state directly
9. Local state
• State for the form is contained
• Calling setState causes the component to re-
render, can be asynchronous and batched
• There is hidden pain here
10. Demo 2: Context API
• What do we mean by *prop drilling*?
• What does Context API look like?
• Provider, Consumer
11. Context API
• Provider exposes state to Consumer
• Provider is a fancy wrapper around setState
• Can dramatically tidy your code, has little
learning curve, easy to conceptualise, is
syntactically fiddly
12. Demo 3: Redux
• Always has a root level Provider with a default state
• Uses connect function to retrieve state from the
store
• Dispatches an action, triggers a reducer, which
creates a new state, which causes a re-render
13. Redux
• More complexity
• Actions, reducers, connect, immutable
• Scales well, performant
• Excellent debugging, time travel, unit testing is
straightforward
14. Best practices / Common Patterns
• Do use the right tool for the job
• Redux is not dead
• Start with Context API, add Redux if/when
needed
• Avoid *prop drilling*
• Generally avoid local state, becomes unwieldly
quickly, increases test complexity
15. Best practices / Common Patterns
• When working with local state, prefer
container/presentation relationship
• Avoid ref
• Lift the state up
16. Summary
• Know tools at your disposal
• Finally… this space is still evolving.
• GraphQL and Apollo Client are gaining popularity quickly
17. Hello!
• Jon Preece, front-end developer
• Currently specializing in React
• Active on GitHub: jpreecedev
• Email: jonpreece@hotmail.co.uk
Notas del editor
State is what makes your application interesting.
When discussing, ”Many, many more” be sure to mention that there is a list of the most popular libraries on a Hackernoon post called ‘The React State Museum” (https://hackernoon.com/the-react-state-museum-a278c726315)
What do I mean when I talk about Local State? I’m specifically talking about using the function `setState`, which comes out of the box with React
Local state refers to state which is contained inside a single component, usually in a container/dumb component relationship
Local state has been a part of React *forever*
Context API was experimental until 16.3 when a new revised version was released. React-Redux is a thin wrapper over the legacy Context API.
Context API is hierarchical by design, so only applies to a component and all its children. You can put Context API at the root of the application, but if you have multiple applications running on the same page they wont be able to share stata. And also, for everything more than the most basic applications, I see this becoming un-wiedly very quickly.
Redux keeps the state of your application in a single place, called the store. The store is immutable and cannot be updated directly. To update the store, you call a function (called a reducer) which makes the changes, which updates the store, which causes your application to re-render. State is the data, store is where the data is kept.
One does not simply choose a state management tool
Know your API’s / libraries
There is no reason why you can’t have local state, Redux & Context API all working together in a single application
You probably wouldn’t want to choose multiple tools such as Redux, MobX and Unstated in a single React application, because that would add several paradigms and increase complexitity.
One does not simply choose a state management tool.
Know your tools
Local State is perfect for normal forms such as log in forms, registration forms, your e-commerce sites checkout, etc. I like to use local state when I’m handling data that isn’t final yet.
Context API has a Provider/Consumer pattern. At the root of a component hierarchy (or the root of your application) sits a provider component which contains the state and functions to change the state. The consumer is used on a component anywhere in the same hierarchy, meaning that state doesn’t have to be passed down the tree (called prop drilling)
Redux is strikingly similar to Context API in that it effectively has a Provider/Consumer pattern, although its less obvious. The main difference is that instead of having potentially many different states, there is a single state contained within a global store, that can be accessed using a function called `connect`, which can be used to access the state and pass that state through to the component.
setState is only asynchronous when passed a function, which returns the current state. When called with an object, the function call is executed synchronously. In the interest of future proofing your code (I’m thinking about React Fiber here), you should write your code to work asynchronously.
What is the hidden pain?
This is a fairly contrived example where we are passing an initial state object to the registration form. What happens, if, after the form has been rendered the first time, the initialState object gets changed, causing a re-rendering?
Answer: The registration form will not be reflect the changes, because the local state never gets updated. We used to have a component lifecycle method called ComponentWillReceiveProps, which would pass you the local state and the updated props so you could update your component accordingly, but this lifecycle method has recently been deprecated and will be removed entirely from React in the next major version release.
Context API to me is really just a wrapper around setState. To use Context API, I wrap the Provider up in a React component, which also carries the state and functions to update the state. Then I use a Consumer to acess the state and those functions to change the state when there is some kind of interaction from the user.
Can tidy the code because you don’t have calls to setState all over the place, you get given a state object, which you can deconstruct off you state manipulation functions. No need to say ‘this.state.myProp’ either, just ‘myProp’., which reduces noise. This pattern is easy to conceptualise and reason about, making it approachable to developers of all levels.
I have a few complaints. Almost all examples will show usages of the Conumer with ES6 arrow functions and destructuring. Also because of JSX we have to throw in an extra set of curly braces. This makes it syntactically fiddly, even with editor tools such as ‘Indent Rainbow’ and ‘Bracket Pair Colorizer’ I still find myself awkwardly trying to get my brackets to match up.
Mention that Enzyme test framework has poor support for Context API currently, although there are workarounds, and this will likely improve in upcoming major releases.
Why is unit testing straight forward? Your component is often just a stateless functional component, which takes props and has no other external dependencies. In short, your component is a function that takes input and returns output in a simple way.
Usually your component will have a default export that connects your component to the Redux store. However, for the sake of unit testing you can simply export your function as-is, avoiding any complete set up, mocking of stores, providers etc, resulting in smaller, cleaner test suite.