@deppi/state
TypeScript icon, indicating that this package has built-in type declarations

0.0.0-development.4 • Public • Published

@deppi/state

redux but without redux and with RxJS

Usage

Note rxjs is a peer dependency, meaning you will need to include it yourself in your project in order for this package to work properly.

import {
  /**
   * createStore is the main interface for @deppi/state
   * everything else are just helpers and might/will be
   * moved to other packages
   * 
   * createStore creates a new store instance. A Store 
   * conforms to the interface
   * {
   *  // updates$ is a stream of events
   *  // that have been `dispatched`
   *  updates$: Observable<Action>;
   *  // state$ is a stream of new state
   *  // values returned from the reducer
   *  state$: Observable<State>;
   *  // dispatch is the _only_ way to update
   *  // the store at all
   *  dispatch: (a: Action) => void;
   *  // getState returns the current state
   *  // at time of calling
   *  getState: () => State;
   * }
   */
  createStore,
  /**
   * logger is an extra that will be moved
   * 
   * It is a MiddlewareFactory function that
   * logs every action and state 
   */
  logger,
  /**
   * epics is a function that, given a list of Epics,
   * returns a MiddlewareFactory function
   * 
   * Example:
   * 
   * epic$ = (store) => store.updates$.pipe(
   *  ofType('SOME_TYPE'),
   *  mapTo({
   *    type: 'SOME_OTHER_TYPE'
   *  })
   * )
   * 
   * middleware: [epics([epic])]
   */
  epics,
  /**
   * Helper function for dealing with updates$
   * useful when making epics
   * 
   * Example:
   * 
   * updates$.pipe(
   *  ofType('SOME_TYPE', /OTHER_TYPE/)
   * )
   * 
   * is equivalent to
   * 
   * updates$.pipe(
   *  filter(({ type }) => type === 'SOME_TYPE' || /OTHER_TYPE/.test(type))
   * )
   */
  ofType,
  /**
   * A way for us to connect to the store and observer changes
   * based on a Properties object
   */
  connect
} from '@deppi/store'

/**
 * We create a single reducer for our 
 * whole state object. This is responsible
 * for taking a state and action and returning
 * the next state
 */
const reducer = (state, action) => {
  if (action.type === 'UPDATE_USER') {
    return ({ ...state, user: ...action.paylaod })
  }

  return state
}

export const store = createStore({
  /**
   * Each store needs a reducer
   */
  reducer,
  /**
   * We can choose to give it a default state
   * or we can choose to let it set it as
   * {} if not given
   */
  initialState: {
    user: {
      firstName: 'Tim',
      lastName: 'Roberts',
      location: {
        city: 'Cookeville',
        state: 'TN'
      }
    }
  },
  /**
   * We can also add middleware. middleware is
   * an array of MiddlewareFactory functions.
   * 
   * Example:
   * 
   * middlewareF = store => Observable<void>
   * 
   * {
   *   middleware: [middlewareF]
   * }
   */
  middleware: [epics([
    ({ updates$ }) => updates$.pipe(
      ofType('Some_Type', /and_reg_ex/),
      map(action => ({
        type: 'New action'
      }))
    )
  ]), logger]
})

const subscription = connect({
  store,
  properties: {
    name: (state, store, props) =>
      Promise.resolve(`${state.user.firstName} ${state.user.lastName}`)
  }
}).subscribe(
  updatedValues => {
    // these are the updated values mapped from properties
    // ex: { name: `John Smith` }
  }
)

/**
 * At some point in the future,
 * we emit an event into our system
 */
setTimeout(() => 
  store.dispatch({
    type: 'UPDATE_USER',
    payload: {
      firstName: 'John',
      lastName: 'Smith'
    }
  })
)

Using with React

Inside of extras, there is a file called connectHoC. It is a general structure for how to create a Higher-Order Component to connect to deppi. Using that Component looks like this:

// store.js
import { connect as connectProperties, ... } from '@deppi/store'

/*
 we create our store
*/
const store = createStore(...)
const connectReact = store => (properties, defaultState = {}) => Comp =>
  class Wrapper extends Component {
    constructor(props) {
      super(props)

      this.state = defaultState
    }

    componentDidMount() {
      this.subscriber = connectProperties({
        store,
        properties,
        context: this.props,
      }).subscribe(newState => this.setState(oldState => Object.assign({}, oldState, newState)))
    }

    componentWillUnmount() {
      this.subscriber && this.subscriber.unsubscribe()
    }

    render() {
      return (
        <Comp { ...this.state} { ...this.props} />
      )
    }
  }


export const connect = connectReact(store)

// component.js
import React from 'react'
// import our custom connect function
// defined inside of `store.js`
import { connect } from './store'
import { of } from 'rxjs'

const User = ({ name, city, state }) => (
  <div>
    <h2>Hello, {name}</h2>
    <p>You live in {city}, {state}</p>
  </div>
)

// and we connect our component,
// passing in our properties config
export default connect({
  // each key will be given as a prop
  // with the resolved value
  name: (state, props, { updates$, state$, dispatch, getState }) =>
    // you can return a Promise
    Promise.resolve(`${state.user.firstName} ${state.user.lastName}`),
  state: state =>
    // an observable,
    of(state.user.location.state),
  city: state =>
    // or a regular value
    `${state.user.location.city}`,
},{
    /**
     * You can also give it default props,
     * and the system will start with these as
     * the given props while resolving the 
     * properties
     */
    name: 'John Smith',
    city: 'Other',
    state: 'State'
  })(User)

Types

Name Type Description
Reducer (State, Action) -> State A function that returns a new state given a pair of state, action
InitialState any The initial starting state of the Deppi Store
Middleware Store -> Observable<void> A function that returns an observable but the return value is never used
StoreConfig { reducer: Reducer, initialState?: InitialState, middleware?: [Middleware]} The configuration object for the Deppi Store
Store { updates$: Observable<Action>, state$: Observable<State>, dispatch: Action -> void, getState: () -> State } The Store object
Epic Store -> Observable<Action> Interface for create new actions due to current ones
Transformers `(State, Context, Store) -> Observable Promise
Properties { [string]: Transformer } Config for mapping properties to values
ConnectConfig { store: Store, properties: Properties, context: Any } Our configuration for the connection

API

Name Type Description
createStore StoreConfig -> Store Returns a Deppi Store
connect ConnectConfig -> Observable<Properties> Returns an Observable
epics [Epic] -> Middleware A way to create a Middleware Factory given a list of epics
logger Middleware A generic logging middleware

Package Sidebar

Install

npm i @deppi/state

Weekly Downloads

2

Version

0.0.0-development.4

License

MIT

Unpacked Size

157 kB

Total Files

37

Last publish

Collaborators

  • beardedtim