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

overscope

v0.1.2

Published

React Hooks over Contexts state-management toolkit

Downloads

19

Readme

Overscope: Hooks over Contexts state-management toolkit for React

Convenience. Speed. Flexibility.

If you want to organize state management in your React application in a modern way, while maintaining flexibility and usability, then this package is for you.

Hooks over Context approach

Why should you choose the hooks + contexts approach?

This approach allows you to easily and quickly organize independent and local state management at the level of a separate branch of the component tree with complete state isolation.

In simple terms: you can place several identical components on the page and their states will not overlap.

Problems of this approach

However, this approach has 2 significant problems:

  1. performance - any update of the context value will update absolutely all components using this context;
  2. boilerplate - leads to the appearance of a large amount of boilerplate and essentially unnecessary code.

This package solves these problems.

Installation

Install stable version using:

npm install overscope
yarn add overscope

Introduction

To solve the problems of the original (contexts + hooks) approach, a set of tools was created, including:

  • Tools for working with contexts;
  • Tools for working with state;
  • Tools for working with transform (reducers).

Advantages

Substantial advantages over the original (contexts + hooks) approach and other toolkits:

1. Partiality

The toolkit includes separate solutions for working with contexts and states, they can be used both together and separately.

2. Separated state from transform (reducers)

The state and transform are stored separately, which allows you to always understand what kind of data was extracted from the store.

3. Immer

The built-in immer allows you to update state using a mutable approach without deviating from the immutable data paradigm.

4. Selectors

Selectors allow you to extract data from the store, and only this data will affect the update of the component.

5. Lightweight

Package size does not exceed 40 kb.

Disadvantages

TBD

Usage

  1. Working with data scheme:

If you are using typescript you should describe the state and transform types like this:

// State (only value)
export type DummyState = {
  count: number,
}

// Reducers (aka Transform object)
export type DummyTransform = {
  increase: () => void,
  multiply: (multiplier: number) => void,
}
  1. Working with contexts:

Use a context factory and specify the types created earlier.

The factory returns a tuple, the first element of which is the context provider, the second element is a hook for extracting data from the store.

export const [ DummyProvider, useDummy ] = overscopeContext<DummyState, DummyTransform>()
  1. Working with data:

Form the state using the usual useState hook with the previously created type.

And also describe the transform (list of reducers) using the useTransform hook from the toolbox. To get the actual state value, you can use current in the patch context, or use stateRef to get the previously actual state value.

Remember: another powerful immer tool is used inside, use it to the fullest!

const [ state, setState ] = useState<DummyState>({
  count: initial,
})

const transform = useTransform<DummyState, DummyTransform>([ state, setState ], (patch, stateRef) => ({
  increase: () => patch((current) => {
    current.count++
  }),
  
  multiply: (multiplier) => patch((current) => {
    current.count *= multiplier
  }),
  
  complicated: {
    fractal: () => patch((current) => {
      // ... compute fractal
    }),
  },

  /**
   * Automatic events for *root* reducers (increase, multiply)
   * 
   * Warning: will not work with nested reducers (complicated.fractal)
   */
  _afterEach: () => {
    // writes actual state after calling any of root reducers
    console.log('before', stateRef.current)
  },
  
  _beforeEach: () => {
    // writes actual state before calling any of root reducers
    console.log('before', stateRef.current)
  },
}))
  1. Working with extraction:

Form the state using the usual useState hook with the previously created type.

And also describe the transform (list of reducers) using the useTransform hook from the toolkit. Remember: another powerful immer tool is used inside, use it to the fullest!

const count = useDummy((value) => value.state.count)

const {
  increase,
  multiply,
} = useDummy((value) => value.transform)

In comparison

With Contexts + Hooks:

import { DummyState, DummyTransform } from './dummyTypes'
import { FunctionComponent, useCallback, useContext, useMemo, useState } from 'react'

export type DummyStore = DummyState & DummyTransform

export const DummyContext = createContext<DummyStore>({
  count: 0,

  statistics: {
    multiply: 0,
  },

  increase: () => {},
  multiply: () => {},
})

export const DummyProvider = DummyContext.Provider

export const useDummy = (): DummyState => (
  useContext(DummyContext)
)

export const DummyWorker: FunctionComponent<any> = () => {
  const {
    count,
    statistics,
    increase,
    multiply,
  } = useContext(DummyContext)
  
  const multiplyClick = useCallback(() => {
    multiply(5)
  }, [])
  
  return (
    <div>
      <div>{ count }</div>
      <div>{ statistics.multiply }</div>
      <button type="button" onClick={ increase }>Increase</button>
      <button type="button" onClick={ multiplyClick }>Multiply</button>
    </div>
  )
}

export const DummyBlock: FunctionComponent<any> = () => {
  const [ state, setState ] = useState<DummyState>({
    count: initial,

    statistics: {
      multiply: 0,
    },
  })

  const increase = useCallback(() => {
    setState((old) => ({
      ...old,
      count: old.count + 1,
    }))
  }, [])

  const multiply = useCallback((amount: number) => {
    setState((old) => ({
      ...old,
      count: old.count * amount,
      statistics: {
        ...old.statistics,
        multiply: old.statistics.multiply + 1,
      },
    }))
  }, [])

  const contextState = useMemo(() => ({
    ...state,
    increase,
    multiply,
  }), [ state, increase, multiply ])

  return (
    <DummyProvider value={ contextState }>
      <DummyWorker />
    </DummyProvider>
  )
}

With overscope:

import { DummyState, DummyTransform } from './dummyTypes'
import { overscopeContext, useTransform } from 'overscopeContext'

export const [ DummyProvider, useDummy ] = overscopeContext<DummyState, DummyTransform>()

export const DummyWorker: FunctionComponent<any> = () => {
  const {
    count,
    statistics,
  } = useDummy((value) => value.state)

  const {
    increase,
    multiply,
  } = useDummy((value) => value.transform)

  const multiplyClick = useCallback(() => {
    multiply(5)
  }, [])

  return (
    <div>
      <div>{ count }</div>
      <div>{ statistics.multiply }</div>
      <button type="button" onClick={ increase }>Increase</button>
      <button type="button" onClick={ multiplyClick }>Multiply</button>
    </div>
  )
}

export const DummyBlock: FunctionComponent<any> = () => {
  const [ state, setState ] = useState<DummyState>({
    count: initial,

    statistics: {
      multiply: 0,
    },
  })

  const transform = useTransform<DummyState, DummyTransform>(setState, (patch) => ({
    increase: () => patch((current) => {
      current.count++
    }),
    multiply: (value: number) => patch((current) => {
      current.count *= value
      current.statistics.multiply++
    }),
  }))
  
  return (
    <DummyProvider state={ state } transform={ transform }>
      <DummyWorker/>
    </DummyProvider>
  )
}

Warning: Active development...

The package is under active development, each new version may contain breaking changes