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.
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.).
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.
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.
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.
Here are some implications of calling component as function vs rendering it as element.
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.
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>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With