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

possible-states

v1.3.2

Published

A cleaner way to manage states in user interfaces

Downloads

81

Readme

Possible States

A tiny library to deal with states, destroy booleans and useless data

Build Status

Todos

  • the order matters in the vuejs caseof fallback
  • only renders the first child on the vuejs when component

Goal

This was written with user interface in mind, but can be used anywhere. I wanted a cleaner way to deal with user interface states in React and Vue code. It's a way to get Enum types in javascript without having to use typescript or flow.

A few problems you might have experienced:

Implicit states and unreadable logic in the jsx / html template

{this.isLoaded() && !this.state.errors ? (
  <p>
    Status: {this.response.status}
  </p>
) : null}

By defining a Success state, that could be simplified. That was a simple example, but we know that if clauses can get pretty narly…

Booleans proliferation and other data type choices

isLoading, hasFetched, isDeleting, isEditing, these kind of booleans seems to be everywhere. Sometimes it's worse, sometimes we use the fact that a variable is null or undefined to mean implicitely that the ui is not in a particular state. I want a way to state clearly what the UI is doing.

The impossible sometimes happens

I bet you had a button with a loading indicator spinning forever, long after the server crashed. That is an impossible state. After the crash, some flags needs to be set to true, some other to false, some errors must be set, … With a static type system, that could be dealt with automatically. Unfortunately, javascript isn't one of them.

Some fields are not always used

Why bother with an error attribute when everything went well?

Installation

npm save possible-states
// or
yarn add possible-states

Usage

Create an object that holds the logic for state transition.

The transition functions toOk and toError are defined automatically.

import PossibleStates from 'possible-states'

// the default value is the first argument we passed to this function:
let ui = PossibleStates('ok', 'error')

ui.current() === 'ok'

ui = ui.toError()

ui.current() === 'error'

The possible state object is immutable. Whenever a transition function is called, a new object gets created.

Clean up logic in jsx with the when function.

:tada: Release in 1.3 not__ helpers functions execute when the state is not the one specified.

state.ui.notLoading(<Whatever/>)

These function are not only helpful for controlling markup, you can control anything with it. Here is an example with CSS classes:

<div className={state.ui.whenLoading('background-grey') || 'background-white'}/>

In the previous case, we would also have a whenOk and whenError function that would accept a callback and run it whenever the state matches.

:tada: Released in version 1.2 when___ and caseOf both accept now an object or a callback.

// That means that instead of this:
state.ui.whenLoading(() => <Spinner/>)

// You can do this directly:
state.ui.whenLoading(<Spinner/>)

// And the same for caseOf:
state.ui.caseOf({
  loading: <Spinner/>,
  _: 'Text works also',
})

Use it to clean up if-else statements that are getting out of hand:

// before

{this.loaded && !this.state.errors ? (
  <p>
    Status: this.response.status
  </p>
) : null}

// after

this.ui.whenSuccess(({status}) => (
  <p>
    Status: {status}
  </p>
))

For more checks and control, use the caseOf function.

That's how it could look like in React.

const users = PossibleStates('waiting', 'loading', 'success', 'failure')

// ... handle the fetching and transitions somewhere

users.caseOf({
  waiting: () => <p>Blank layout</p>,
  loading: () => <Spinner/>,
  success: () => <UsersList />,
  failure: () => <ErrorMessage />,
})

The library will force you to handle all the cases you have defined. If we are only interested in a couple clauses, use _ as a catch all clause:

users.caseOf({
  success: () => <UsersList />,
  _      : () => <Whatever />,
})

That's cool, but what is cooler is to define the data contained, so you don't have to deal with it in a separate field. Use the name<data1, data2> syntax for that.

  • The initial state cannot hold data
  • The data must be passed as arguments when transitioning to that state (or an error is thrown)
  • The data will be passed as an argument to the callbacks used in when and caseOf

Again, in React, it looks like this.

:exclamation: note that you need to use the functional form of setState, because state updates may be asynchronous and we need the value of the state.

// define the initial UI state
this.state.ui = PossibleStates('wait', 'ok<result>', 'error<reason>')

// then whenever needed, use transition. Use the function form of setState.
this.setState(state => ({ui: state.ui.toOk('Your result goes here')})

// Update the UI state on React
this.setState(state => ({ ui: state.ui.toOk('Your result goes here') }))

// Update the UI state on Vue
this.state.ui = this.state.ui.toOk('Your result goes here')

this.state.ui.whenOk((data) => (
  <div>
    {data.result} // <-- 'goes here'
  </div>
))

// or

this.state.ui.caseOf({
  ok: data => <div>data.result</div>  // <-- only the ok clause will receive the data as an argument
  _ : () => <div>nothing</div>
})

If you are into Vue, you can use 2 components to achieve the same result in templates.

import {When, CaseOf} from 'possible-states'

<template>
  <div>
    <When :state="ui" of="error">
      <div>Uh oh! Something when wrong!</div>
    </When>
  </div>
</template>

If the given state as data, use a scoped slot. If error is defined as error<message>:

<template>
  <div>
    <When :state="ui" of="error">
      <div slot-scope="props">Uh oh! {{props.message}}</div>
    </When>
  </div>
</template>

Use named slot named after the state they are matching.

<template>
  <div>
    <CaseOf :state="ui">
      <div slot="yes">yeah</div>
      <div slot="no">nope</div>
    </CaseOf>
  </div>
</template>

Fallback with the default slot.

:exclamation: note that at the moment, the default needs to be defined as the first child.

Pass data using scoped slots. If we had yes<yeah>:

<template>
  <div>
    <CaseOf :state="ui">
      <div slot="yes" slot-scope="props">{{ props.yeah }}</div>
      <div slot="no">nope</div>
    </CaseOf>
  </div>
</template>

Contributing

Be nice.

Add tests. If you are not sure how, let me know and we can figure it out.

Submit a PR from a branch named fix-xxxxxxx or feature-xxxxxx. Not from master.

Make sure yarn test and yarn lint pass.

Write meaningful commit messages. If needed, squash commits before opening your PR.

License

MIT

Copyright (©) 2018-present, Christophe Graniczny