Advanced React patterns - vonschappler/Ultimate-React GitHub Wiki

A quick overview about reusability

In React we can reuse two types of code:

  1. UI code:
  • Using components and props as the component api
  • Using the children prop, which allow us to pass components into components
  1. Stateful logic code (code that make use use hooks):
  • Writing custom hooks

But there are some cases where it might be necessary to join both UI and Stateful logic at the same time, and this is where some useful patterns can be used, to help us with this task.

The first pattenr is called Render props patter because they are not something buil-in react, but some sort of solution to solve certain problems that might appear during React application development.

With these patterns, the user of the component has total control of what's being rendered as the component, by passing up a function which tell the component what and how to we wish to render.

The Compund component pattern is another solution which can be used in certain situations, in which we desire to write "super components" powered by small existent components.

This pattern also allow us to create self-contained components that need to manage their own state individually, without this state being really necessary in the parent component which use this super component.


The Render props pattern:

The Reder props pattern consists basically in pass a prop called render, which is a function that a components uses to know what to render and how to do it. So basically, when the children props os not enough to render properly a component due to its limitation, we make use of the Render props pattern.

A simple way to make use of this is simply adding the render prop inside a component, like shown below:

function H1({ render }) {
  return <h1>{render()}</h1>;
}

function ComponentWithRenderProp() {
  return (
    <div>
      <H1
        render={() => {
          return 'Some title';
        }}
      />
      <H1
        render={() => {
          return `Some title with a simple math operation: 2+2 = ${2 + 2}`;
        }}
      />
    </div>
  );
}

This is also called also inversion of control, which is an important principal in software development in general, because now instead of the component itself defining how and what to render, the user of the component is the one who makes those definitions by using the render prop on the component.

On the example above, the

no longer knows what it is rendering because it has no control on what will happen inside the render function.

All it know is that it recives the function and it will call the function as necessary, making it easy to reuse it as shown above.


The Higher order component pattern (HOC):

This partten is uncomonly used nowadays, but it's still important to learn a bit about it. It's important to mention here that almost no one writes HOC by hand, because most actual libraries expose those for us.

So we are presenting only a quick overview about them:

Let's begin by imagning that the component from the code below is provided by a 3rd-party library and so it cant be changed, but we still need to use it while also adding something like a button to the component to change it's visibilty status.

// file SectionsList.js

function function SectionsList({ sections }) {
  return (
    <h3>
      <ul>
        {sections.map((section) => (
          <li key={section.name}>{section.name}</li>
        ))}
      </ul>
    </h3>
  );
}

export default SectionsList

The solution to that is to use HOC, which is simply a custom component that recevies another component and return everything as a new functional component that overpowers version of the component provided by the 3rd-party library. The implementation of this technique is displayed on the snippets of code below:

// file withButton.js

import { useState } from 'react';

export default function withButton(WrappedComponent) {
  return function List(props) {
    const [isOpened, setIsOpened] = useState(true);

    function toggleOpen() {
      setIsOpened((isOpen) => !isOpen);
    }
    return (
      <div>
        {isOpened && <WrappedComponent {...props} />}
        <button onClick={toggleOpen}>
          {isOpened ? `Hide sections` : `Display sections`}
        </button>
      </div>
    );
  };
}
// App.js

import SectionsList from 'path/to/SectionsList';
import withButton from 'path/to/withButton';

const sections = [{ name: 'Section 1' }, { name: 'Section 2' }];

function App() {
  const H1WithButton = withButton(Sections);
  return <H1WithButton sections={sections} />;
}

It's common practice that the HOC always starts with the with keyword, to make it simpler to identify it's a custom component created to overpower a component provided else where, which code can't be changed.


The Compund component pattern:

The idea behind the Compound component pattern is to create a set of related components that together achieve a common and useful task, like for example, modal windows, tables, paginations and so many other components.

The way we implement this pattern is by creating a parent component, with all the children components what really only belongs to that parent and just make sense to be used WITH that parent component and with their siblins - like for example HTML select elements and their options selector boxes, because the select element implements the input which allows us to select any of the options listed as chidren components to it.

This is a way to create highly reusable components with higly expressive component API, basically using mo props at all.

One of the benefits of making this approach is that we can then easly customize complex elements without causing the so called "props explosion" in which we pass in too many proprs to set how the component should be rendered.

An example of how this can benefits is us shown in the snippet of code below:

// a simple counter component with props exposion, used anywhere in the application
<Counter
  iconIncrease='+'
  iconDecrease='-'
  label='NOT flexible counter'
  hideLabel={false}
  hideIncrease={false}
  hideDecrease={false}
/>

// the same counter component, used anywhere in the application, but defined with the compound component technique
<Counter>
  <Counter.Decreate icon='-' />
  <Counter.Count />
  <Counter.Increase icon='+' />
  <Counter.Label>Flexible Counter</Counter.Label>
</Counter>

Note that the second <Counter/> component can be easly customized by simply not declaring for example <Counter.Label> if we want to hide the label, instead of adding the prop hideLabel={false} to the original counter - which also then in this simple example also removes another prop from the original counter (label).

But how do we write compound components? Well, the code below examplifies the creation of the compound counter which can be used as the Flexible Counter, used above.

Note that to create a compound component patter a few steps are required:

  1. Create a context to be used on the compound component
  2. Create the parent component
  3. Create the child compoments that will implment the common task of the compoment
  4. Add the child compoments as properties of the parent component (this is optional, but by doing so, that enables the possibilty of making it necessary to export ONLY the parent compoment created).

Following the steps above it's possible to reproduce something similar to the code below, which will provide us a TOTALLY cusmoizable counter, wihtout props:

import { createContext, useContext, useState } from 'react';

// step 1
const ConterContext = createContext();

// step 2
function Counter({ children }) {
  const [count, setCount] = useState(0);

  const increase = () => setCount((c) => c + 1);
  const decrease = () => setCount((c) => c - 1);

  return (
    <ConterContext.Provider value={{ count, increase, decrease }}>
      <span>{children}</span>
    </ConterContext.Provider>
  );
}
export default Counter;

// step 3
function Count() {
  const { count } = useContext(ConterContext);
  return <span>{count}</span>;
}
function Label({ children }) {
  return <span>{children}</span>;
}

function Increase({ icon }) {
  const { increase } = useContext(ConterContext);
  return <button onClick={increase}>{icon}</button>;
}
function Decrease({ icon }) {
  const { decrease } = useContext(ConterContext);
  return <button onClick={decrease}>{icon}</button>;
}

// setp 4
Counter.Count = Count;
Counter.Label = Label;
Counter.Increase = Increase;
Counter.Decrease = Decrease;

The code above may seem too much for simply creating a counter, but this is just to show how compound components are created. In real life world applications this can and surely will be used for more complext situations like creating resuable modal windows, resuable tables, etc.

React Portal:

React Portal is a feature that allows us to render elements outside of the parent component's DOM structure, while still keeping the element in the original position of the component tree, making things like props to work normaly because the component tree wont change - making the use of React Portal an excelent feature to be used on components we wish to keep on top of other components, like for example, modals, tooltips, menus and many other components we can think about.

The way to implement this is displayed on the code below:

// other react imports
import { createPortal } from 'react-dom';

function PortalCompoment({ children, onClose }) {
  return createPortal(
    <div>Something to be rendered outside the DOM tree</div>,
    document.body
  );
}

export default PortalComponent;

NOTE:

Using document.body as the second argument of createPortal will make it so that the component PortalComponent stays on the same position on the component tree, but moved anywhere inside the (rendere) DOM tree while still being able to receive props coming from parent react elements if necessary.

The createPortal function needs two arguments:

  1. The components to be rendered inside the portal
  2. The parent HTML element which will be the parent of the portal

We can say as an analory that this is a portal that creates a tunel from a component to another, no matter on where the component is inside the rendered DOM tree. One of the advantages to make use of this is simple because this helps to avoid conflict some CSS property definitions like for example overflow: hidden when we build a modal.

Of course there are many other cases when the use of portals are useful, but this is just one example among many situations in where css definitions can conflict and where using a "portal" can help us to prevent this conflict.

⚠️ **GitHub.com Fallback** ⚠️