ross@normalcool
2026-07-11

React and Data Composition

The way I personally see React, is it’s an approach to binding data. Data as a word is pretty ambiguous so let me clarify that this little post is about structured data responses from async resources.

I like small functions - a lot. Even if there’s more of them, I find it easier to follow control flow.

I really like capturing and presenting individual properties of data. Here’s an example, working backwards.

/**
 * VideoCard composables
 **/
import * as React from 'react';

type Video = {
  id: string;
  assetPath: string;
  title: string;
  description: string | null;
  duration: number;
  thumbnail: string;
  createdAt: Date;
}

const VideoCardCtx = React.createContext<Video | null>(null);
export const useVideo = () => React.useContext(VideoCardCtx);

export function Provider ({ children, value }: {
  children: React.ReactNode;
  value: Video;
})  {
  return (
    <VideoCtx.Provider value={value}>
      {children}
    </VideoCtx.Provider>
  );
}

export function Title({ className }: { className?: string }) {
  const video = useVideo();
  if (!video) {
    return null // maybe you throw an error here or even return a fallback component
  }
  return (
    <h3 className={className}>{video.title}</h3>
  );
}

export function Description({ className }: { className?: string }) {
  const video = useVideo();
  if (!video) {
    return null // maybe you throw an error here or even return a fallback component
  }
  return (
    <p className={className}>{video.description}</p>
  );
}

export function Duration({ className }: { className?: string }) {
  const video = useVideo();
  if (!video) {
    return null // maybe you throw an error here or even return a fallback component
  }
  return (
    <p className={className}>{video.duration}</p>
  );
}

export function Thumbnail({ className }: { className?: string }) {
  const video = useVideo();
  if (!video) {
    return null // maybe you throw an error here or even return a fallback component
  }
  return (
    <img className={className} src={video.thumbnail} />
  );
}

export function CreatedAt({ className }: { className?: string }) {
  const video = useVideo();
  if (!video) {
    return null // maybe you throw an error here or even return a fallback component
  }
  return (
    <time className={className}>{video.createdAt}</time>
  );
}

Usage may look like this:

import * as VideoCard from './VideoCard.tsx';

export function VideosList() {
  const { data } = useQuery(getVideos);
  return (
    <div className="flex flex-col gap-4">
      {data?.map(x => (
        <VideoCard.Provider value={x}>
          <VideoCard.Title />
          <VideoCard.Description />
          <VideoCard.Duration />
          <VideoCard.Thumbnail />
          <VideoCard.CreatedAt />
        </VideoCard.Provider>
      ))}
    </div>
  );
}

And you may ask, but why? Always a reasonable question. To me it’s because interfaces often display data in more than one way. To me this approach encapsulates, at the right level, in order to represent data at the structural level. It allows for a separation between, markup, styles, and data.

To elaborate or further explain my take. I see components like VideoListHomePage, VideoListSearchResults, etc… Largely for these the data is the same, but different enough, or maybe the styling calls for a different markup structure. If you take the single property approach, you’ve separated structure and styles more effectively and open yourself to composition opportunities - especially if you’re a tailwind user.