Git Product home page Git Product logo

react-redux-ui-tools's Introduction

React Redux UI Tools

Circle CI

Think of react-redux-ui-tools as block-level scoping for UI state.

Install

npm i react-redux-ui-tools -S
yarn add react-redux-ui-tools

Goals of this library

  1. Implementation of UI state should be easy to achieve.
  2. UI state should be global.
  3. UI state should be easily managed from each component via an action.
  4. UI state should be easy to reset (manually and when unmounting).

How it works

In this example, each block-scope represents a component and each variable represents a UI state key:

{
    // Everything inside this scope has access to filter and tags. This is our root UI component.
    let filter = ''
    let tags = []

    // Imagine the following scopes are a list of to-do task components:
    [
        {
            // Everything inside this scope has access to isSelected - plus all parent variables.
            let isSelected = true
        },
        {
            // This also has isSelected inside its scope and access to parent variables, but
            // we want isSelected to be in a separate scope that can be manipulated independently from other
            // siblings.
            let isSelected = false
        }
    ]
}

Usage

Wrap your root component with the react-redux-ui-tools ui HOC. It's given a new scope for temporary UI variables which:

  • are automatically bound to this.props.ui.
  • are automatically passed to any child component wrapped with the ui HOC.
  • will be automatically reset on componentWillUnmount (preventable via options).
  • can be reset manually via the this.props.ui.resetUI function.
  • are updatable by any child component wrapped with the ui HOC.

This is powerful. Each component is reusable and can still affect UI state for parent components.

Specifically, the ui HOC injects four props into your components:

  1. uiKey: The key passed to the HOC.
  2. ui: The UI state for the component's uiKey.
  3. updateUI: A function accepting either a name/value pair or object which updates state within uiKey.
  4. resetUI: A function which resets the state within uiKey to its default.

On instantiation and after calls to resetUI, the HOC will set any default state specified. On componentWillUnmount the entire state in uiKey will be set to undefined.

Configuration

The ui HOC takes a configuration object with the following keys:

key: string or function, defaults to random characters

The name of the key used in the UI reducer under which we store all state.

If specified as a function, will be passed props as the first argument and will only generate the key when the component is mounted. If not specified it will be autogenerated based on the component name suffixed with a random hex code. Components using the same key will share the same UI context!

persist: bool, defaults to false

Set to true if the UI state for this component should persist after componentWillUnmount. You must also explicitly define a key for this component, otherwise the component will randomize the key and load new UI state on instantiation.

Note: All parent UI components also need to set this to true for this to take effect. Think of block-level scoping again โ€” if a parent scope quits all child scopes are also out of context!

state: object

All UI variables need to be explicitly defined in the state object. This allows us to determine which scope a variable belongs to, as scope is inherited in the component tree.

Think of this as using let inside your block scopes.

reducer: function, optional

An additional reducer for this component's UI state.

mergeProps: function, optional

mergeProps passed to react-redux's connect.

options: object, optional

options passed to react-redux's connect.

Example configuration:

const uiConfig = {
    key: 'SomeComponent', // or as a function, e.g. props => `SomeComponent:${props.id}`
    persist: true,
    state: {
        uiVar1: '',
        uiVar2: (props, state) => state.router.location.query.searchTerm
    },
    reducer: (state, action) => {
        switch( action.type ) {
            case '@@reduxReactRouter/routerDidChange':
                if ( action.payload.location.query.extra_filters ) {
                    return {
                        ...state,
                        extraFilters: true
                    }
                }
                return state
            default:
                return state
        }
    },
    mergeProps: () => ({}),
    options: {}
}

Example HOC usage

// rootReducer.js
import { combineReducers } from 'redux'
import { reducer as ui } from 'react-redux-ui-tools'

const createRootReducer = reducers => {
    return combineReducers( {
        ...reducers,
        ui,
    } )
}

export default createRootReducer
// SomeComponent.js
import React, { Component } from 'react'
import ui from 'react-redux-ui-tools'

class SomeComponent extends Component {
    render() {
        const {
            someUIFlag,
            updateUI,
            resetUI
        } = this.props.ui

        return (
            <button onClick={updateUI('someUiFlag', !someUiFlag)}>
                Toggle flag in UI state
            </button>

            <button onClick={resetUI()}>
                Reset UI state
            </button>
        )
    }
}

const uiConfig = { /* as above */ }

export default ui( uiConfig )( SomeComponent )

NOTE: If you wish to use Immutable.JS for UI state, add the react-redux-ui-tools reducer with a third parameter set to Immutable.JS' Map class, e.g.:

import { combineReducers } from 'redux'
import { Map } from 'immutable'
import { reducer as ui } from 'react-redux-ui-tools'

const createRootReducer = reducers => {
    return combineReducers( {
        ...reducers,
        ui: (state, action) => ui( state, action, Map )
    } )
}

export default createRootReducer

NOTE: You may also use the HOC as a decorator:

// SomeComponent.js
import React, { Component } from 'react'
import ui from 'react-redux-ui-tools'

const uiConfig = { /* as above */ }

@ui( uiConfig )
class SomeComponent extends Component {
    render() {
        const {
            someUIFlag,
            updateUI,
            resetUI
        } = this.props.ui

        return (
            <button onClick={updateUI('someUiFlag', !someUiFlag)}>
                Toggle flag in UI state
            </button>

            <button onClick={resetUI()}>
                Reset UI state
            </button>
        )
    }
}

export default SomeComponent

Example of UI state inheritance

import React, { Component } from 'react'
import ui from 'react-redux-ui-tools'

// Component A gets its own context with the default UI state below.
// this.props.ui will contain this state map.
@ui( {
    state: {
        // use the filter query parma via redux-router as the default
        filter: (props, state) => state.router.location.query.filter,
        isFormVisible: true,
        isBackgroundRed: false
    }
} )
class A extends Component {
    render() {
        return (
            <div>
                // This will render '{ "filter": '', isFormVisible: true, isBackgroundRed: false }'
                <pre><code>{ this.props.ui }</code></pre>

                // Render child B
                <B />
            </div>
        )
    }
}

// B inherits context from A and adds its own context.
// This means that this.props.ui still contains A's state map.
@ui()
class B extends Component {
    render() {
        return (
            <C />
        )
    }
}

// C inherits context from its parent B. This works recursively,
// therefore C's this.props.ui has the state map from A plus someChildProp.
// Setting variables within C updates within the context of A; all UI
// components connected to this UI key will receive the new props.
@ui( {
    state: {
        someChildProp: 'foo'
    }
} )
class C extends Component {
    render() {
        return (
            <div>
                <p>I have my own UI state C and inherit UI state from B and A</p>
                <p>
                    If I define variables which collide with B or A mine will
                    be used, as it is the most specific context.
                </p>
            </div>
        )
    }
}

MIT license.

Developed by Jonathan Horowitz.

Based on the original redux-ui code written by Franklin Ta and Tony Holdstock-Brown.

react-redux-ui-tools's People

Contributors

apostolisanastasiou avatar baerrach avatar dfguo avatar ignatiusreza avatar invader444 avatar jdstep avatar krzysiek1507 avatar kurtfunai avatar rivertam avatar skeate avatar tonyhb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

react-redux-ui-tools's Issues

Use transform-runtime babel plugin to avoid errors due to Symbol.iterator === undefined

Hey there, @jhorowitz-firedrum! ๐Ÿ‘‹

First off, thanks for forking redux-ui and keeping the library alive. ๐Ÿ™

So, I'm working on a cross-platform app using React, React Native, and Expo. Just moved over to react-redux-ui-tools, and everything's working great on web and iOS, but I'm getting the following error on Android when I open the app:

TypeError: undefined is not a function
(evaluating 'searchKeyPath[typeof Symbol === "function" ? Symbol.iterator : "@@iterator"]()')

It points to:

- node_modules/react-redux-ui-tools/dist/utils.js:157:12 in __getIn
- node_modules/react-redux-ui-tools/dist/utils.js:257:15 in getIn
- node_modules/react-redux-ui-tools/dist/ui.js:157:29 in componentWillMount

Any thoughts as to why this is occurring on Android? ๐Ÿ˜ฐ

Thanks! ๐Ÿ˜ƒ

Proposal: allow state param to be object _or_ function

Currently, state object type (POJO or Immutable.js collection or whathaveyou) can be configured by passing a third argument to the reducer function

combineReducers({
  ...reducers,
  ui: (state, action) => ui( state, action, Map )
})

And keys in the ui state tree can be either keys or functions in order that they can be initialized based on component props.

@ui({
  state: {
    uiVar1: '',
    uiVar2: (props, state) => state.router.location.query.searchTerm
  },
  ...
})

Proposal: allowing the entire state param to be either an object or a function could simplify the above two features, and result in fewer function calls when initializing the state.

E.g.

// state as plain object
@ui({
  state: {
    uiVar1: 'this',
    uiVar2: 'that'
  }
})

// state as function
@ui({
  state: (props, state) => ({
    uiVar1: 'this',
    uiVar2: state.router.location.query.searchTerm
  })
})

// state as function returning Immutable.js collection
@ui({
  state: (props, state) => Immutable.Map({
    uiVar1: 'this',
    uiVar2: state.router.location.query.searchTerm
  })
})

Any issues/cases I'm not covering here?

If you're interested in this extension to the API, I'd be happy to submit a PR.

nested decorated component errors on unmount

It appears this was already reported in the original fork. Essentially, if two nested components are decorated with the redux-ui decorator and the parent is unmounted, that parent will error on unmount and neither parent nor child will clean itself up in store.

The code errors here.

@economysizegeek does a good job a describing why this is happening in the linked issue.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.