Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
March 11, 2020 03:02 am GMT

React Single File Components Are Here

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 1export 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.

Redwood Cells

Here's what Redwood Cells look like:

// Data Example 2export const QUERY = gql`  query {    posts {      id      title      body      createdAt    }  }`;export const Loading = () => <div>Loading...</div>;export const Empty = () => <div>No posts yet!</div>;export const Failure = ({ error }) => <div>Error loading posts: {error.message}</div>;export const Success = ({ posts }) => {  return posts.map(post => (    <article>      <h2>{post.title}</h2>      <div>{post.body}</div>    </article>  ));};

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:

// Storybook Example 1import React from 'react';import { storiesOf } from '@storybook/react';import { Button } from '@storybook/react/demo';storiesOf('Button', module) .addWithJSX(   'with Text',   () => <Button>Hello Button</Button> ) .add('with Emoji', () => (  <Button>    <span role="img" aria-label="so cool">             </span>  </Button> ));

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.

CSF lets you write your components like this:

// Storybook Example 2import React from 'react';import { Button } from '@storybook/react/demo';export default { title: 'Button' };export const withText = () => <Button>Hello Button</Button>;export const withEmoji = () => (  <Button>    <span role="img" aria-label="so cool">             </span>  </Button>);

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 SCF 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:

// SCF + SFC Exampleexport default {   title: 'PostList',  excludeStories: ['QUERY']};export const QUERY = gql`  query {    posts {      id      title      body      createdAt    }  }`;export const Loading = () => <div>Loading...</div>;export const Empty = () => <div>No posts yet!</div>;export const Failure = ({ error }) => <div>Error loading posts: {error.message}</div>;export const Success = ({ posts }) => {  return posts.map(post => (    <article>      <h2>{post.title}</h2>      <div>{post.body}</div>    </article>  ));};

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 Exampleexport 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:

// Styled SFC - Dynamic Exampleexport const STYLE = props => `  h2 {    color: ${props.color} // dynamic!  }`// etc...

And that would upgrade to a CSS-in-JS equivalent implementation.

Interacting with Hooks

What if styles or other future SFC segments need to interact with component state? We could lift hooks up to the module level:

// Hooks SFC Exampleconst [toggle, setToggle] = useState(false)export const STYLE = `  h2 {    color: ${toggle ? "red" : "blue"}  }`export const Success = ({ posts }) => {  return posts.map(post => (    <article>      <h2 onClick={() => setToggle(!toggle)}>{post.title}</h2>      {toggle && <div>{post.body}</div>}    </article>  ));};

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.

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:

/components/myComponent/Component.tsx/components/myComponent/Component.scss/components/myComponent/Component.graphql/components/myComponent/Component.stories.js/components/myComponent/Component.test.js

And some people may think that is better than this:

/components/myComponent/Component.tsx/styles/Component.scss/graphql/Component.graphql/stories/myComponent/Component.stories.js/tests/myComponent/Component.test.js

But we're exchanging that for:

export default = // ... metadataexport const STYLE = // ...export const QUERY = // ...export const Success = // ...export const Stories = // ...export const Test = // ...

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 ideahere on my old proposal.

This movement has been a long time coming, and I can see the momentum accelerating now.


Original Link: https://dev.to/swyx/react-single-file-components-are-coming-3g74

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To