Stop accessing React context directly

Cody Patnaude
Nerd For Tech
Published in
3 min readApr 1, 2021

--

I was doing a code review for a colleague of mine and came across some code that immediately sent up a red flag in my mind.

<Checkbox label={translationContext.getTranslation(checkboxLabel)}/>

This was similar to code I had written many times, but during this particular review, this line of code jumped out at me. I started thinking, we’re exposing the entirety of the translation context in this component? What if we need to change it? There has to be a better way! So I began looking at the rest of our code and found, this was NOT the fault of this developer. This was a pattern (or should I say anti-pattern) that was strewn all over the place! Looking in some other sections I found some examples like this:

... inside functional component
const getNextSection = () => {
valid = context.actions.validateCurrentSection()

if(!valid){
return context.actions.showErrors()
} else {
return context.actions.getNextSection()
}
}

We had created this entire pattern where our context object had this actions member that stored all the functions that handled state changes within the context. Thankfully we were using typescript so all the functions that existed within actions were documented and could be autocompleted. But this meant that EVERY component needed access to the entire context, no matter how little information it actually needed. Even worse this meant that every time the context changed the entire component re-rendered! Even if the data that changed wasn’t actually being used by the component in question.

This also introduced some other issues. What if we needed to move these functions to a different context in the future? We would need to track down every time that function is accessed via context.actions.functionIWantToMove, and change the context the function is being called on, meaning I would have to give that component full access to a SECOND context. All so it could access one piece of data or call a single function.

I did a quick git blame to see who could have possibly implemented such an irresponsible pattern and, of course, it was me. Clearly this is a horrible way to do things. We needed a better solution.

React hooks to the rescue!

One of the things React is really good at is separation of concerns. No feature embodies this more so than hooks. I went back to that line that originally caused the red flags, I’ve expanded the code to provide some more context:

import React, {useContext} from "react"
import {TranslationContext} from "../contexts/translation-context.tsx"
const CheckboxGroup = ({options}) => (
translationContext = useContext(TranslationContext)
<div>
{options.map(option => (
<Checkbox label={translationContext.getTranslation(option.text) />
))
}
</div>
)

We were quickly able to whip up a hook that accomplished this in a much cleaner way. We did end up having to wrap the Checkbox components in a wrapper component, but I think it was worth it.

import React from "react"
import {useTranslate} from "../translate"
const TranslatedCheckbox = ({label}) => {
const translatedValue = useTranslate(label)

return <Checkbox label={translatedValue} />
}

Then my outer CheckboxGroup component ended up looking like:

const CheckboxGroup = ({options}) => (
<div>
{options.map(
option => <TranslatedCheckbox label={option.label}/>
)
}
</div>
)

This ended up working out SO much better. Not only was it cleaner, but it fixed our leaky abstraction by hiding the translation’s implementation details. Now the only components that were actually DOING the translating needed to know about the TranslationContext.

I just wanted to note that the code snippets featured here are contrived examples used to make a point, edited down to show only the relevant parts. I am aware they will not run as is. Hopefully the point still gets across.

--

--

Cody Patnaude
Nerd For Tech

Full stack web developer and software engineer for 10+ years