Updating React context outside of a component

Published on in JavaScript, React and Testing

React context is normally updated inside the React app. Here's a hacky way how I circumvented that restriction in a test file.

Table of contents

Example problem

Let's say I have a React component called MyComponent which uses a React context called MyContext.

When the value of the context changes, the component does something, e.g. makes an Adobe Analytics tracking call:

function MyComponent() {
  const myContext = useContext(MyContext)

  useEffect(() => {
  }, [myContext])

  return null

And then I want to write unit tests for that... But how can I do that since React contexts can't be easily updated outside of React components?

Hacky workaround

I realized that in the test file, I can pass an empty object for the wrapper component (App in this case) as a prop (named contextUpdater).

Inside App, I can store a reference to the context value setter function (setValue in this case) under the given prop object.

Then, in a test case, I can call contextUpdater.setValue – outside of the component!

Like so:

/* MyComponent.test.js */

describe('MyComponent', () => {
  function App({ contextUpdater = {} }) {
    const [value, setValue] = useState('initial value')

    // This hack allows us to update the context value outside of the component
    contextUpdater.setValue = setValue

    return (
      <MyContext.Provider value={[value, setValue]}>
        <MyComponent />

  beforeEach(() => {
    global._satellite = { track: jest.fn() }

  it('makes a tracking call when MyContext changes', () => {
    const contextUpdater = {}

    render(<App contextUpdater={contextUpdater} />)

    act(() => contextUpdater.setValue('new value'))

    act(() => contextUpdater.setValue('third value'))

It works! IQ 200! Proud moment of the week.

Too hacky?

So yeah, actually this feels very hacky, probably because this is hacky.

Another question is whether the test is too implementation-specific. On the other hand, MyComponent directly depends on MyContext, so I'm not sure.

Anyway, the test passes, so I think this is good enough for now.