citykid
citykid

Reputation: 11070

What exactly makes the difference between a "React function" in contrast to a "regular Javascript function"?

The react docs boldly state that hooks shall only be called inside "React functions":

https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions

"Don’t call Hooks from regular JavaScript functions."

That raises the question what precisely distinguishes a React function from a regular js function. Is it the return of a JSX Element? How about those functions:

function F1() { return <div>hello</div> } // certainly can use hooks 

function F2() { return F1() }   // can use hooks? is a react function? is a regular js function?
function F2() { return <F1 /> } // makes no difference, right?

function F3(s) { return <div>{s}</div> } // is a react function because uses jsx?  

function F4() { return F3("bugs bunny") }

function F5({s}) { return <div>{s}</div> }

They all return a JSX Element. But I am not sure that f2 or f4 really are React functions and participate in the hook attachment. The way we pass arguments should only not matter. So the question: What exactly makes the difference?

(I know how JSX, hyperscript work, no need to explain those basics. I just did not look into the internals of the hook system.)

Upvotes: 1

Views: 291

Answers (2)

Jeff Bowman
Jeff Bowman

Reputation: 95634

You get to decide whether a function is a React function, a custom function, or a regular function, which defines who can call it from where. If F1 is a React function (functional component) you can call it as <F1 /> but should not call it as F1(); if F1 is a regular function you can call it as F1() but should not call it as <F1 />. The naming convention (F1, f1, or useF1) would help you distinguish which type of function you intend, which helps you and the linter hold to those rules; things might work if you break the rules, but they also might fail in difficult-to-debug ways.

  • React function (functional component): Call from createElement or JSX. Can call hooks according to the rules and should return createElement or JSX expressions. Named with upper camel case (Foo) by convention.
  • Custom hook: Call from functional components or other custom hooks. Can call hooks according to the rules; can return anything. Named with lower camel case starting with "use" (useFoo) by convention.
  • Regular function: Call from anywhere. Shouldn't call hooks; can return anything. Named with lower camel case (foo) as usual.

In addition to Nicholas Tower's answer, you should observe that <F1 /> and F1() are not identical. From the "JSX Represents Objects" header of React's Main Concepts / Introducing JSX:

Babel compiles JSX down to React.createElement() calls.

These two examples are identical:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

Specifically, this example:

function F1() {
  return <F1>hello</F1>;
}

function F2() {
}

compiles to this JS:

"use strict";

function F1() {
  return /*#__PURE__*/React.createElement(F1, null, "hello");
}

function F2() {}

F1 doesn't call F2, F1 passes F2 to React.createElement(). Consequently, createElement gets to call F2 exactly and only when it is ready to do so, allowing it to create a fresh internal state (i.e. history of hook calls). So when you define F2 like this:

// React function, returns JSX
function F2() {
  useState('Mary')           // 1. Initialize the name state variable with 'Mary'
  useEffect(persistForm)     // 2. Add an effect for persisting the form
  useState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'
  useEffect(updateTitle)     // 4. Add an effect for updating the title
  useLogin()                 // 5 & 6
}

// Custom hook, can call hooks according to rules, named "use" by convention
function useLogin() {
  const [user, setUser] = useState(getDefaultUser());
  const [domain, setDomain] = useState(getDomain());
}

// Normal function, can't call hooks
function getDefaultUser() {
  return "defaultUser";
}

...then React can generate a new hook state for that particular component invocation, call F2 itself, and then wind up with an internal state that looks like [useState, useEffect, useState, useEffect, useState(useLogin:user), useState(useLogin:domain)]. Because of the rules for hooks barring conditionals and loops, if you've done it right, every invocation of F2 will wind up with exactly those six entries. If you use hooks from a "regular Javascript function" like getDefaultUser(), it might work by accident, but it will be harder to understand the usage and hold to the rules.

Thus, you are the one that determines which type of function a function is, all in service of keeping that state predictable.

Upvotes: 1

Nicholas Tower
Nicholas Tower

Reputation: 84947

Whether the function is a component or not comes down to how you will use the function. Do you plan to render it as a JSX element, eg: <f1 />? If so, it's a react component and it can use hooks (though you will need to rename it to uppercase, as in <F1 />). During the render process, react will call your function, but only after it has set up the appropriate internal state so that the hooks can work correctly.

On the other hand, if you plan to call it directly (eg, f1()), you can't set up react's internal state for it, and react won't know you're calling the function so it can't do the setup for you. Thus, the only way you can get away with calling hooks in this kind of function is if you exclusively call this function while another component is rendering. This is called a "custom hook". They're a very useful tool for reusing code, but you should use the naming convention of starting with use, so that the programmer and the lint tools know to enforce the rules of hooks.

Upvotes: 2

Related Questions