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

react-wisteria

v8.4.0-rc

Published

React Functional Context

Downloads

34

Readme

React Wisteria

react-wisteria is a library that utilize the Golden Path Functional setter API into a multiple state stores that makes it easier to work with state. Get your state and update it without the need for selectors, actions, reducers, types, etc.

Wisteria

Wisteria is a genus of flowering plants in the legume family, Fabaceae, that includes ten species of woody climbing bines that are native to China.

image

Installation

npm i react-wisteria --save

Usage

Let's build a counter application

Apr-23-2020 11-48-06

This application has 3 components (Counter, Display and Controls) with <Display/> and <Controls/> being the children of <Counter/> (the Root component of our app).

First we wrap our Root component (<Counter/>) like this:

import { useCreateStore, StoreProvider } from 'react-wisteria';

const Counter = (props) => {
    // Here we create our global store. Wisteria goes with a "store per feature" way of doing things
    // because of that we've stores in the naming but one can create a single store and pass it.
    // All the stores creation should be in the higher level component (aka <App/>).
    const stores = useCreateStores([{ name: 'my-store', initialState: props }]);

    return (
        // Here we pass the store to the `StoreProvider`
        <StoreProvider stores={stores}>
            <div className="counter">
                <Display/>
                <Controls/>
            </div>
        </Provider>
    );
};

export default Counter;

As you can see, the context is being sent to Provider as part of its options.

Let us now render our Root component with its initial props (only the current counter value):

export const CounterAt_0 = () => {
    return (<Counter count={0}/>);
};

The <Display/> component, which is a child of Root, can now reach the state easily.

import { connect, useWisteriaStore, useWisteriaStateSlice } from 'react-wisteria';

const Display = ({ count }) => {
    return (
        <div className="display">
            {count}
        </div>
    )
};

const useStateToProps = () => {
    const myStore = useWisteriaStore('my-store'); // Worth using constants so you can track dependencies between stores easily.

    // Passing only one param will get the whole context but it's not preferred due to performance issues.
    // Also you can do a deep access to properties (e.g. count.value.a) - The component will render only if this deep path has changed.
    const count = useWisteriaStateSlice(myStore, 'count');

    return {
        count
    }
};

export default connect(useStateToProps)(Display);

In our second child, <Controls/>, we want to update the state:

import { connect, useWisteriaStore, useWisteriaStateUpdater } from 'react-wisteria';

const Controls = ({ onAddition, onDecrement }) => {
    return (
        <div className="controls">
            <div className="control" onClick={onAddition}>+</div>
            <div className="control" onClick={onDecrement}>-</div>
        </div>
    )
};

const useStateToProps = () => {
    const myStore = useWisteriaStore('my-store');
    const setContext = useWisteriaStateUpdater(myStore);

    const onAddition = () => {
        setContext('count', (count) => count + 1);
    };

    const onDecrement = () => {
        setContext('count', (count) => count - 1);
    };

    return {
        onAddition,
        onDecrement
    };
}

export default connect(useStateToProps)(Controls);

Please note: Multiple setContext calls will be batched based on React.setState batching whilst having only one render phase. Still you can use setContext.batchUpdates in order to queue multiple updates like this:

setContext.batchUpdates([
    ['firstPath', 'x'],
    ['secondPath', 'y']
]);

We've another hook that can perform batch updates on multiple stores as well and it's called useWisteriaBatchUpdater and we can use it as so:

import { useWisteriaBatchUpdater } from 'react-wisteria';

const useStateToProps = () => {
    const batchUpdater = useWisteriaBatchUpdater();

    const onClick = () => {
        batchUpdater([
            ['first-store', 'count', 100],
            ['second-store', 'count', 200]
        ]);
    };

    return {
        onClick
    };
}

Also, setContext is using golden-path syntax under the hood which means that you can update nested paths and arrays easily.

It's also important to remember to wrap dynamic data in the v function from golden-path in case it might have special tokens that might break the Golden Path parser.

import { v } from 'golden-path';

setContext('name', 'newName');
setContext(`peoples.0.id`, 12);
setContext(`peoples[id=1].name`, 'person name with id=1');
setContext(`peoples[name="Alex"]`, { name: 'Alex2' });

// For dynamic data it's recommended to wrap with v() in order to not break the parser.
// Same are relevant to decimal point as well (e.g. 2.2)
const nameFromServer = '[]&4%45.';
setContext(`peoples[name="${v(nameFromServer)}"].age`, 20);

// Greedy path to update all males
setContext(`peoples*[sex="male"].isMale`, true);

// Multiple condition - Not greedy (first match)
setContext(`peoples[id=1][age>=20]`, {});

// Multiple condition - Greedy (All matches)
setContext(`peoples*[id=1][age>=20].kind`, true);

Up until now, we can see that the Context we passed into the options of the library has wrapped the values of the Root props and we can inspect the state by extracting { context } and update it by extracting { setContext }.

(We can even compare objects by referential equality (===) since updates break the reference in the changed object up to the upper parent reference, so we can distinguish changes in each level without having to do expensive diffing.)

effects Option

There are two other major principles of react-wisteria - the handling of effects and derived states.

Let's say that we have an effect (General browsers API related stuff) that pops an alert message (or triggers a service request) if a specific condition is met (e.g. once the counter hits 10). In order to do this, we need access to context and setContext in our effects which allows us to inspect and respond with updates:

import React from 'react';

const useRequestReportOnTen = () => {
    const myStore = useWisteriaStore('my-store');
    const count = useWisteriaStateSlice(myStore, 'count');

    React.useEffect(() => {
        if (count !== 10) { return; }

        alert('Got ten and sending to the backend!');
    }, [count]);
};

export default useRequestReportOnTen;

First we define our hook - then we inject it into our options:

import { StoreProvider, useCreateStores } from 'react-wisteria';

const Counter = (props) => {
    const stores = useCreateStores([{ name: 'my-store', initialState: props, effects: [useRequestReportOnTen] }]);

    return (
        <StoreProvider stores={stores}>
            <div className="counter">
                <Display/>
                <Controls/>
            </div>
        </StoreProvider>
    );
};

export default Counter;

debugging

You can debug and trace your state updates by adding to the url the ?debugWisteria query param, or by setting window.isWisteriaDebugModeForced to true, You can also inspect your stores state last value by using window.ReactWisteriaStores and update them by using window.ReactWisteriaStoresUpdaters.

Doing ?debugWisteria=changed will console only real updates with different value than the current one, with only ?debugWisteria you might see unchanged updates and this might spam your console.

image