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

pathstore-react

v0.0.6

Published

State container (store) that works great with preact, react, and redux devtools.

Downloads

19

Readme

Pathstore

A simple, performant global store that works well with React.

✨ Also works with Redux Devtools ✨

Why does this exist?

Wanted a global store that is:

Table of Contents

From React local state to Pathstore

Suppose we have a simple counter component that uses local react state:

const Counter = () => {
  const [count, setCount] = useState(0)
  return <div>
    <button onClick={() => setCount(count + 1)} >Increment</button>
    <span>count: {count}</span>
  </div>
}

To use Pathstore instead of local state, replace useState(0) with store.use(['counter'], 0). Your component should look like this.

const Counter = () => {
  const [count, setCount] = store.use(['counter'], 0)
  return <div>
    <button onClick={() => setCount(count + 1)} >Increment</button>
    <span>count: {count}</span>
  </div>
}

Now the counter value is stored in the global state {..., counter: <value>, ...} and its value can easily be used in other components.

You might wonder, why did we pass in ['counter'] instead of just 'counter'. This is because Pathstore lets you use nested values just as easily as any other values. For example, if instead of ['counter'] we pass in ['counter', 'nestedExample'], then the value of the counter in the store would look something like this:

{
  counter: {
    nestedExample: <value>,
    ...
  },
  ...
}

Getting started

install

npm install --save pathstore-react

create a store

import {createStore} from 'pathstore-react'
import {useState, useRef, useEffect} from 'react'

export const store = createStore({ useEffect, useState, useRef, reduxDevtools: true })

use the store

Examples

Table of Contents

Form Input


// This will be a lot more satisfying if you have Redux Devtools running in your browser...

const TextInput = ({formName, name, defaultValue = '', ...props}) => {
  const [value, setValue] = store.use([formName, 'values', name], defaultValue)
  return <input
    onChange={ev => setValue(ev.target.value)}
    name={name}
    value={value}
    {...props}
  />
}

const FieldError = ({formName, name, ...props}) => {
  const [value] = store.use([formName, 'errors', name])
  return value ? <span {...props}>{value}</span> : null
}

const ExampleForm = () => {
  const name = 'ExampleForm'
  const onSubmit = ev => {
    ev.preventDefault()
    const values = store.get([name, 'values'])
    // from here you can run some validations, submit the values, etc.
    // ...
    // As an example, lets say there's an email field error:
    store.set([name, 'errors', 'email'], 'A fake email error')
    return
  }
  return <form onSubmit={onSubmit}>
    <TextInput formName={name} name='email' type='email' />
    <FieldError formName={name} name='email' />
    <TextInput formName={name} name='password' type='password' />
    <FieldError formName={name} name='password' />
    <button>Submit</button>
  </form>
}

Initialization

Often you need to set some initial state before your app even starts, and maybe again when the user logs out.

const initStore = (store) => {
  store.set([], {
    token: localStorage.getItem('token'),
    theme: localStorage.getItem('theme')
  })
}

const store = createStore(...)
initStore(store)

Many updates at once

Sometimes you want to change state in more than once place but you only want your components to rerender after all the changes are made. There's a noPublish option for that.

const onSubmit = (ev) => {
  // ...
  store.set(['modalState'], undefined, {noPublish: true})
  store.set(['modal'], undefined)
  // subscriptions will only be called after the second store.set is called
}

Performance

Improving performance of global stores was one of the main reasons Pathstore was built. Global stores often call every subscriber for every state change. It's basically asking every stateful component "Do you care about this change?" for every single state change. This becomes a problem if you're storing things that can change many times in a short period of time (like mouse position). This doesn't seem optimal. With Pathstore, subscribers can subscribe to a specific location in the store, which could cut down significantly on the number of times it's called.

I haven't gotten the chance to benchmark Pathstore vs alternatives like Redux and Unistore. Not even sure the best way to do this. If anyone has ideas, please let me know by creating an issue.

Pathstore is also quite small, for those concerned with initial load times.

API

Table of Contents

createStore

Creates a new store.

Parameters

  • init Object The initialization object.
    • useState Function The useState function from react or preact.
    • useRef Function The useRef function from react or preact.
    • reduxDevtools Boolean Whether to turn on redux devtools.

Returns

Examples

import { useState, useRef } from 'react'
let store = createStore({useState, useRef, reduxDevtools: true})
store.subscribe([], state => console.log(state) );
store.set(['a'], 'b')   // logs { a: 'b' }
store.set(['c'], 'd')   // logs { a: 'b', c: 'd' }

store

An observable state container, returned from createStore

store.set

A function for changing a value in the store's state. Will call all subscribe functions of changed path.

Parameters

  • path Array The path to set.
  • value Any The new value. If you provide a function, it will be given the current value at path and should return a new value. (see examples).
  • options Object (optional) Some additional options.
    • noPublish Boolean (optional) Do not trigger subscriber functions. The subscribe functions that would have been called will instead be called the next time store.set is called without the noPublish option
    • identifier String (optional) A custom identifier that will be shown in Redux Devtools. Normally in Redux-land this would be the action. In Pathstore this is normally the path.
    • setByFunction Boolean (optional, default false) If passing a function in as the value argument, the function will be called with the current value at the path. If this is false then the function passed in will be treated as a value and saved directly in the store.

Examples

store.set([], {}) // the store is {}
store.set(['a'], 1) // the store is {a: 1}
store.set(['a'], x => x + 1, {setByFunction: true}) // the store is {a: 2}
store.set(['b', 0, 'c'], 1) // the store is {a: 2, b: [{c: 1}]}

store.get

A function for retrieving values in the store's state.

Parameters

  • path Array The path to use.

Returns

  • value Any The value at path.

Examples

store.set([], {a: 1, b: {c: 'd'}, e: ['f', 'g', 'h']})
store.get(['a'])  // => 1
store.get(['b', 'c'])  // => 'd'
store.get(['e', 1])  // => 'g'
store.get(['e', 4])  // => undefined
store.get(['z'])  // => undefined
store.get([])  // => {a: 1, b: {c: 'd'}, e: ['f', 'g', 'h']}

store.subscribe

Add a function to be called whenever state changes anywhere along the specified path.

Parameters

  • path Array The path to use.
  • subscriber Function The function to call when state changes along the path.

Returns

  • unsubscribe Function Stop subscriber from being called anymore.

Examples

Subscribe to any state changes

let unsub = store.subscribe([], () => console.log('state has changed') );
store.set(['a'], 'b')   // logs 'state has changed'
store.set(['c'], 'd')   // logs 'state has changed'
store.set([], {})   // logs 'state has changed'
unsub()   // stop our function from being called
store.set(['a'], 3)   // does not log anything

Subscribe to a specific path in state

let unsub = store.subscribe(['a', 'b', 'c'], () => console.log('a.b.c state has changed') );
store.set([], {a: {b: {c: 4}}})   // logs 'a.b.c state has changed'
store.set(['a', 'b', 'c'], 5)   // logs 'a.b.c state has changed'
store.set(['b'], 5)   // does not log anything
store.set(['a', 'b', 'd'], 2)   // does not log anything
store.set(['a', 'b', 'c', 'd', 'e'], 2)   // logs 'a.b.c state has changed'
store.set([], {x: 123})   // logs 'a.b.c state has changed'

store.use

Hook that returns a stateful value, and a function to update it.

Parameters

  • path Array The path to use.
  • initialValue Any (optional) The initial value.
  • options Object (optional) Some additional options.
    • cleanup Boolean (optional, default false) Set the value at path in state to undefined when the component unmounts.
    • override Boolean (optional, default false) Set the value at path to initialValue even if there is already a value there.
    • identifier String (optional) An identifier to use in Redux Devtools.
    • setByFunction Boolean (optional, default false) Passing a function in to the set function returned by store.use, the function will be called with the current value at the path. If this is false then the function passed in will be treated as a value and saved directly in the store.

Return

  • [value, setValue] Array
    • value Any The value at path
    • setValue Function Set a new value at path

Examples

A counter component, the value of the counter will be stored in state under {counter: <here>}

const Counter = () => {
  const [count, setCount] = store.use(['counter'], 0)
  return <div>
    <button onClick={() => setCount(count + 1)} >Increment</button>
    <span>count: {count}</span>
  </div>
}

The same component, but storing the count under {counter: {nested: <here>}}

const Counter = () => {
  const [count, setCount] = store.use(['counter', 'nested'], 0)
  return <div>
    <button onClick={() => setCount(count + 1)} >Increment</button>
    <span>count: {count}</span>
  </div>
}

This time storing the count under a dynamic id key {counter: {<id>: <here>}}

const Counter = ({id}) => {
  const [count, setCount] = store.use(['counter', id], 0)
  return <div>
    <button onClick={() => setCount(count + 1)} >Increment</button>
    <span>count: {count}</span>
  </div>
}

Using the cleanup option. When the component unmounts the value will be set to undefined, {counter: undefined}

const Counter = () => {
  const [count, setCount] = store.use(['counter'], 0, {cleanup: true})
  return <div>
    <button onClick={() => setCount(count + 1)} >Increment</button>
    <span>count: {count}</span>
  </div>
}

Using the override option. When the component mounts the value will be set to initialValue, even if there is already a value in state. count will be 0 because the current value 4 is overriden with the initial value 0

store.set([], {counter: 4})
const Counter = () => {
  const [count, setCount] = store.use(['counter'], 0, {override: true})
  return <div>
    <button onClick={() => setCount(count + 1)} >Increment</button>
    <span>count: {count} will always start at 0</span>
  </div>
}

Real World Examples