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

@alinnert/tstate

v1.0.3

Published

This is a very simple data store/state management library, writen in TypeScript. It also comes with a React hook.

Downloads

204

Readme

TState

This is a very simple data store/state management library, writen in TypeScript. It also comes with a React hook.

Why?

When I started learning React (after making some projects with Vue) I was looking for a data store solution. The two big ones in the React ecosystem are Redux and MobX. But I think both have flaws in my opinion:

Redux is bulky (in terms of boilerplate code) and the necessary type parameters for the useSelector<T, U>() hook are awkward but necessary. Also, there's another library necessary to write async actions.

The API of MobX just feels like its all over the place. This gives me a hard time getting started with it. There are many ways to do the same thing, including decorators which aren't supported by create-react-app - for a good reason.

Then I stumbled over one of those "You probably don't need Redux" articles. It made me think. So I created a module that fetches data from a server, stores it (+ some status information) in variables and connected that modules to a React component via a hook. I made another one for a different resource on the same server. Now I was able to identify the repeating parts and abstracted them away into a new module. TState is the result of that effort. It turned out to be less than 50 LOC which was really surprising.

What are the benefits?

  • It is written with TypeScript in mind. It's super convenient to type your stores.
  • The API surface is tiny, which makes it easy to learn.
  • It "supports" async actions out of the box, by not limiting to sync actions in the first place.
  • No wrapper component needed for React.
  • Also, React support is separated from the core implementation, but included. So, you can use it basically anywhere.

Are there drawbacks?

  • A store root must be an object, no arrays and other non-object types are supported. Its properties can be of any type, though.
  • Currently, there's no support for computed values. (May be added later)
  • No time-travel debugging.

How do I install it?

$ npm install tstate

How do I use it?

First of all you create your store.

newsStore.ts

import { createStore, createReactHook } from './createStore.ts'

// STEP 1: create interfaces that describe your store
interface News {
  title: string
  body: string
}

interface NewsStore {
  items: News[]
  loading: boolean
  error: boolean
}

// STEP 2: create the initial state
// The state MUST be an object. Simple values like
// numbers or strings but also arrays are not supported.
// Wrapping the state object in a function is recommended
// if you want to reuse the initial state later,
// like in a 'reset' mutation.
function getInitialState (): NewsStore {
  return {
    items: [],
    loading: false,
    error: false
  }
}

// STEP 3: create the store
const newsStore = createStore(getInitialState())

// STEP 4A (React): create the React hook and export it
export const useNewsStore = createReactHook(newsStore)

// STEP 4B (No React): subscribe to the store
// You will receive the latest state immediately after subscribing.
const unsubscribeNewsStore = newsStore.subscribe((newState) => {
  doSomethingWith(newState)
})

// Unsubscribe if you're finished with your business.
// Useful for component frameworks like React.
// This is what the built-in React hook actually does.
unsubscribeNewsStore()

// You can also access the current state. This returns the
// store object. In this case it's of type `NewsStore`.
newsStore.state

// STEP 5: write some (maybe async) functions to change your store
// Pro Tip: You can put all calls to `newsStore.set()` in separate
// ("mutation") functions to avoid repeating the entire state object.
export async function loadNews (): Promise<void> {
  newsStore.set({ loading: true, error: false })
  
  try {
    const result = axios.get('/news')
    newsStore.set({ items: result.data, loading: false, error: false })
  } catch {
    newsStore.set({ loading: false, error: true })
  }
}

Now you can use this hook and the functions in your components

NewsList.tsx

import React, { FC } from 'react'
import { useNewsStore, loadNews } from '../stores/newsStore'

export const NewsList: FC = () => {
  // Destructure the top-level props from the store.
  // The fun part is: The types get inferred all the through,
  // starting at the interface "NewsStore".
  // The type of `items` is `News[]`
  // The type of `items[0].title` is `string`.
  const { items, loading, error } = useNewsStore()
  
  useEffect(() => {
    // Fetch news from the server when the component mounts.
    // Just call the function directly. No fancy stuff needed.
    loadNews()
  }, [])
  
  function handleRefreshClick () {
    // If the user clicks the "Refresh" button, load the news again.
    loadNews()
  }

  return (
    <div className="news-list">
      <button onClick={handleRefreshClick}>Refresh</button>
    
      {loading ? <div>Loading...</div> : null}
      {error ? <div>An error occured.</div> : null}
      {items.length === 0 ? <div>There are no news.</div> : null}
      
      {items.map(item => (
        <article>
          <h2>{item.title}</h2>
          <p>{item.body}</p>
        </article>
      ))}
    </div>
  )
}

That's all there is to it!