13

I have a growing number of custom hooks and many of them access the same react context via the useContext hook. In many components there is the need to use more than one of these custom hooks.

Is it better to call useContext once per component and pass the context into my custom hooks or is it better to call useContext within each custom hook? There may not be a right or wrong answer, but by "better" I mean is there a best practice? Does one approach make more sense than the other?

user1843640
  • 3,613
  • 3
  • 31
  • 44
  • 3
    I guess this is entirely subjective but I would lean towards thinking if each of your custom hooks are dependant on the context, add a `useContext` into each the custom hooks rather than passing it in. There would be more `useContext` across your codebase but you would avoid having to always pass it down. If you imagine a scenario where you have one component that only has one of your custom hooks, it would be self contained in that scenario and you wouldn't have to import your context in that component, only the custom hook. – Tom Finney Mar 13 '19 at 17:14
  • 1
    I'd suggest the title of this question should be "Is it better to call useContext inside a custom hook or pass data in?" - it seems like custom hooks are the real focus here, not the number of times `useContext` is called. – Retsam Mar 13 '19 at 19:20
  • @TomFinney, that's what I've been doing but not sure it feels right. As the code base gets larger, the potential refactoring gets larger, hence the question. – user1843640 Mar 13 '19 at 20:26
  • 1
    @Retsam, good suggestion to change the title. – user1843640 Mar 13 '19 at 20:28

1 Answers1

16

I would suggest passing the context in, personally: I believe that will make the custom hook more clear, more flexible and more testable. It decouples the logic that operates on the context data from the logic responsible for getting that data.

Clarity

If you use useContext inside a custom hook, that becomes an implicit contract for the hook: it's not clear from looking at the call signature that it depends on values from context. Explicit data flow is better than implicit data flow, generally. (Of course, the Context API exists because sometimes implicit data flow is useful, but in most cases I think it's better to be explicit)

Flexibility

You might at some point find a component that needs to leverage the logic contained in the custom hook, but needs to provide a different value than the one on the context, or perhaps wants to modify the value. In this case, it'd be very convenient to do this:

const MySpecialCaseComponent = () => {
    const context = useContext(MyContext);
    useMyCustomHook({
       ...context,
       propToOverride: someValue
    });
    return (<div></div>)
}

That's highly inconvenient if the custom hook reads straight from context - you'd probably have to introduce a new component, wrapped in a new context provider.

Testability

It's easier to test a custom hook if it doesn't depend on context API. Perhaps in the simplest cases, you can just call your custom hook with test data and check the return value.

Or you can write a test like:

test("Test my custom hook", () => {
    const TestComponent = () => {
        useMyCustomHook({ /** test data */ });
        return (/* ... */);
    };
    const element = shallow(<TestComponent />);
    // do testing here
})

If you use context in your hook, then you'd have to render your test component inside a <MyContext> provider, and that makes things more complicated. Especially if you're trying to do shallow render (and even more so if you're using react-test-renderer/shallow, testing components with context is more complicated.


TL;DR I don't think it's wrong to useContext inside a custom hook, but I think explicit data flow should be your first resort, for all the usual reasons that explicit data flow is generally preferred over implicit data flow.

Retsam
  • 30,909
  • 11
  • 68
  • 90
  • 4
    Some good points. The biggest benefit of calling useContext in the custom hooks is that it makes for a cleaner looking and less verbose component. That said, I agree with your points on being explicit and I will likely follow your suggestion. – user1843640 Mar 13 '19 at 20:31
  • 2
    Another thing not accounted for: sure, not putting useContext in your hook means you can test without relying on useContext; but if you put useContext in your hook then your component does NOT rely on useContext. It's a question of where do you want the useContext dependency for testing purposes; you'll have to mock it somewhere. – user1713450 Jan 30 '21 at 05:04