What is difference between these two way of passing component as props.
RepoMenu as it is:<Fetch
url={`https://api.github.com/users/${login}/repos`}
renderSuccess={RepoMenu}
/>
RepoMenu in a function<Fetch
url={`https://api.github.com/users/${login}/repos`}
renderSuccess={({ data }) => <RepoMenu data={data} />}
/>
RepoMenu internally uses a custom hook.
function RepoMenu({ data, onSelect = (f) => f }) {
const [{ name }, prev, next] = useIterator(data);
return (
<div style={{ display: "flex" }}>
<button onClick={prev}><</button>
<p>{name}</p>
<button onClick={next}>></button>
</div>
);
}
Using one (Passing RepoMenu as it is) starts failing, React starts throwing an exception Rendered more hooks than during the previous render.
Initially i thought may it is not possible to use the component like the method 1. But to test it more further i created a small Pure Component and just tried to render the data passed to it.
function RepoMenuPure({ data = [], onSelect = (f) => f }) {
return (
<div style={{ display: "flex" }}>
<h3>There are {data.length} repos for this user</h3>
</div>
);
}
And when i tried to use it like below, It worked in this case.
const UserRepositories = ({ login, selectedRepo, onSelect = (f) => f }) => {
return (
<Fetch
url={`https://api.github.com/users/${login}/repos`}
renderSuccess={RepoMenuPure}
/>
);
};
Can someone please help me explain what is happening and what i am missing?
I have created a small sandbox here: https://codesandbox.io/s/stackoverflow-example-bqfn3e?file=/src/App.js
return renderSuccess({ data });
Your problem is you're calling a component like a function. Every time you call renderSuccess (which is RepoMenu - now it's not considered a component), it will trigger your custom hook useIterator which is invalid.
You can refer to this document
Don’t call Hooks inside loops, conditions, or nested functions.
Nested functions part is your current situation.
With the pure component, it was working because you didn't use any hooks, that's why you don't see any errors come out.
For the fix, you can do it this way
import React from "react";
import { useFetch } from "./hooks/useFetch";
function Fetch({
url,
loadingFallback = <div>Loading....</div>,
errorFallback = <div>Some thing went wrong</div>,
renderSuccess: RenderSuccess //introduce it as a component
}) {
const { data, error, loading } = useFetch(url);
if (loading) {
return loadingFallback;
}
if (error) {
return errorFallback;
}
if (data) {
return <RenderSuccess data={data} />; //render a component instead of executing a function
}
return null;
}
export default Fetch;
In the first option :
renderSuccess={({ data }) => <RepoMenu data={data} />}
You are saying that Fetch component expects that renderSuccess receives a function that returns a Component. If you were using typescript, the type of renderSuccess in Fetch would be something like:
renderSuccess : (data: any) => JSX.Element
// OR return the component itself
renderSuccess : (data: any) => RepoMenu
And that´s why when you call renderSuccess({ data }) inside Fetch component works.
In the second option:
renderSuccess={RepoMenu}
You are saying that Fetch component expects that renderSuccess receives the RepoMenu Component (not a function). If you were using typescript, the type of renderSuccess in Fetch would be something like:
renderSuccess : JSX.Element
// OR the component itself
renderSuccess : RepoMenu
For this option work, you should change your Fetch component to something like this:
import React from "react";
import { useFetch } from "./hooks/useFetch";
function Fetch({
url,
loadingFallback = <div>Loading....</div>,
errorFallback = <div>Some thing went wrong</div>,
renderSuccess: Component
//renderSuccess
}) {
// console.log("renderSuccess", renderSuccess);
const { data, error, loading } = useFetch(url);
if (loading) {
return loadingFallback;
}
if (error) {
return errorFallback;
}
if (data) {
// return renderSuccess({ data });
return <Component data={data} />;
}
return null;
}
export default Fetch;