6
votes

I'm somewhat confused on the relationship between functional components in React and the Render Props and HOC patterns.

That is,

is it true that the only way to create Render Prop is with a class component? is it true that the only way to create an HOC is with a class component?

And the same for usage.

I'm trying to find examples of Render Props and HOC's with functional components and all I find are class components. I get that React Hooks do a lot of the same, but I'm trying to understand how the Render Props and HOC patterns can apply to functional components (or if they do at all)


Edit Below:

Applying what @chaimFriedman suggested, this is what I came up with using no class or component for an HOC hoping it makes sense.

import React, { useState, useEffect } from 'react';
import useAxios from 'axios-hooks';

function withFetching(url) {
  return function(Speakers) {
    return () => {
      const [speakerData, setSpeakerData] = useState([]);
      const [isLoading, setIsLoading] = useState(true);
      const [{ data, loading }, refetch] = useAxios('http://localhost:4000/speakers');
      useEffect(() => {
        setSpeakerData(data);
        setIsLoading(loading);
      }, [loading]);

      if (isLoading) return <div>loading..</div>;

      return <Speakers data={speakerData}></Speakers>;
    };
  };
}

const Speakers = function(props) {
  //debugger;
  return (
    <ul>
      {props.data.map((speaker) => (
        <li key={speaker.id}>
          <span>
            {speaker.firstName} {speaker.lastName}
          </span>
        </li>
      ))}
    </ul>
  );
};

const API = 'http://localhost:4000/speakers';
export default withFetching(API)(Speakers);
2
Show an example of class composition and see how it can be done with functions, without it you just asking a too broad question. The simple answer is that you can do everything with a functional component (except error boundary and assigning a reference on the functional component instance - which is almost useless usecase)Dennis Vash

2 Answers

12
votes

Both render props and HOC can absolutely apply to functional components. Let's think a little more about what each one really is to see why they do in fact work with functions as well as classes.

Render props is when you have a prop that is a function which returns JSX. This of course should work for function components because aside from life cycle methods there really isnt much that is different than class components. Here is an example with code.

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function Renderer(props) {
  return (
    props.children()
  );
}

function App() {
  return (
    <div className="App">
      <Renderer>
        {() => {
          return (
            <h1>I am being rendered by Renderer</h1>
          );
        }}
      </Renderer>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Now for HOC.

A HOC really is just a higher order function, but because we use it in react we call it a higher order component. A higher order function is a function which either accepts a function as an argument, or returns a function. Now a functional component can absolutely do this. Here is an example.

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function Renderer(Wrapped) {
  return function New(props) {
    return <Wrapped {...props} />
  }
}

function Child(props) {
  return (
    <h1>Hello {props.name}</h1>
  );
}

function App() {
  const C = Renderer(Child)
  return (
    <div className="App">
     <C name="john" />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

EDIT: I realized my HOC example was wrong so updated.

I hope this helps.

2
votes

Here is the sample code converting example from React documentation into function component. https://reactjs.org/docs/render-props.html

import React from "react";

const Cat = ({mouse}) => {
  return (
    <img
      src="/cat.png"
      alt="cat"
      style={{ position: "absolute", left: mouse.x, top: mouse.y }}
    />
  );
};

const Mouse = (props) => {
  const [state, setState] = React.useState();

  const handleMouseMove = (event) => {
    setState({
      x: event.clientX,
      y: event.clientY
    });
  };
  
  return (
    <div style={{ height: "100vh" }} onMouseMove={handleMouseMove}>
      {props.render(state)}
    </div>
  );
};

const MouseTracker = () => {
  return (
    <div>
      <h1>Move the mouse around!</h1>
      <Mouse render={(mouse) => <Cat mouse={mouse} />} />
    </div>
  );
};

export const App = () => {
  return (
    <div className="App">
      <MouseTracker />
    </div>
  );
}