Ravi Ojha
Ravi Ojha

Reputation: 168

React: Ways of passing component as a props

What is difference between these two way of passing component as props.

  1. Passing RepoMenu as it is:
<Fetch
  url={`https://api.github.com/users/${login}/repos`}
  renderSuccess={RepoMenu}
/>
  1. Wrapping the 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}>&lt;</button>
      <p>{name}</p>
      <button onClick={next}>&gt;</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

Upvotes: 0

Views: 70

Answers (2)

Luis Paulo Pinto
Luis Paulo Pinto

Reputation: 6046

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;

Upvotes: 1

Nick Vu
Nick Vu

Reputation: 15540

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;

Sandbox

Upvotes: 2

Related Questions