4 React Design Patterns You Should Know

4 React Design Patterns You Should Know

That helps you write better React code
Ferenc Almasi β€’ πŸ”„ 2023 March 31 β€’ πŸ“– 7 min read

This lesson is a preview from our interactive course

In this lesson, we are going to introduce you to four common React patterns that can help you write cleaner and better code. All of the patterns below are built on top of commonly occurring problems, similar to what design patterns are used for.

Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master React

Child-to-Parent Communication

Child-to-parent communication, also known as bottom-up communication, often becomes a problem when building React applications. Naturally, data flows from top to bottom in React. While technically there is no way to switch the direction, we can work around this issue with callback functions. Take the following as an example:

import React from 'react'

const Child = ({ onClick }) => {
    const data = {
        text: 'Click on me!'
    };

    const handleClick = () => onClick(data);

    return <button onClick={handleClick}>{data.text}</button>; 
}

const App = () => {
    
   // Inside the function we have access to the data
   const setState = data => {
       console.log(data);
   };
 
    return <Child onClick={setState} />;
}

export default App;
Child to parent communication with event handlers Execute code
Copied to clipboard!

In the above example, we have a parent (App) and Child component. We want to retrieve the data stored inside the Child component and use it inside App. To achieve this, we can pass an onClick event listener to the ChildΒ component with a function (setState) that expects the data as its parameter.

The key here is to invoke the passed function with the data inside the child, making it available inside the parent as well. This is what happens on the highlighted line.

Lifting state

Of course, there are also cases where we don't want to wait for a user event to happen. We just want to pass the data as soon as the component is rendered. In this case, we cannot rely on an onClick listener. Instead, we need to lift the state up:

import React from 'react'

const Child = ({ text }) => {
    return <button onClick={() => console.log(text)}>{text}</button>; 
}

const App = () => {
   const data = {
        text: 'Click on me!'
    };
 
    return <Child text={data.text} />;
}

export default App;
Lifting state Execute code
Copied to clipboard!

In this example, we change the way AppΒ and Child are rendered. Functionally, everything stayed the same, but now we have the state of the Child one level up, inside the App. This means we can access the necessary data in the correct component, and pass any required data down to our Child component via props.


Higher-order components

Higher-order components (HOC for short), are a pattern used for reusing component logic. Just like higher-order functions in JavaScript, React uses the same compositional nature but with components.

Let's look at a simple common example of a higher-order component: a conditional wrapper.

import React from 'react'

const ConditionalWrapper = ({ condition, wrapper, children }) => {
    return condition ? wrapper(children) : children;
}

const App = () => {
    const renderWithLink = false;

    return (
        <ConditionalWrapper
            condition={renderWithLink}
            wrapper={children => (
                <a href="#">{children}</a>
            )}
        >
            <img
                src="https://www.webtips.dev/assets/img/logo.png"
                style={{ background: 'white' }}
            />
        </ConditionalWrapper>
    );
}

export default App;
Creating conditional wrappers Execute code
Copied to clipboard!

Higher-order components are pure functions. Rather than mutating existing components, they return new components.

The ConditionalWrapper component expects three different props:

In this example, the img inside the ConditionalWrapper is wrapped with a link conditionally. As the current condition is false, the image is rendered as is. Change the condition to true to see how it will be wrapped with an anchor.

Whenever multiple components need to be wrapped and decorated with additional functionality, we usually have a great candidate for a HOC. Some other commonly used examples of HOC include components likeΒ Authenticate, Loading, orΒ WrapWithStyles.

Reinforce your knowledge!

Which of the following is the correct way to turn renderWithLink into a hook?

1.) const [renderWithLink, updateRenderWithLink] = useEffect(false);
2.) const [renderWithLink, updateRenderWithLink] = useState(false);
3.) const [renderWithLink, updateRenderWithLink] = useState(false, false);
Copied to clipboard!

Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master React

Render props

Render props are another common technique used for uncoupling the rendering logic from a component. This is what we did with the above HOC. The wrapper prop acts as a render prop that renders the elements passed to it when the passed condition is true. When working with render props, this prop is called render by convention. Let's see an example:

import React from 'react'

const User = ({ render }) => {
    const userData = {
        name: 'John'
    };

    return render(userData);
}

const App = () => {
    return (
        <React.Fragment>
            <User render={user => <h1>Welcome {user.name}!</h1>} />
            <User render={user => <h2>Welcome {user.name}!</h2>} />
        </React.Fragment>
    );
}

export default App;
Using render props Execute code
Copied to clipboard!

Here, we can follow the same steps that we did for the ConditionalWrapper component. We need to call the render prop as a function to render the JSX passed to it. By passing the userData as a parameter to the render prop, we can access it during creation.

Essentially, we just decoupled the rendering logic from the component, making it possible for each User component to define its own render logic. We can reuse the same component over and over while rendering different things.


Provider pattern

Last but not least, we already had an introduction to the useContext hook in the Hooks section. This hook is often used in a provider pattern to provide a global state to an application. In this pattern, all providers are created at the top of the React tree to make a context globally accessible.

// Using a separate component for collecting providers in one place
const ApplicationProvider = ({ children }) => (
    <UserProvider value={user}>
        <MetaProvider value={meta}>
            {children}
        </MetaProvider>
    </UserProvider>
)

ReactDOM.createRoot(document.getElementById('root')).render(
    <React.StrictMode>
        <ApplicationProvider>
            <App />
        </ApplicationProvider>
    </React.StrictMode>
);
Using providers for the provider pattern
Copied to clipboard!

Often, many providers are required for an application to hold many types of data. It is a common practice to collect these providers into a single component to keep the number of JSX elements to a minimum for the root of the application.

Reinforce your knowledge!

When is the default value used within a createContext call?

Did you find this page helpful?
πŸ“š More Webtips
Frontend Course Dashboard
Master the Art of Frontend
  • check Unlimited access to hundred of tutorials
  • check Access to exclusive interactive lessons
  • check Remove ads to learn without distractions
Become a Pro

Recommended