This is the repo for swyx's blog - Blog content is created in github issues, then posted on swyx.io as blog pages! Comment/watch to follow along my blog within GitHub
source: devto
devToUrl: "https://dev.to/swyx/react-single-file-components-are-coming-3g74"
devToReactions: 55
devToReadingTime: 8
devToPublishedAt: "2020-03-11T03:02:23.857Z"
devToViewsCount: 3680
title: React Single File Components Are Here
published: true
description: React has long eschewed convention in favor of the extreme flexibility of JS. It is time for the next level in React authorship formats.
category: essay
tags: Tech, React, JavaScript
slug: react-sfcs-here
displayed_publish_date: "2020-03-09"
Aug 2020 Edit: This idea was presented as part of a bigger talk at React Rally - Growing a Meta-Language
Dec 2020 edit: the release of React Server Components landed on two files for client and server. The initial exploration of React Blocks was a single file but they were split for typing, scoping and bundler integration concerns. However this still doesn't preclude having a "single file component format" for client components that solve other concerns, like styling.
May 2021 edit: the Remix framework from the React Router duo also adopt a single file component format that involves exporting links, loader, and action.
The launch of RedwoodJS today marks a first: it is the first time React components are being expressed in a single file format with explicit conventions.
I first talked about this in relation to my post on React Distros, but I think React SFCs will reach standardization in the near future and it is worth discussing now as a standalone post.
Context
It is a common joke that you can't get through a React conference without reference to React as making your view layer a pure function of data, v = f(d). I've talked before about the problems with this, but basically React has always done a collective ¯\_(ツ)_/¯ when it comes to having a satisfying solution for actually fetching that all-important data. React Suspense for Data Fetching may be a nice solution for this in the near future.
Here's a typical React "single file component" with Apollo Client, though it is the same with React Query or any data fetching paradigm I can imagine:
// Data Example 1
export const QUERY = gql`
query {
posts {
id
title
body
createdAt
}
}
`;
export default function MyComponent() {
const { loading, error, data: posts } = useQuery(QUERY);
if (error) return <div>Error loading posts: {error.message}</div>
if (loading) return <div>Loading...</div>;
if (!posts.length) return <div>No posts yet!</div>;
return (<>
posts.map(post => (
<article>
<h2>{post.title}</h2>
<div>{post.body}</div>
</article>
));
</>)
}
For styling, we might use anything from Tailwind to Styled-JSX to Styled-Components/Emotion to Linaria/Astroturf to CSS Modules/PostCSS/SASS/etc and it is a confusing exhausting random eclectic mix of stuff that makes many experts happy and many beginners lost. But we'll talk styling later.
This does the same thing as the "SFC" example above, except instead of writing a bunch of if statements, we are baking in some conventions to make things more declarative. Notice in both examples are already breaking out the GraphQL queries, so that they can be statically consumed by the Relay Compiler, for example, for persisted queries.
This is a format that is more native to React's paradigms than the Single File Component formats of Vue, Svelte, and my own React SFC proposal a year ago, and I like it a lot.
Notice that, unlike the HTML-inspired formats of the above options, we don't actually lose the ability to declare smaller utility components within the same file, since we can just use them inline without exporting them. This has been a major objection of React devs for SFCs in the past.
Why Formats over Functions are better
As you can see, the authoring experience between the Example 1 and Example 2 is rather nuanced, and to articulate this better, I have started calling this idea Formats over Functions.
The idea is that you don't actually need to evaluate the entire component's code and mock the data in order to access one little thing. In this way you bet on JS more than you bet on React itself.
This makes your components more consumable and statically analyzable by different toolchains, for example, by Storybook.
Component Story Format
Team Storybook was actually first to this idea and a major inspiration for me, with it's Component Story Format. Here's how stories used to be written:
You needed to have Storybook installed to make use of this, and if you ever needed to migrate off Storybook to a competitor, or to reuse these components for tests (I have been in this exact scenario), you were kind of screwed.
Component Story Format (CSF) lets you write your components like this:
and all of a sudden you can consume these files in a lot more different ways by different toolchains (including by design tools!) and none of them have to use Storybook's code, because all they need to know is the spec of the format and how to parse JavaScript. (yes, JSX compiles to React.createElement, but that is easily mockable).
Merging CSF and SFCs
You're probably already seeing the similarities - why are we authoring stories and components separately? Let's just stick them together?
Adding TypeScript would take no change in tooling at all.
And therein lies the beauty of the format, and the impending necessity of standardizing exports for fear of stepping on each others' toes as we push forward React developer experience.
The Full Potential of Single File Components
I think Styling is the last major frontier we need to integrate. Utility CSS approaches aside, here's how we can include static scoped styles for our components:
// Styled SFC - Static Example
export const STYLE = `
/* only applies to this component */
h2 {
color: red
}
`
export const Success = ({ posts }) => {
return posts.map(post => (
<article>
<h2>{post.title}</h2>
<div>{post.body}</div>
</article>
));
};
// etc...
and, if we needed dynamic styles, the upgrade path would be fairly simple:
This of course changes the degree of reliance on the React runtime that we assume in SFCs, so I am less confident about this idea, but I do still think it would be useful. I have other, more extreme ideas on this front.
While Redwood uses exports to declare loading and error states, Suspense uses <Suspense> and error boundaries. It's possible to compile from the former to the latter but not the other way, which is a key point of the "formats over functions" idea - things are more consumable that way. However it is a valid question whether you should be able to access those internal states - after all, if you're just reading them for testing purposes, should you be testing how React works? Counterpoint: what if you wanted to see your loading and error states separately in a Storybook or design tool?
It also brings to mind the work that Next.js has done with getStaticProps, getStaticPaths and getServerSideProps - as the first hybrid framework, it is nice to use static exports to let the framework pick from data requirements, as well as to not tie yourself so tightly to GraphQL. getStaticPaths in particular is very elegant - moving page creation inside components themselves.
Conclusion - Ending with Why
It's reasonable to question why we want everything-in-one file rather than everything-in-a-folder. But in a sense, SFCs simply centralize what we already do with loaders.
I find that the file length is mitigated by having keyboard shortcuts for folding/expanding code in IDE's. In VSCode, you can fold/unfold code with keyboard bindings:
Fold folds the innermost uncollapsed region at the cursor:
Ctrl + Shift + [ on Windows and Linux
⌥ + ⌘ + [ on macOS
Unfold unfolds the collapsed region at the cursor:
Ctrl + Shift + ] on Windows and Linux
⌥ + ⌘ + ] on macOS
Fold All folds all regions in the editor:
Ctrl + (K => 0) (zero) on Windows and Linux
⌘ + (K => 0) (zero) on macOS
Unfold All unfolds all regions in the editor:
Ctrl + (K => J) on Windows and Linux
⌘ + (K => J) on macOS
Ultimately, Colocating concerns rather than artificially separating them helps us delete and move them around easier, and that optimizes for change.
I have more thoughts on how we can apply twists of this idea here on my old proposal.
This movement has been a long time coming, and I can see the momentum accelerating now.
source: devto devToUrl: "https://dev.to/swyx/react-single-file-components-are-coming-3g74" devToReactions: 55 devToReadingTime: 8 devToPublishedAt: "2020-03-11T03:02:23.857Z" devToViewsCount: 3680 title: React Single File Components Are Here published: true description: React has long eschewed convention in favor of the extreme flexibility of JS. It is time for the next level in React authorship formats. category: essay tags: Tech, React, JavaScript slug: react-sfcs-here displayed_publish_date: "2020-03-09"
The launch of RedwoodJS today marks a first: it is the first time React components are being expressed in a single file format with explicit conventions.
I first talked about this in relation to my post on React Distros, but I think React SFCs will reach standardization in the near future and it is worth discussing now as a standalone post.
Context
It is a common joke that you can't get through a React conference without reference to React as making your view layer a pure function of data,
v = f(d)
. I've talked before about the problems with this, but basically React has always done a collective¯\_(ツ)_/¯
when it comes to having a satisfying solution for actually fetching that all-important data. React Suspense for Data Fetching may be a nice solution for this in the near future.Here's a typical React "single file component" with Apollo Client, though it is the same with React Query or any data fetching paradigm I can imagine:
For styling, we might use anything from Tailwind to Styled-JSX to Styled-Components/Emotion to Linaria/Astroturf to CSS Modules/PostCSS/SASS/etc and it is a confusing exhausting random eclectic mix of stuff that makes many experts happy and many beginners lost. But we'll talk styling later.
Redwood Cells
Here's what Redwood Cells look like:
This does the same thing as the "SFC" example above, except instead of writing a bunch of
if
statements, we are baking in some conventions to make things more declarative. Notice in both examples are already breaking out the GraphQL queries, so that they can be statically consumed by the Relay Compiler, for example, for persisted queries.This is a format that is more native to React's paradigms than the Single File Component formats of Vue, Svelte, and my own React SFC proposal a year ago, and I like it a lot.
Notice that, unlike the HTML-inspired formats of the above options, we don't actually lose the ability to declare smaller utility components within the same file, since we can just use them inline without exporting them. This has been a major objection of React devs for SFCs in the past.
Why Formats over Functions are better
As you can see, the authoring experience between the Example 1 and Example 2 is rather nuanced, and to articulate this better, I have started calling this idea Formats over Functions.
The idea is that you don't actually need to evaluate the entire component's code and mock the data in order to access one little thing. In this way you bet on JS more than you bet on React itself.
This makes your components more consumable and statically analyzable by different toolchains, for example, by Storybook.
Component Story Format
Team Storybook was actually first to this idea and a major inspiration for me, with it's Component Story Format. Here's how stories used to be written:
You needed to have Storybook installed to make use of this, and if you ever needed to migrate off Storybook to a competitor, or to reuse these components for tests (I have been in this exact scenario), you were kind of screwed.
Component Story Format (CSF) lets you write your components like this:
and all of a sudden you can consume these files in a lot more different ways by different toolchains (including by design tools!) and none of them have to use Storybook's code, because all they need to know is the spec of the format and how to parse JavaScript. (yes, JSX compiles to React.createElement, but that is easily mockable).
Merging CSF and SFCs
You're probably already seeing the similarities - why are we authoring stories and components separately? Let's just stick them together?
You already can:
Adding TypeScript would take no change in tooling at all.
And therein lies the beauty of the format, and the impending necessity of standardizing exports for fear of stepping on each others' toes as we push forward React developer experience.
The Full Potential of Single File Components
I think Styling is the last major frontier we need to integrate. Utility CSS approaches aside, here's how we can include static scoped styles for our components:
and, if we needed dynamic styles, the upgrade path would be fairly simple:
And that would upgrade to a CSS-in-JS equivalent implementation.
Interacting with Hooks
What if styles or other future Single File Component segments need to interact with component state? We could lift hooks up to the module level:
This of course changes the degree of reliance on the React runtime that we assume in SFCs, so I am less confident about this idea, but I do still think it would be useful. I have other, more extreme ideas on this front.
Other Opportunities
Dan Abramov replied with something I missed - the server/client split. There is ongoing work with React Flight (to do with streaming SSR) and Blocks (to do with blocking rendering without being tied to Relay/GraphQL) that I'm basically completely ignorant about.
While Redwood uses exports to declare loading and error states, Suspense uses
<Suspense>
and error boundaries. It's possible to compile from the former to the latter but not the other way, which is a key point of the "formats over functions" idea - things are more consumable that way. However it is a valid question whether you should be able to access those internal states - after all, if you're just reading them for testing purposes, should you be testing how React works? Counterpoint: what if you wanted to see your loading and error states separately in a Storybook or design tool?It also brings to mind the work that Next.js has done with
getStaticProps
,getStaticPaths
andgetServerSideProps
- as the first hybrid framework, it is nice to use static exports to let the framework pick from data requirements, as well as to not tie yourself so tightly to GraphQL.getStaticPaths
in particular is very elegant - moving page creation inside components themselves.Conclusion - Ending with Why
It's reasonable to question why we want everything-in-one file rather than everything-in-a-folder. But in a sense, SFCs simply centralize what we already do with loaders.
Think about it: we often operate like this:
And some people may think that is better than this:
But we're exchanging that for:
I find that the file length is mitigated by having keyboard shortcuts for folding/expanding code in IDE's. In VSCode, you can fold/unfold code with keyboard bindings:
Fold folds the innermost uncollapsed region at the cursor:
Ctrl + Shift + [
on Windows and Linux⌥ + ⌘ + [
on macOSUnfold unfolds the collapsed region at the cursor:
Ctrl + Shift + ]
on Windows and Linux⌥ + ⌘ + ]
on macOSFold All folds all regions in the editor:
Ctrl + (K => 0)
(zero) on Windows and Linux⌘ + (K => 0)
(zero) on macOSUnfold All unfolds all regions in the editor:
Ctrl + (K => J)
on Windows and Linux⌘ + (K => J)
on macOSUltimately, Colocating concerns rather than artificially separating them helps us delete and move them around easier, and that optimizes for change.
I have more thoughts on how we can apply twists of this idea here on my old proposal.
This movement has been a long time coming, and I can see the momentum accelerating now.