Reputation: 2035
function renderSomething(foo) {
return <div>sth {foo}</div>
}
function Something({ foo }) {
return <div>sth {foo}</div>
}
function Component(props) {
const { foo } = props
return (
<>
{renderSomething(foo)}
<Something foo={foo} />
</>
)
}
The JSX result of renderSomething()
and <Something />
is identical. I wonder what's the difference(e.g. render way, behavior, influence, etc) between these two ways?
And what applicable scenario for render method(i.e. renderSomething()
)? Can I use hooks inside?
Upvotes: 22
Views: 6943
Reputation: 1074335
The JSX result of
renderSomething()
and<Something />
is identical.
renderSomething(foo)
and <Something foo={foo} />
do quite different things, but in that specific usage, the end result is the same: React creates a React element that tells it to render a div
later (if the element is used). Doing renderSomething(foo)
is treating renderSomething
like a hook, which has implications for how you use it when when it runs.
Here are two key differences:
With renderSomething(foo)
, your code is calling the function immediately and passing in the argument. With <Something foo={foo} />
, you aren't calling Something
. You're asking React to remember it and call it later if/when it needs to render the component where you did these calls.
Because your code, not React, calls renderSomething
, if you used hooks in it, they'd be putting information in the calling component's instance and state information. In contrast, if Something
uses hooks, that instance and state information is stored in a component instance for Something
itself.
(There are other differences. You can't use renderSomething
as a component via <renderSomething foo={foo} />
because function component names must start with upper case to differentiate them from HTML elements, and because renderSomething
expects a string argument, not a props object.)
Let's look at a couple of those differences more closely:
function renderSomething(foo) {
console.log(`renderSomething: Called with foo = ${foo}`);
return <div>sth {foo}</div>;
};
function Something({ foo }) {
console.log(`Something: Called with foo prop = ${foo}`);
return <div>sth {foo}</div>;
}
console.log(`Creating elements via renderSomething("x"):`);
const ex1 = renderSomething("x");
console.log(`Creating elements via <Something foo="x" />:`);
const ex2 = <Something foo="x" />;
console.log(`Done`);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Notice that the code called renderSomething
(you see its console.log
occur), but didn't call Something
(you don't see its console.log
). That's because of difference #1 above. The key thing is that a component's function is called only if it needs to be rendered. We haven't rendered either result above, so the function is never called.
The second difference is more subtle, but let's suppose two things:
Example
) may or may not need to render it.That works just fine with Something
:
const { useState, useEffect } = React;
function Something({ foo }) {
const [thing, setThing] = useState(null);
useEffect(() => {
setTimeout(() => { // Fake ajax
setThing("thing");
}, 100);
}, []);
return <div>foo = {foo}, thing = {thing}</div>;
}
function App() {
const [foo, setFoo] = useState("");
return <div>
<div>
<label>
<input type="checkbox" checked={!!foo} onChange={() => setFoo(foo => foo ? "" : "hi")} />
Toggle "foo"
</label>
{foo ? <Something foo={foo} /> : null}
</div>
</div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
label {
user-select: none;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
But now let's try it with renderSomething
:
const { useState, useEffect } = React;
function renderSomething(foo) {
const [thing, setThing] = useState(null);
useEffect(() => {
setTimeout(() => { // Fake ajax
setThing("thing");
}, 100);
}, []);
return <div>foo = {foo}, thing = {thing}</div>;
}
function App() {
const [foo, setFoo] = useState("");
return <div>
<div>
<label>
<input type="checkbox" checked={!!foo} onChange={() => setFoo(foo => foo ? "" : "hi")} />
Toggle "foo"
</label>
{foo ? renderSomething(foo) : null}
</div>
</div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
label {
user-select: none;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
It blows up when you tick the box with this error:
Warning: React has detected a change in the order of Hooks called by App. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks Previous render Next render ------------------------------------------------------ 1. useState useState 2. undefined useState ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at App (<anonymous>:35:22)
That's because that makes the code call renderSomething
when we didn't call it on the first render. That's because of #2 above: Since renderSomething
isn't a component function, it doesn't get its own component instance, and any calls to hooks within it are just like calls to hooks in the parent component (Example
). But function components aren't allowed to call hooks sometimes and not other times. That's because React relies on the order in which hooks are called, and that order has to be consistent for it to do its hook management. Again, using renderSomething
like that is using renderSomething
as a hook (this is exactly how custom hooks work, by ending up calling built-in hooks, which then store the information in the parent instance).
So as you can see, while the result you got in your example was the same, in general they're quite different things. :-)
Upvotes: 39