Swift-like React: Can We? Should We?
Tags: react, swift
Published: 2022-04-12 05:04:05

⚠️ This is just an experiment that is clearly not meant for production.

After playing with SwiftUI for a few days I was impressed by its syntax and
wanted to see if I can write React this way. Possibly an absurd thought, but why not.

It turned out like this:

```tsx
const SandwichItem = (sandwich: Sandwich) =>
  HStack(
    Image(sandwich.thumbnailName)
      .rounded(8),

    VStack(
      Text(sandwich.name)
        .as('h2')
        .variant('title'),
      Text(`${sandwich.ingredientCount} ingredients`)
        .as('small')
        .variant('subtitle'),
    ),
  )
    .alignment('center')
    .spacing('medium')

render(root, List(sandwiches, SandwichItem))
```

To compare, this is what it would look like in **JSX**:

```tsx
const SandwichItem = ({ sandwich }: SandwichItemProps) => (
  <HStack alignment="center" spacing="medium">
    <Image src={sandwich.thumbnailName} cornerRadius={8} />

    <VStack>
      <Text as="h2" variant="title">
        {sandwich.name}
      </Text>
      <Text as="small" variant="subtitle">
        {sandwich.ingredientCount} ingredients
      </Text>
    </VStack>
  </HStack>
)

render(
  root,
  <List
    items={sandwiches}
    renderItem={(sandwich) => <SandwichItem sandwich={sandwich} />}
  />,
)
```

Syntax is subjective. I’m sure some of you will prefer one over the other for
various reasons. Besides personal preferences, a few things do stand out with this syntax:

- It is a composition of **function** calls, nothing on top of JavaScript.
- Components take required **inputs** as arguments.
- Modifications are done through chained-functions, called **modifiers**.

I particularly like the separation between inputs and modifiers. In JSX, both would be **props**.

```tsx
Text('Professional photographer')
  .variant('subtitle')
  .color('muted')
```

Inputs could be primitive types like a `string` or object types like an array of components.

```tsx
VStack(
  Text('Your Messages').variant('title'),
  Text(`${unread} messages`).variant('subtitle'),
)
```

## Polymorphic, Chain-able Modifiers
Modifiers are just functions. What makes them interesting is that they can be shared by multiple components and implemented independently.

```tsx
Text(/*...*/)
  .color('muted')

HStack(/*...*/)
  .color('muted')
  .align('start')
  .spacing('gutter')

AutoGrid(/*...*/)
  .minWidth(360)
  .spacing('small')
```

Let’s see a couple of **modifier** implementations:

```tsx
// Colorable.ts
export interface Colorable extends JSX.Element {
  color: <T extends Colorable>(this: T, color: string) => T
}

export function color<T extends Colorable>(this: T, color: string) {
  const style = {
    ...this.props.style,
    color,
  }

  this.props = {
    ...this.props,
    style,
  }

  return this
}
```

```tsx
// Fontable.ts
type Font = keyof typeof fontVariants

export interface Fontable extends JSX.Element {
  font: <T extends Fontable>(this: T, font: Font) => T
}

export function font<T extends Fontable>(this: T, font: Font) {
  const style = {
    ...this.props.style,
    ...fontVariants[font],
  }

  this.props = {
    ...this.props,
    style,
  }

  return this
}
```

A component can now implement these traits:

```tsx
// Text.tsx
import { Fontable, font } from '@modifiers/Fontable'
import { Colorable, color } from '@modifiers/Colorable'

export default function Text(text: string): Fontable & Colorable {
  const element = <span>{text}</span>

  return {
    ...element,
    font,
    color,
  }
}
```

And now the component could be invoked using:

```tsx
Text('Hello, world!')
  .font('title')
  .color('hotpink')
```

A component can implement multiple modifiers which can be chained. Also, a modifier could be implemented by many components, making them polymorphic.

ℹ️ You can see more components and modifiers at https://github.com/nayaabkhan/swift-react.

## Problems
There are a few things I noticed that don’t work. Very likely, there are be more.

- **useContext** doesn’t work. But `<Context.Consumer />` works fine.
- React Developer Tools doesn’t show the components in the inspector.

I suspect this is because React cannot identify our components as we don’t use either JSX or `createElement` when creating them. If you find more problems, please report them in the comments. And if you know work-arounds, they’re very welcome.

## But, Why?
Finally, let’s address the elephant in the room. Why all this trouble?

Maybe I’m [bike shedding](https://thedecisionlab.com/biases/bikeshedding). But experimenting and sharing with everyone is the only way to know. Maybe this resonates with others and becomes a thing. Or gets buried in the rubble of bad ideas. Who knows, but it was worth it for the fun I had.

I had a great time authoring React this way. Maybe this kind of syntactic change can have unexpected, but useful impact.

- Allow component API designers to be very specific of expected input types. For instance, `Text` only accepts strings or markdown instead of any kind of **ReactNode**.
- Make it easier to share the common API along with its implementation using modifiers.
- Introduce higher-level constructs like [Identifiers](https://github.com/nayaabkhan/swift-react/blob/main/library/components/layout/List.tsx#L8 "").
- Swap React with something else without any impact on the users of the library.

In closing, I hope you [try it out on CodeSandbox](https://codesandbox.io/s/swifty-react-5vvpgi), have fun, and share your thoughts.

Until next time 👋.