frintjs / frint

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

Proposal: Drop `streamProps` helper function #405

Open fahad19 opened 6 years ago

fahad19 commented 6 years ago

Background

Introducing frint-props

We also developed frint-props and frint-props-react recently, which is done in a separate repository here: https://github.com/frintjs/frint-props

The features that they provide makes existing streamProps redundant.

We can of course have a safe migration path for this.

Examples

Same result, one with streamProps, the other with frint-props.

With streamProps

import React from 'react';
import { observe, streamProps } from 'frint-react';

import { incrementCounter, decrementCounter } from '../actions/counter';

function MyComponent(props) {
  // `props.counter` (`Integer`)
  // `props.increment` (`Function`)
  // `props.decrement` (`Function`)
  // `props.foo` (`String`)
  return <p></p>;
}

export default observe(function (app) {
  const defaultProps = { counter: 0 };
  const store = app.get('store');

  return streamProps(defaultProps)
    .set({ foo: 'foo value' })
    .set(
      store.getState$(),
      state => ({ counter: state.counter.value })
    ),
    .setDispatch({
      increment: incrementCounter,
      decrement: decrementCounter,
    }, store)
    .get$();
})(MyComponent);

With frint-props

import React from 'react';
import { withDefaults, withStore } from 'frint-props';
import { compose } from 'frint-props-react';

import { incrementCounter, decrementCounter } from '../actions/counter';

function MyComponent(props) {
  return <p></p>;
}

export default compose(
  withDefaults({
    foo: 'foo value',
    counter: 0,
  }),
  withStore(
    state => ({ counter: state.counter.value }),
    { 
      increment: incrementCounter, 
      decrement: decrementCounter, 
    },
  )
)(MyComponent);

Further reading

dremonkey commented 6 years ago

Just started using frintjs in an application I am building and while I like this proposal on the surface, how would the following translate to this new approach?

Basic idea being that I need grab the id from props$ so that I can look up something on my store.

export default observe(function (app, props$) {

  const store = app.get('store');
  return streamProps({
    id: null,
    counter: -1
  })
    .set(
      Observable.combineLatest(
        props$,
        store.getState$()
      ),
      ([ props, state ]) => {

        const { id } = props
        const { counter } = state;

        return counter[id];
      }
    )
    .get$();
})(renderFn);
fahad19 commented 6 years ago

@dremonkey: glad to know you are using it!

Something like this should help you reach your desired result:

import React from 'react';
import { compose } from 'frint-props-react';
import { withDefaults, withObservable } from 'frint-props';
import { distinctUntilChanged, map } from 'rxjs/operators';

function MyComponent(props) {
  const { yourOwnPropKey } = props;

  return <p></p>;
}

export default compose(
  withDefaults({
    yourOwnPropKey: null,
  }),

  withObservable(
    (app, props$) => props$.pipe(
      // let's only stream further if props passed from parent have changed
      distinctUntilChanged(),

      // we have latest props, and we stream the store's state
      props => app.get('store').getState$().pipe(
        // before passing state further, we extract only a portion of it
        map(state => state.counter[props.id]),
      ),

      // we have what we need, and shape it to a props-compatible object
      desiredValue => ({
        yourOwnPropKey: desiredValue,
      }),
    ),
  ),
)(MyComponent);

You could also use observe higher-order component directly:

export default observe((app, props$) => {
  return props$.pipe(
    distinctUntilChanged(),
    switchMap(
      props => app.get('store').getState$().pipe(
        map(state => ({
          yourOwnPropKey: state.counter[props.id]),
        }),
      )
    ),
  );
})(MyComponent);
dremonkey commented 6 years ago

@fahad19 That's very helpful. Thank you!