npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

frint-props

v0.2.1

Published

Compose props reactively for FrintJS Apps

Downloads

393

Readme

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