DEV Community

Suhan Wijaya
Suhan Wijaya

Posted on • Edited on • Originally published at Medium

6 1

Decouple Data from UI in React Part 2

A further exploration of the Hooks, Render Props, and HOC patterns

c. 1512, Oil on canvas, Source: programming-memes.com


In Part 1, I presented an approach to decouple the data fetching/management layer from the UI, which would free us from being locked into any particular data library or framework. Let’s call this Approach A.

Approach A. Custom Hook

Let’s create a custom hook — useSomeData — that returns the properties someData, loading, and error regardless of the data fetching/management logic. The following are 3 different implementations of useSomeData.

With Fetch API and component state:

import React, { useState, useEffect } from 'react';
const useSomeData = () => {
const [someData, setSomeData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!someData) {
setLoading(true);
fetch('/some-data')
.then(response => response.json())
.then(data => setSomeData(data))
.catch(error => setError(error))
.finally(() => setLoading(false));
}
}, []);
return { someData, loading, error };
};

With Redux:

import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { selectSomeData } from 'path/to/data/selectors';
import { fetchSomeData } from 'path/to/data/action';
const useSomeData = () => {
const dispatch = useDispatch();
const someData = useSelector(selectSomeData);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!someData) {
setLoading(true);
dispatch(fetchSomeData())
.catch(error => setError(error))
.finally(() => setLoading(false));
}
}, []);
return { someData, loading, error };
};

With Apollo GraphQL:

import { gql, useQuery } from '@apollo/client';
const useSomeData = () => {
const { data: someData, loading, error } = useQuery(gql`
fetchSomeData {
data {
# some fields
}
}
`);
return { someData, loading, error };
};

The 3 implementations above are interchangeable without having to modify this UI component:

import React from 'react';
import useSomeData from 'path/to/custom-hook';
const SomeComponent = (props) => {
const { someData, loading, error } = useSomeData();
return (
<React.Fragment>
{loading && <div>{'Loading...'}</div>}
{!loading && error && <div>{`Error: ${error}`}</div>}
{!loading && !error && someData && <div>{/* INSERT SOME AMAZING UI */}</div>}
</React.Fragment>
);
};

But, as Julius Koronci correctly pointed out, while the data fetching/management logic is decoupled, the SomeComponent UI is still coupled to the useSomeData hook.

In other words, even though we can reuse useSomeData without SomeComponent, we cannot reuse SomeComponent without useSomeData.

Perhaps this is where Render Props and Higher Order Components do a better job at enforcing the separation of concerns (thanks again to Julius for highlighting this).


Approach B. Render Props

Instead of a custom hook that returns someData, loading, and error, let’s create a Render Props component — SomeData — that wraps around a function (i.e., children needs to be a function), implements the data logic, and passes in someData, loading, and error into the function.

import React, { useState, useEffect } from 'react';
const SomeData = ({ children }) => {
// DATA FETCHING/MANAGEMENT FRAMEWORK OR LIBRARY OF YOUR CHOICE
return children({ someData, loading, error });
};
const SomeComponent = ({ someData, loading, error }) => (
<React.Fragment>
{loading && <div>{'Loading...'}</div>}
{!loading && error && <div>{`Error: ${error}`}</div>}
{!loading && !error && someData && <div>{/* INSERT SOME AMAZING UI */}</div>}
</React.Fragment>
);
const SomeComponentWithSomeData = () => (
<SomeData>
{renderProps => (<SomeComponent {...renderProps} />)}
</SomeData>
);

You can replace line 4 in the snippet above with Redux, Apollo GraphQL, or any data fetching/management layer of your choice.

We can now reuse SomeComponent (UI component) without SomeData (Render Props component). We can also reuse SomeData without SomeComponent.


Approach C. Higher Order Components (HOC)

Let’s create a HOC — withSomeData — that accepts a React component as an argument, implements the data logic, and passes someData, loading, and error as props into the wrapped React component.

import React, { useState, useEffect } from 'react';
const withSomeData = Component => {
const ComponentWithSomeData = (props) => {
// DATA FETCHING/MANAGEMENT FRAMEWORK OR LIBRARY OF YOUR CHOICE
return <Component {...props} someData={someData} loading={loading} error={error} />;
};
return ComponentWithSomeData;
};
const SomeComponent = ({ someData, loading, error }) => (
<React.Fragment>
{loading && <div>{'Loading...'}</div>}
{!loading && error && <div>{`Error: ${error}`}</div>}
{!loading && !error && someData && <div>{/* INSERT SOME AMAZING UI */}</div>}
</React.Fragment>
);
const SomeComponentWithSomeData = withSomeData(SomeComponent);
view raw medium-hoc.jsx hosted with ❤ by GitHub

You can replace line 5 in the snippet above with Redux, Apollo GraphQL, or any data fetching/management layer of your choice.

We can now reuse SomeComponent (UI component) without withSomeData (HOC). We can also reuse withSomeData without SomeComponent.

Today I learned.

Which approach do you prefer and why?


Resources


Read More


📫 Let’s connect on LinkedIn or Twitter!

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

SurveyJS custom survey software

JavaScript Form Builder UI Component

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay