frint-props

    0.2.1 • Public • Published

    frint-props

    npm

    Compose reactive props for FrintJS Apps


    Guide

    Installation

    With npm:

    $ npm install --save rxjs frint-props
    

    Via unpkg CDN:

    <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.0/Rx.min.js"></script>
     
    <script src="https://unpkg.com/frint-props@latest/dist/frint-props.min.js"></script>
     
    <script>
      // available as `window.FrintProps`
    </script> 

    Concepts

    The package consists of multiple functions that enable you to compose your props as an RxJS Observable.

    There are two kinds of functions:

    1. That adds new props to the stream
    2. That processes the props stream further (like RxJS operators)

    And then there is compose function that accepts both kinds of functions as arguments, and returns a single function that you can use anywhere.

    Usage

    We can start small, and prepare a stream that only emits the props once, and it doesn't change over time:

    import { withDefaults } from 'frint-props';
     
    const defaultProps = {
      foo: 'foo value',
    };
    const props$ = withDefaults(defaultProps)();

    Now that we have the Observable available as props$, we can subscribe to it as needed:

    props$.subscribe(props => console.log(props));

    Reactivity

    But we have more real world use cases that require our props to change over time too. We can consider using withState for this example:

    import { withState } from 'frint-props';
     
    const props$ = withState('counter', 'setCounter', 0)();

    Now the props$ Observable will emit an object with these keys:

    • counter (Integer): The counter value
    • setCounter(n) (Function): Call this function to update counter value

    Composition

    You can compose multiple functions together to generate a combined stream of all props. For that, we can use the compose function:

    import { compose, withDefaults, withState } from 'frint-props';
     
    const props$ = compose(
      withDefaults({ foo: 'bar' }),
      withState('counter', 'setCounter', 0),
      withState('nane', 'setName', 'FrintJS')
    )();

    The props$ Observable will now emit with an object with these keys:

    • foo (String)
    • counter (Integer)
    • setCounter(counter) (Function)
    • name (String)
    • setName(name) (String)

    As you call functions like setCounter or setName, it will emit a new object with updated values for counter and name.

    Operators

    Besides adding just props, you may also need to process the stream further.

    For example, you may want to control how often the Observable emits new values. We can use shouldUpdate function for this:

    import { compose, withDefaults, withState, shouldUpdate } from 'frint-props';
     
    const props$ = compose(
      withDefaults({ counter: 0 }),
      withState('counter', 'setCounter', 0),
      shouldUpdate((prevProps, nextProps) => {
        return prevProps.counter !== nextProps.counter;
      })
    )();

    The implementation of shouldUpdate above tells our props$ Observable to emit new values only if the counter value has changed. Otherwise nothing new is emitted.

    Write your own functions

    The API of creating your own functions is pretty simple.

    Basic function to add props

    A basic function that is concerned about adding new props can be written like this:

    function withFoo() {
      return function () {
        return {
          foo: 'foo value here',
        };
      };
    }

    You can also return an Observable instead of a plain object:

    import { of } from 'rxjs/observable/of';
     
    function withFoo() {
      return function () {
        return of({
          foo: 'foo value here',
        });
      };
    }

    Accessing additional arguments

    You would notice that instead of returning the Object/Observable directly from our function, we return another function which takes care of returning the final result.

    This allows us to access additional arguments when composing props.

    Imagine if we want to make the FrintJS app instance available to our functions:

    const app = new App();
     
    const props$ = compose(
      withFoo()
    )(app);

    The argument app can now be accessed inside our function like this:

    withFoo() {
      return function (app) {
        return {
          foo: 'foo value here',
        };
      };
    }

    The way some of our functions are designed in this repository, the returned functions expect to receive the same arguments as our observe higher-order component receives in frint-react, which are: app and parentProps$.

    Operator functions

    Besides just adding new props, functions can also take care of processing the stream further just like how RxJS operators work.

    We can create a function that will check if there is any foo prop, and then capitalize it:

    import { map } from 'rxjs/operators/map';
     
    function capitalizeFoo() {
      return function () {
        return map((props) => ({
          ...props,
          foo: props.foo
            ? props.foo.toUpperCase()
            : undefined,
        }));
      };
    }

    Can be composed together as follows:

    import { compose } from 'frint-props';
     
    const props$ = compose(
      withFoo(),
      capitalizeFoo()
    );

    The props$ Observable will now emit { foo: 'FOO VALUE HERE' }.


    API

    All the functions return a function, when called, returns an Observable of props.

    withDefaults

    withDefaults(defaultProps)

    Arguments

    1. defaultProps: Default props to start the stream with

    Example

    const props$ = withDefaults({ foo: 'foo value here' })();

    withState

    withState(valueName, setterName, initialValue)

    Arguments

    1. valueName (String): Prop name for the value
    2. setterName (String): Prop name for the setter function
    3. initialValue (any): Initial value for the state

    Example

    const props$ = withState('counter', 'setCounter', 0)();

    withStore

    withStore(mapState, mapDispatch, options = {})

    Works with frint-store or Redux store set in FrintJS App as a provider.

    Arguments

    1. mapState (Function OR null): Maps state to props
    2. mapDispatch (Object): Action creators keyed by names
    3. options (Object) [optional]: Object with additional configuration
    4. options.providerName (String): Defaults to store
    5. options.appName (String): Defaults to null, otherwise name of any Child App

    Example

    const app = new App(); // assuming it has a `store` provider
     
    const mapState = state => ({
      foo: state.foo,
      bar: state.bar
    });
     
    const mapDispatch = {
      handleClick: () => ({
        type: 'HANDLE_CLICK'
      })
    };
     
    const props$ = withStore(mapState, mapDispatch)(app);

    You can also pass null in place of mapState parameter if you don't want to subscribe to store updates.

    const app = new App(); // assuming it has a `store` provider
     
    const mapDispatch = {
      handleClick: () => ({
        type: 'HANDLE_CLICK'
      })
    };
     
    const props$ = withStore(null, mapDispatch)(app);

    withObservable

    withObservable(source$, ...mappers)

    Arguments

    1. source$ (Observable OR function returning Observable)
    2. mapper (Function): Returning props OR Observable of props

    Example

    import { of } from 'rxjs/observable/of';
     
    const props$ = withObservable(
      of({ foo: 'foo value here' }),
      props => ({ foo: props.foo.toUpperCase() }),
      props => ({ foo: `${props.foo}!` })
    )();

    Generated from a function:

    import { of } from 'rxjs/observable/of';
     
    const props$ = withObservable(
      () => of({ foo: 'foo value here' })
    )();

    withHandlers

    withHandlers(handlers)

    This function can be only used via compose.

    Arguments

    • handlers (Object): Functions keyed by prop name

    Example

    const props$ = compose(
      withHandlers({
        handleClick: props => () => console.log('Clicked!')
      })
    )();

    Other props are accessible too:

    const props$ = compose(
      withState('counter', 'setCounter', 0),
      withHandlers({
        increment: props => () => props.setCounter(props.counter + 1)
      })
    )();

    Additional arguments can be accessed as follows:

    const props$ = compose(
      withHandlers({
        handleClick: (props, arg1, arg2) => () => console.log('Clicked!')
      })
    )(arg1, arg2);

    compose

    compose(...functions)

    Composes multiple functions into a combined single function, that can be called later.

    Example

    const props$ = compose(
      withDefaults({}),
      withState('counter', 'setCounter', 0),
      withState('name', 'setName', 'FrintJS'),
      shouldUpdate((prevProps, nextProps) => true)
    )();

    map

    map(mapperFn)

    Arguments

    1. mapperFn (Function): Function that accepts processed props, and returns new mapped props object

    Example

    const props$ = compose(
      withDefaults({ foo: 'foo value' }),
      map(props => ({ foo: props.foo.toUpperCase() }))
    )();

    Will emit { foo: 'FOO VALUE' }.

    pipe

    pipe(operator)

    Pipes with any RxJS operator.

    Arguments

    1. operator (Function): RxJS operator

    Example

    import { map } from 'rxjs/operators/map';
     
    const props$ = compose(
      withDefaults({ foo: 'foo value' }),
      pipe(map(props => ({ foo: props.foo.toUpperCase() })))
    )();

    shouldUpdate

    shouldUpdate((prevProps, nextProps) => true)

    Controls when to emit props.

    Arguments

    1. Function: receives previous and next props, and should return a Boolean deciding whether to update or not

    Keywords

    Install

    npm i frint-props

    DownloadsWeekly Downloads

    169

    Version

    0.2.1

    License

    MIT

    Unpacked Size

    91.7 kB

    Total Files

    48

    Last publish

    Collaborators

    • jbaudin
    • rmachado-travix
    • mmirandaalex
    • fahad19
    • markvincze
    • viacheslaff
    • oriente