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

redux-ui-shallow

v0.2.1

Published

UI state management for Redux and React

Downloads

3

Readme

redux-ui: ui state without profanity

NOTE: This project is forked from tonyhb/redux-ui. I noticed that whenever any slice of UI state changed, every component with the @ui() decorator was re-rendering, even if the components were decorated with @pureRender.

This was because of two problems:

  • Some methods were being bound at runtime. I changed them to bind in the constructor so the same references can be passed as props every time.
  • Each component decorated with @ui() is connected to the entire ui object on the global store, but it only receives a small slice of that state (i.e. the "scoped" properties). Every time any component updated a UI property, all components decorated with @ui() receive new state, even if none of the properties in the slice they're scoped to changed.

In order to fix the second issue in your app:

  1. Uninstall redux-ui: npm uninstall redux-ui --save

  2. Install redux-ui-shallow: npm install redux-ui-shallow --save

  3. Replace import references to redux-ui with redux-ui-shallow in your components and root reducer

  4. Add shallowCompare: true to your @ui() decorators' options, e.g.

    import React, {Component} from 'react';
    import ui from 'redux-ui-shallow';
    import pureRender from 'pure-render-decorator';
    
    @ui({
      state: {
        isShown: true
      },
      shallowCompare: true
    }
    @pureRender
    export default class MyComponent extends Component {
    };

Now only when the slice of relevant UI state is updated will the component re-render. Note that a shallow comparison algorithm is used, so when updating the UI properties, be careful to replace rather than manipulate objects and arrays.

--

Think of redux-ui as block-level scoping for UI state. 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 tasks:
  {
    // 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
    // isSelected is in a separate scope and can be manipulated independently from other
    // siblings.
    let isSelected = false
  }
}

Wrap your root component with the redux-ui @ui() decorator. It's given a new scope for temporary UI variables which:

  • are automatically bound to this.props.ui
  • are automatically passed any child component wrapped with the @ui() decorator
  • will be automatically reset on componentWillUnmount (preventable via options)
  • can be reset manually via a prop
  • are updatable by any child component within the @ui() decorator

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

Setup

Step 1: Add the redux-ui reducer to your reducers under the ui key:

import { reducer as uiReducer } from 'redux-ui'
// ...
combineReducers({ ...yourReducers, ui: uiReducer })

Step 2: In each 'scene' or parent component add the UI decorator with the key in which to save all state:

import ui from 'redux-ui';

@ui({
  state: {
    yourVars: 'withDefaults',
    filters: []
  }
})
class YourComponent extends React.Component {
}

Step 3: In each child component use the basic @ui() decorator; it will automatically read and write UI state to the parent component's UI key.

You can also define variables in child components. If your child component has variables named the same as a parent component think of block scoping: everything within your child component down will read from the child's scope, but the parent will use the parent's UI variable.

Usage

The @ui decorator injects four props into your components:

  1. uiKey: The key passed to the decorator from the decorator (eg. 'some-decorator' with @ui('some-decorator')
  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

The decorator will set any default state specified (see below). On componentWillUnmount the entire state in uiKey will be set to undefined. You can also blow away state by calling resetUI (for example, on router changes).

Decorator API

The decorator takes an object of options:

@ui({
  // optional key which is used to determine the UI path in which state will
  // be stored. if omitted this is randomly generated.
  key: 'some-name',
  // optional persist, defaults to false. if set to true persist will keep UI
  // state for this component after it unmounts. if set to false the UI state will
  // be deleted and recreated when the component remounts
  persist: true,
  // **required**: UI state for the component
  state: {
    uiVar1: '',
    // You can set default UI state based on the component's props and the
    // global store's state.
    uiVar2: (props, state) => state.router.location.query.searchTerm
  },
  // optional mergeProps passed to react-redux' @connect
  mergeProps: () => ({}),
  // optional `options` passed to react-redux @connect
  options: {}
})

Non-decorator API

You can use redux-ui without using an ES7 decorator like so:

import ui from 'redux-ui';
// or ui = require('redux-ui').default;

class SomeComponent extends Component {
}

SomeComponentWithUI = ui({ key: 'some-name', state: { ... }})(SomeComponent);
key: string, defaults to random characters

The name of the key used in the UI reducer under which we store all state. Allows you to create selectors in reselect with known paths, and allows setting persist below.

If this is 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, so don't supply a name to a list of components (generated in a loop) if they need their own UI state.

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.

Examples

import ui from 'redux-ui';

// 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>
    );
  }
}

Aims

UI state:

  1. Should be global
  2. Should be easily managed from each component via an action
  3. Should be easy to reset (manually and when unmounting)

All of these goals should be easy to achieve.


MIT license.

Written by Franklin Ta and Tony Holdstock-Brown.