Notes

Getting started with React Context

Edit on GitHub

ReactJS
5 minutes

Are we doing the Context vs. Redux debate? No. Just consider this article as someone who hasn’t heard of Redux, someone who’s new to React, managing state that needs to be shared by multiple components, with React’s built-in functionality.

Every component that’s using a particular context re-renders if anything tin that context changes.

Provider will have hooks and redux logic

  • <Provider> must be passed a value prop, this value is what the Consumer will have access to. This will be your values in state and funtcions to modify state etc.
 1import React, { createContext, useContext, useState } from 'react'
 2
 3import { sampleData } from 'sample-data.ts'
 4
 5const ListsContext = createContext(sampleData.lists)
 6ListsContext.displayName = 'ListsContext'
 7
 8// the hook to use our context and have reducer logic
 9function useListsContext() {
10  const context = useContext(ListsContext)
11  if (!context) throw new Error('ListsContext must be used with ListsProvider')
12  return context
13}
14
15// the provider we'll export
16function ListsProvider(props: any) {
17  const [data, setData] = useState(sampleData.lists)
18
19  const value = { data, setData }
20
21  return <ListsContext.Provider value={value} {...props} />
22}
23
24export { useListsContext, ListsProvider }
 1import React, { Fragment } from 'react'
 2
 3import { Text } from 'react-native'
 4
 5import * as Type from './Lists.types'
 6import { useListsContext, ListsProvider } from './ListsContext'
 7
 8export default function Lists(props: any) {
 9  const data: Type.Lists = useListsContext()
10
11  return (
12    <ListsProvider>
13      <Fragment {...props}>
14        <Header>
15          <Text>Lists</Text>
16        </Header>
17        <Body>
18          {data.map((list: Type.List) => (
19            <Item key={list.id}>
20              <Title>{list.title}</Title>
21              <Description>3 items</Description>
22            </Item>
23          ))}
24        </Body>
25      </Fragment>
26    </ListsProvider>
27  )
28}

Leigh Hailday passes {children} https://www.youtube.com/watch?v=u06qAON66iw

Kent C. Dodds does not https://kentcdodds.com/blog/application-state-management-with-react

without context

 1import React from 'react'
 2
 3import { View } from 'react-native'
 4import styled from 'styled-components/native'
 5
 6import { Color } from 'Theme'
 7
 8interface TagProps {
 9  title: string
10  desc: string
11  color: string
12}
13
14interface TagsProps {
15  data: any
16}
17
18export function Tag({ title, desc, color, ...rest }: TagProps) {
19  return (
20    <StyledTag color={color} {...rest}>
21      <Title>{title}</Title>
22      <Description>{desc}</Description>
23    </StyledTag>
24  )
25}
26
27export default function Tags({ data, ...rest }: TagsProps): JSX.Element {
28  return (
29    <Container {...rest}>
30      {data.map((tag) => (
31        <Tag key={tag.id} title={tag.title} desc={tag.description} color={tag.color} />
32      ))}
33    </Container>
34  )
35}

with context

 1// TagsContext.tsx
 2import React, { createContext, useContext, useState } from 'react'
 3
 4import { sampleData } from 'sample-data'
 5
 6const TagsContext = createContext(sampleData.tags)
 7TagsContext.displayName = 'TagsContext'
 8
 9export function useTagsContext() {
10  const context = useContext(TagsContext)
11
12  if (!context) throw new Error('useTagsContext must be used within TagsProvider')
13  return context
14}
15
16export function TagsProvider(props: any) {
17  const [data, setData] = useState(sampleData.tags)
18
19  const value = { data, setData }
20  return <TagsContext.Provider value={value} {...props} />
21}
 1// Tags.tsx
 2import React from 'react'
 3
 4import styled from 'styled-components/native'
 5
 6import { Color } from 'Theme'
 7
 8import { useTagsContext, TagsProvider } from './TagsContext'
 9
10interface TagProps {
11  title: string
12  desc: string
13  color: string
14}
15
16export function Tag({ title, desc, color, ...rest }: TagProps): JSX.Element {
17  return (
18    <StyledTag color={color} {...rest}>
19      <Title>{title}</Title>
20      <Description>{desc}</Description>
21    </StyledTag>
22  )
23}
24
25export function Tags(props: any): JSX.Element {
26  const data = useTagsContext()
27
28  return (
29    <TagsProvider>
30      <Container {...props}>
31        {data.map((tag) => (
32          <Tag key={tag.id} title={tag.title} desc={tag.description} color={tag.color} />
33        ))}
34      </Container>
35    </TagsProvider>
36  )
37}

Usage with useReducer

Debugging and DevTools

Standalone react-devtools exist, but the Context shows as a component inside the veryyyy long component tree. And you can’t search with Ctrl + F even if you give your context a displayName. Have fun finding your context provider and consumer..

You should definitely enable console.log by running npx react-native log-android while the app is running. You only have to enable it once.

react-native-debugger for Redux is far superior. react-context-devtool looks good, but that only appears to be working with React for web and not React Native. Same for reactext

Persisting Context

No out of the box solution exists. It’d probably be a manual implementation along the lines of saving every change to LocalStorage (web) or AsyncStorage (native), and then reading from storage instead of Context if the app is offline

Redux vs. Context

  • React Native Debugger works with Redux
  • Persisting state and offline data is simple with redux-persist
  • Type definitions are included in Redux Toolkit. With React Context manually adding the types every time is an asolute annoyance.

Conclusion

I built an app where a couple of components were using their own contexts. I gave up at the point where i couldn’t debug the actions that i was dispatching and being unable to see a nice diff in state change that was happening as a result..

Switching from Redux to React Context felt like i was going out of my way to make my dev life difficult. Things that just worked with Redux (and Redux Toolkit) like type definitions, devtools are something you have to put in extra manual effort for in React Context.

As of this writing, i still fail to understand why i shouldn’t be using Redux. I hated its verbosity at one point, but Redux Toolkit (which is now the default approach) has made it cool and typescript friendly. Context is not at par in terms of DX.

I would stick with Redux just for the DevTools alone. If i had to switch, i’d probably invest in learning MobX..

Other random questions

  • How do i handle updating one context from inside another when i’m using multiple contexts? For example, adding messages to a conversation..
  • How do i cancel dispatched actions? Cancel an AJAX request? Redux Sagas or Redux Observables? Can i do that with React Context?