4 React Design Patterns You Should Know

4 React Design Patterns You Should Know

That helps you write better React code
Ferenc Almasi β€’ Last updated 2023 March 31 β€’ Read time 7 min read
Learn how you can use four different common React design patterns that can help you write better React code.
  • twitter
  • facebook
React

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.


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:

Copied to clipboard! Playground
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

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:

Copied to clipboard! Playground
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

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.

Copied to clipboard! Playground
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://webtips.dev/assets/img/logo.png"
                style={{ background: 'white' }}
            />
        </ConditionalWrapper>
    );
}

export default App;
Creating conditional wrappers Execute code

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?

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

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

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:

Copied to clipboard! Playground
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

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.

Copied to clipboard! Playground
// 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

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?

  • twitter
  • facebook
React
Did you find this page helpful?
πŸ“š More Webtips
Frontend Course Dashboard
Master the Art of Frontend
  • check Access 100+ interactive lessons
  • check Unlimited access to hundreds of tutorials
  • check Prepare for technical interviews
Become a Pro

Courses

Recommended

This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.