frintjs / frint

Modular JavaScript framework for building scalable and reactive applications
https://frint.js.org/
MIT License
756 stars 37 forks source link

Proposal: higher-order component supporting sync operations #338

Closed fahad19 closed 6 years ago

fahad19 commented 6 years ago

Currently

We only have a single observe() higher-order component (HoC), that allows us to stream props to base Component, while giving access to the app instance in scope:

// components/MyComponent.js
import React from 'react';
import { observe } from 'frint-react';
import { of } from 'rxjs/observable/of';

function MyComponent(props) {
  return <p>App name: {props.name}</p>;
}

export default observe(function (app) {
  return of({
    name: app.getName(),
  });
})(MyComponent);

The component only needed the app's name, which happens to be a synchronous operation, but we are still passing the props as a stream using an Observable here.

Which works great in the client side, since this same API allows us to pass props involving both sync and async operations.

Use case for a sync-only operation

When rendering server-side, React can only handle synchronous operations.

Proposals

1: A new sync HoC

A new provide() HoC that will only pass props to base Component synchronously:

import React from 'react';
import { provide } from 'frint-react';

function MyComponent(props) {
  return <p>App name: {props.name}</p>;
}

export default provide(function (app) {
  return {
    name: app.getName(),
  };
})(MyComponent);

2: Extend existing observe() HoC to support sync

If the returned output in observe(fn)'s fn is a plain object, treat it as a sync operation:

export default observe(function (app) {
  return {
    name: app.getName(),
  };
})(MyComponent);

If the returned value is an Observable, then proceed with the existing behaviour of streaming props.

Server-side usage

Depending on which proposal is chosen, the Components could be written like this to make them truly universal:

function MyComponent(props) {
  // ...
}

export default observe(function (app) {
  if (process.env.BABEL_ENV === 'server') {
    // sync
    return {
      name: app.getName(),
    };
  }

  // async
  return streamProps()
    .set('name', app.getName())
    .get$();
});

This wouldn't add to the bundle size in production because, with Webpack's DefinePlugin, we can set the value of process.env.BABEL_ENV to client, which would result in the bundled code to be:

if ('client' === 'server') {
  // sync
  // ...
}

and when minification takes place, the whole if block would be removed.