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

@singletn/react-singletn

v0.0.17

Published

`react-singletn` is a tiny library to help you manage your global and local states (based on [`@singletn/core`](../core)) without any need for configuration and no dependency on context.

Downloads

60

Readme

@singletn/react-singletn  npm version

react-singletn is a tiny library to help you manage your global and local states (based on @singletn/core) without any need for configuration and no dependency on context.

Installing

yarn add @singletn/react-singletn

or

npm i @singletn/react-singletn

Basic usage

To quickly demonstrate how to start using singletn with your react app, we'll simply show how to convert a basic counter managed via React.useState hook into a Singletn state that can use hooks or our components.

The React.useState way

Let's say we have a simple component:

import { useState } from 'react'

function App() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <button onClick={() => setCount(count => count + 1)}>count is {count}</button>
    </div>
  )
}

So, to convert this to a singletn state, first step is to create a class that extends SingletnState:

import { SingletnState } from '@singletn/react-singletn'

// SingletnState takes a generic type to type the state
class CounterState extends SingletnState<{ counter: number }> {
  // initialize the state
  state = {
    counter: 0,
  }

  // expose a method that changes the state.
  increase = () => this.setState(s => ({ counter: s.counter + 1 }))
}

Ok! This should be enough for us to start working with our new Sigletn state. Let's see how to do that:

First, we should import the hook we'll use

import { useSingletn } from '@singletn/react-singletn'

After that, we can replace the React.setState hook with useSingletn hook.

- const [count, setCount] = useState(0)
+ const { state, increase } = useSingletn(CounterState)

Now, we can make the adjustment to the button:

- <button onClick={() => setCount(count => count + 1)}>count is {count}</button>
+ <button onClick={increase}>count is {state.count}</button>

That's all you need to do to convert your local state to use Singletn state. 🎉

Next, let's see some other usages

Starting anew

In order to use react-singletn, you need to create a class that extends SingletnState, provided on the package.

import { SingletnState } from '@singletn/react-singletn'

interface UserState {
  name: string
  email: string
  phoneNumber: string
}

export class User extends SingletnState<UserState> {
  public state = {
    name: '',
    email: '',
    phoneNumber: '',
  }

  public setUser = (user: UserState) => this.setState(user)

  public setName = (name) => this.setState({ name })

  public setEmail = (email) => this.setState({ email })

  // ...
}

Once you have your singletn, you can now start sharing its state:

import * as React from 'react'
import { useSingletn } from '@singletn/react-singletn'
import { User } from './User'

export const App = () => {
  const { getState, setName, setUser } = useSingletn(User)

  React.useEffect(() => {
    fetch('/user')
      .then(response => response.json)
      .then(data => setUser(data))
  }, [])

  return <input value={getState().name} onChange={e => setName(e.target.value)} />
}

Share globally and locally

If your intention is to share the state globally, you can then use simply the reference to the class inside the useSingletn call. However, you can create local states by using useLocalSingletn instead.

export const App = () => {
  // uses the global state for User
  const user = useSingletn(User)

  return (
    // ...
  )
}

export const App = () => {
  // creates a local state for User
  // This state will be deleted when component unmounts
  const user = useLocalSingletn(User)

  return (
    // ...
  )
}

In order to configure the behaviour of your local singletn instance, you can make use of a configuration object as a second parameter.

export const App = () => {
  const user = useSingletn(User, {
    // receive a callback with the new state when there's a change
    onUpdate: (nextState) => {},
    // a function to resolve whether or not the changes to the state should trigger a rerender
    shouldUpdate: (prevState, nextState) => false,
    // An array of keys of the state object that, when changed, triggers a rerender
    // If it's an empty array, it never rerenders.
    watchKeys: ['name'],
  })

  return (
    // ...
  )
}

Note that the options object only acept one option between shouldUpdate and watchKeys.

SingletnController component

This package also exports a SingletnController component. This allows you to avoid re-rendering your whole component when your state changes.

import { SingletnState, SingletnController, getSingletn } from '@singletn/react-singletn'

interface State {
  count: number
}

class Counter extends SingletnState<State> {
  state = {
    count: 0,
  } as State

  increase = () => this.setState(s => ({ count: s.count + 1 }))
  decrease = () => this.setState(s => ({ count: s.count - 1 }))
}

function App() {
  const { increase, decrease } = React.useMemo(() => getContainer(Counter), [])

  return (
    <div>
      <h1>Singletn Playground</h1>
      <div>
        <SingletnController singletn={Counter} watchKeys="count">
          {({ state }) => (
            <h2>Count is {state.count}</h2>
          )}
        </SingletnController>
        <button onClick={decrease}>-</button>
        <button onClick={increase}>+</button>
      </div>
    </div>
  )
}

SingletnController component makes use of render props pattern in order to allow you to re-render only specific parts of the components, so that you don't have to do a complete re-render every time a small part of the state changes.

Prevent rerenders

If your component will only use the methods from the singletn instance, in order to avoid re-rendering it every time the state changes, you can follow some of the following approaches:

// simply tell the hook never to update
const { increase, decrease } = useSingletn(Counter, { shouldUpdate: () => false });

// similar to react effect dependencies
const { increase, decrease } = useSingletn(Counter, { watchKeys: [] });

// instead of using the hook, you can just get the instance from the original singletn instances map.
const { increase, decrease } = getSingletn(Counter)

// if using the SingletnController component, we can pass an empty array as `watch` prop
<SingletnController singletn={Counter} watch={[]}>{singletn => ( /* component */)}</SingletnController>

asSignal

Using the asSignal decorator on your states allow you to skip the components re-render altogether. Here's how this can be done in order with the counter example:

import { SingletnState, SingletnController, getSingletn, asSignal } from '@singletn/react-singletn'

interface State {
  count: number
}

class Counter extends SingletnState<State> {
  state = {
    count: 0,
  } as State

  increase = () => this.setState(s => ({ count: s.count + 1 }))
  decrease = () => this.setState(s => ({ count: s.count - 1 }))

  displayCount = asSignal(() => this.state.count)
}

function App() {
  const { increase, decrease, displayCount } = getContainer(Counter)

  return (
    <div>
      <h1>Singletn Playground</h1>
      <div>
        <h2>Count is {state.displayCount()}</h2>

        <button onClick={decrease}>-</button>
        <button onClick={increase}>+</button>
      </div>
    </div>
  )
}

The code above wouldn't trigger a re-render of the App component. Instead, it will only update the DOM specifically where the counter is being displayed.

Context

Although react-singletn isn't reliant on context, there are cases in which the context could be useful. In these cases, you can use SingletnProvider

const App = () => {
  return [1, 2, 3].map((id) => (
    <SingletnProvider singletn={Counter} key={id}>
      <Countdown />
    </SingletnProvider>
  ))
}

const Countdown = () => {
  const { increase, decrease, getState } = useSingletnContext(Counter)

  const { count } = getState()

  return (
    <div>
      <div>
        <h2>Count is {count}</h2>
        <button onClick={decrease}>-</button>
        <button onClick={increase}>+</button>
      </div>
    </div>
  )
}

Other ways to store your state

singletn also allows you to use different base SingletnState to store your states in other ways. Read more about it in the subprojects: