Incorrect use of react hooks but no hook errors/faults

Viewed 55

React hooks can only be used inside

  • Functional components
  • Other hooks

If you don't follow the above rules, react complains about not following the rules via a fault

But, the below code works (albeit with lint errors) without any stack-trace/errors

import React from 'react'
import { useState, createContext, useContext } from "react";

const SomethingContext = createContext("what is the thing");

// Correct usage
const useSomething = () => {
  const something = useContext(SomethingContext);
  return something;
};

//Incorrect usage
const getTheThing = () => {
  const theThing = useContext(SomethingContext); // works with useState as well (ex below)
  return theThing;
};

//Incorect usage
const getTheThingState = () => {
  const [theThing, setTheThing] = useState("I am the state"); 
  return theThing;
};

const Child = () => {
  const theThing = getTheThing(); //OR getTheThingState()
  return `The thing is "${theThing}"`;
};

export default function App() {
  return (
    <div className="App">
      <SomethingContext.Provider value={"it is something"}>
        <Child />
      </SomethingContext.Provider>
    </div>
  );
}

Here's a demo of the above code. The linter points out the incorrect usage, but the odd part where I'm confused is that React itself doesn't throw any error.

Not able to figure out WHY?

All resources point out to fix the above problem by changing the function into a component i.e function's variable name (I'm aware of it)

const GetTheThing = () => {
//...

But, when does react decide to say "okay, you are not supposed to do that" vs "okay, I'll let this one slide".

1 Answers

Technically there is no problem with the code provided. As the invocation of useContext and useState happening inside component's code. And there is no issue of having one outer function, which just calls the hook inside. Yes, we are breaking the Only Call Hooks at the Top Level rule. But if you go through rationale behind this rule - you'll see that the ordering of invocations is the key here. And having function without any nested if statements still guarantees the ordering. So your code:

export const useSomething = () => {
  const something = useContext(SomethingContext);
  return something;
};

const getTheThing = () => {
  const theThing = useSomething();
  return theThing;
};

const Child = () => {
  const theThing = getTheThing();
  return `The thing is "${theThing}"`;
};

Still complies with correct ordering of:

  • Render fn of Child component started
  • useContext invoked
  • Render fn of Child component finished
Related