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

globstate

v1.1.8

Published

Simple and beginner friendly global state management for React JS and preact.

Downloads

6

Readme

GlobState

Super simple global state management for React JS and preact.

Managing global state in React/preact can be hard and the Redux library is overkill for many applications and can result in overly verbose and complicated code when very simple and easy to understand mechanisms often do just as fine.

Installation

npm i --save globstate

or

yarn add globstate

How to use

We will use the example of a site wide search field here. You have two steps:

.1. Construct a class for the state with methods to modify it, and export an object of that class from the file:

// globals/search.js
import GlobState from 'globstate'
 
class Search extends GlobState {
    _results = null

    constructor() {
        super('search')
    }
    results() {
        return this._results
    }
    doSearch(text) {
        fetch('https://example.org/api/search?q='+text).then(r => r.json()).then(json => {
            this._results = JSON.parse(json)
            this.updateComponents() // This triggers a re-render on all connected components
        })
    }
}
 
// Note that we export an object of type Search, not the class itself
export default new Search()

.2. Create a component that uses that global object and gets updated each time it changes:

// SearchResults.jsx
import GlobState from 'globstate'
import search from './globals/search'
 
class SearchResults extends Component {
    render() {
        // Note that in the render function you can access the global `search` object directly
        return (
            <ul>
                {search.results().map(result => (
                <li>{result.title}</li>
                ))}
            </ul>
        )
    }
}
 
// Export a connected version of the component that will update every time the search results change
export default GlobState.connect(SearchResults, search)
// SearchField.jsx
import search from './globals/search'
 
class SearchField extends Component {
    constructor() {
        super()
        this.state = {
            text: ''
        }
    }
    render() {
        return (
            <form onSubmit={e => {e.preventDefault(); this.submit(); return false}}>
                <input type="text" value={text} onInput={e => this.setState({text: e.target.value})}/>
                <button type="submit">go</button>
            </form>
        )
    }
    submit() {
        search.doSearch(this.state.text)
    }
}
// App.jsx
import GlobState from 'globstate'
import search from './globals/search'
import SearchArea from './SearchArea'
import SearchField from './SearchField'

class App extends Component {
    render() {
        <div>
            {search.results!==null && (
                <p>Info: There are {search.results().length} results  <p/>
            )}
            <SearchField/>
            <SearchArea/>
        </div>
    }
}
App = GlobState.connect(App, search)

That's it! You can use several global objects in the same component without problems and also use asynchronous methods (like setTimeout or API calls) to update global objects.

How it works

Global state is actually really simple. To make it work you need to have some global object that

  1. can be read and modified from everywhere in your web app
  2. updates the components that use it when it changes

The first part is easy, just make an object that has variables and functions, put it in a file and wherever you need it, just import/require the file. In Javascript every file itself is loaded exactly once and thus is a global object already:

// myGlobalCounter.js
class MyGlobalCounter {
    _value = 0

    read() {
        return this._value
    }
    increment() {
        this._value += 1
    }
}
const myGlobalCounter = new MyGlobalCounter()
export default myGlobalCounter

So how to update all necessary components when the counter gets incremented? Components re-draw themselves whenever their state changes, their props change or if forceUpdate() is called. We will use the latter. And some subscription system is needed as well:

// myGlobalCounter.js
class MyGlobalCounter {
    _value = 0
    _subscribers = []

    subscribe(component) {
        this._subscribers.push(component)
    }
    unsubscribe(component) {
        const index = this._subscribers.indexOf(component)
        if (index !== -1)  this._subscribers.splice(index, 1)
    }
    updateComponents() {
        for (const subscriber of this._subscribers) {
            subscriber.forceUpdate()
        }
    }
    read() {
        return this._value
    }
    increment() {
        this._value += 1
        this.updateComponents()
    }
}
const myGlobalCounter = new MyGlobalCounter()
export default myGlobalCounter
// CounterDisplay.jsx
import counter from './MyGlobalCounter'

class CounterDisplay extends Component {
    componentWillMount() {
        counter.subscribe(this)
    }
    componentDidUnmount() {
        counter.unsubscribe(this)
    }
    render() {
        return (
            <div>
                Counter: {counter.read()}
                <span onClick={e => counter.increment()}>+</span>
            </div>
        )
    }
}

Now you can use the component in multiple places in your web app and they are all connected to each other through the myGlobalCounter variable. Whenever it changes it calls its updateComponents method which will call forceUpdate on all subscribed compnents. This is by the way exactly how React Redux does (or, at least, did) it as well!

The subscription management should go into its separate class of course. And to shorten all the subscribing/unsubscribing you can create a simple High Order Component (HOC) that takes care of that:

// Updater.js
class Updater {
    _subscribers = []

    subscribe(component) {
        this._subscribers.push(component)
    }
    unsubscribe(component) {
        const index = this._subscribers.indexOf(component)
        if (index !== -1)  this._subscribers.splice(index, 1)
    }
    updateComponents() {
        for (const subscriber of this._subscribers) {
            subscriber.forceUpdate()
        }
    }
    static connect(BaseComponent, ...globals) {
        return class extends Component {
            componentWillMount() {
                for (const g of globals) {
                    g.subscribe(component)
                }
            }
            componentWillUnmount() {
                for (const g of globals) {
                    global.unsubscribe(component)
                }
            }
            render() {
                return <BaseComponent {...this.props}/>
            }
        }
    }
}

And Updater here is basically GlobState. See, global state management can be that simple!

Examples

Counter

The usual example, a counter:

// globals/counter.js
const GlobState = require('globstate')

class Counter extends GlobState {
    _value = 0

    read() {
        return this._value
    }
    increment() {
        this._value += 1
        this.updateComponents()  // This will update all subscribed components
    }
}

export default new Counter()
// Counter.jsx
import counter from './globals/counter'

class Counter extends Component {
    render() {
        return (
            <div>
                Counter: {counter.read()}
                <span onClick={e => counter.increment()}>+</span>
            </div>
        )
    }
}

export default GlobState.connect(Counter, counter)

User management

Many web apps have a mechanism to login, register and logout a user. The user then of course is a global state and can be managed with the GlobState library. This example includes async state changes as login/register/logout are done via API calls.

// globals/user.js
const GlobState = require('globstate')

class User extends GlobState {
    loggedin = false
    username = false

    constructor() {
        super()
    }

    login(email, password, next) {
        setTimeout(() => {  // Simulate an api call..
            if (email==='[email protected]' && password==='123') {
                this.loggedin = true
                this.username = 'Michel'
                next(null) // tell caller that we finished
            } else {
                this.loggedin = false
                next(new Error('Wrong credentials!'))
            }
            this.updateComponents()
        }, 500)
    }
}

export default new User()
// App.jsx
import user from './globals/user'

// Just some component that uses the global user object
class SomeComponent extends Component {
    render() {
        return (
            <div>
                {user.loggedin? (
                    <span>Logged in!</span>
                ) : (
                    <span>Not logged in!</span>
                )}
            </div>
        )
    }
}
SomeComponent = GlobState.connect(SomeComponent, user)

class Header extends Component {
    render() {
        return (
            <header>
                {user.loggedin? (
                <span>Hello {user.username}!</span>
                ) : (
                <button type="button" onClick={e => this.tryLogin()}>login</button>
                )}
            </header>
        )
    }
    tryLogin() {
        const email = prompt('E-Mail')
        const password = prompt('Password')
        user.login(username, password, (err) => {
            if (err) alert(err.message)
        })
    }
}
Header = GlobState.connect(Header, user)

function App() {
    return (
        <div>
            <Header/>
            <main>
                <SomeComponent/>
            </main>
        </div>
    )
}

License

MIT