Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React hooks: call component as function vs render as element

Say we have component:

let Component = (props)=><div>Hi</div>;

I have sometimes come across code where someone calls a react component as function in render:

 const App = () => (
     <div> {Component()} </div>
 )

vs rendering it as element

 const App = () => (
     <div> <Component/> </div>
 )

In react hooks, what are possible drawbacks of calling component as function?


There is similar question but it is not specifically targeted at hooks - also that question is more about performance.

like image 472
Giorgi Moniava Avatar asked Oct 04 '21 07:10

Giorgi Moniava


People also ask

Can we call React component as function?

When a function is called directly as Component() it will just run and (probably) return something. No lifecycle, no hooks, none of the React magic. It's very similar to assigning some JSX to a variable, but with more flexibility (you can use if statements, switch, throw, etc.).

Are React hooks called every render?

Yes they are called on each render, in the first render it initialise a memory cell, on re-render it read the value of the current cell : When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one.

Can you use React hooks in functions?

Don't call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders.

Do hooks replace render props and higher order components?

No, they do not. Historically they often were, since that was the only way to have state and lifecycles, but now you could write them as a functional component.


1 Answers

Here are some implications of calling component as function vs rendering it as element.

  1. Potential violation of rules of hooks

When you call a component as a function (see TestB() below) and it contains usage of hooks inside it, in that case react thinks the hooks within that function belongs to the parent component. Now if you conditionally render that component (TestB()) you will violate one of the rules of hooks. Check the example below, click the re-render button to see the error:

Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.

 
function TestB() {
  let [B, setB] = React.useState(0);
  return (
    <div
      onClick={() => {
        setB(B + 1);
      }}
    >
      counter B {B}
    </div>
  );
}
 

function App() {
  let [A, setA] = React.useState(0);

  return (
    <div>
      <button
        onClick={() => {
          setA(A + 1);
        }}
      >
        re-render
      </button>
      {/* Conditionally render TestB() */}
      {A % 2 == 0 ? TestB() : null}
    </div>
  );
}
ReactDOM.render(
  <App />,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Now you can use <TestB/> instead and see the difference.

  1. Reconciliation might not work as expected

When you render a react component as react element say <TestB/> and then on next render you render some different component <TestC/> instead of it (in the same place in component hierarchy), due to reconciliation algorithm (and since component type has changed), react will unmount <TestB/> component (all its state will be gone) and mount a new component <TestC/> instead.

If you call it as function however (e.g. TestB()), the component type will not participate in reconciliation anymore and you might not get expected results:

function TestB() {    
  return (
    <div     
    >
      <input/>
    </div>
  );
}
function TestC() {
  console.log("TestC")
  return (
    <div     
    >
      <input/>
    </div>
  );
}

function App() {
  let [A, setA] = React.useState(0);

  return (
    <div>
      <button
        onClick={() => {
          setA(A + 1);
        }}
      >
        re-render
      </button>
      {/*  Here we are alternating rendering of components */}
      {A % 2 == 0 ? TestB() : TestC()} 
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
    <div id="react"></div>
  • Type something in the input
  • Now click the re-render button
  • You can see now from the log that component TestC was rendered, but the input shows the same value you typed before - which might not be what you want as you rendered a different component. This happened because reacts reconciliation algorithm couldn't detect that we moved to a different component (from TestB to TestC) and didn't remove previous input instance from DOM.

Render these components as elements now (<TestB/> and <TestC/>) to see the difference.

like image 99
Giorgi Moniava Avatar answered Oct 22 '22 00:10

Giorgi Moniava