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

use-struct

v1.0.5

Published

Manage local and global states with one hook.

Downloads

1

Readme

Manage local and global states with one hook.

Group States, Reducers, Selectors and Callbacks in a Struct (like a Class) and keep your code organized.

Works globaly too (like Redux).

Follow me on instagram! =)

------ Main features: -------

  1. Simple and intuitive syntax (val, get, set, act).
  2. Easy persistence.
  3. Parent and children logic.
  4. Extension and inheritance.
  5. Context API.
  6. Natural source code for JS and React (made with functions and Hooks).
  7. Free of useEffects (side effects doesn't work in SSR).

------ Disclaimer: -----

  • No TypeScript yet (sorry).
  • useStruct is essentially a hook, so it will not work in Next's getInitialProps.
  • Debounce and throttle effects doesn't work in selectors and callbacks too.
  • In Redux a action can dispatch many others actions and modify the state multiple times sequentially. But this is impossible in useStruct. By definition a hook can modify the state only one time when called. Keep it in mind.

Overview:

// yarn add use-struct immer -D

import useStruct from 'use-struct'
import produce from 'immer'


const Calculator = useStruct({
    // ======== state ======== //
    val: {
        greetings: 'Hello World from first struct! :)',
        counter: 0,
    },
            

    // ======== selectors ======== //
    get: ({ val, get }) => [{
        addedCounter:       additive    => val.counter + additive,
        multipliedCounter:  multiplier  => val.counter * multiplier,
    }, [val.counter]],
    

    // ======== reducer ======== //
    set: ({ val, get }) => ({
        counter: payload => produce(val, val => {
            val.counter = payload
        }),
        increment: () => produce(val, val => {
            val.counter++
        }),
        decrement: () => produce(val, val => {
            val.counter--
        }),
    }),


    // ======== callbacks ======== //
    act: ({ val, get, set, act }) => [{
        addBy:    pld => set.add(pld),
        subBy:    pld => set.sub(pld),
        sayHello: () => {
            alert(val.greetings)
        },
    }, [val.counter]],
})

In component:

export default function Component() {

    // like a class
    const Calculator = useStruct({ ...inputs })

    return (
        <div>
            {/* ======== STATE ======== */}
            <h1>Greetings: {Calculator.greetings}</h1>
            <h2>Current counter: {Calculator.counter}</h2>
            
            <br />
            
            {/* ======== SELECTORS ======== */}
            <h3>Added counter by 3: {Calculator.get.addedCounter(3)}</h3><br />
            <h3>Multiplied counter by 2: {Calculator.get.multipliedCounter(2)}</h3><br />
            
            <br />
            
            {/* ======== REDUCER ======== */}
            <button onClick={() => Calculator.set.increment()} >Increment</button><br />
            <button onClick={() => Calculator.set.decrement()} >Decrement</button><br />
            
            <br />
            
            {/* ======== CALLBACKS ======== */}
            <button onClick={() => Calculator.addBy(4)} >Add counter by 4</button><br />
            <button onClick={() => Calculator.subBy(2)} >Sub counter by 2</button><br />

        </div>
    )
}

Examples:

1) Fetching data from an API

// yarn add use-struct immer axios react-json-pretty -D

import useStruct from 'use-struct'
import produce from 'immer'
import axios from 'axios'
import RenderJson from 'react-json-pretty'


export default function Component() {
    
    // ================ structs ================ //
    const ForeignData = useStruct({
        // ======== state ======== //
        val: {
            description: 'Fetch data from API', 
            data: [],
        },

        // ======== reducer ======== //
        set: ({ val }) => ({
            data: pld => produce(val, val => {
                val.data.push(...pld)
            })
        }),
        
        // ======== callbacks ======== //
        act: ({ val, set }) => [{
            fetchData: () => {
                const call = axios.get('https://jsonplaceholder.typicode.com/todos')

                call.then(({ data }) => {
                    set.data(data)
                }).catch(error => {
                    console.error(error)
                })
            },
        }, [val]]
    })

    // ================ render ================ //
    return (
        <div>
            {/* ======== STATE ======== */}
            <h1>Description: {ForeignData.description}</h1>
            <RenderJson data={ForeignData.val} />

            {/* ======== CALLBACKS ======== */}
            <button onClick={() => ForeignData.fetchData()} >Fetch data</button>
        </div>
    )
}

2) ** New recent features: use for hooks and efc for effects. **

Such that constructor of a Class, the useStruct can invoke initial hooks too and provide them by prefix .use. And even more, finally you can put side effects (such that useEffect) for react to changes of state.

Example:

export default function Componen() {

    // ================ structs ================ //
    const Calculator = useStruct({
        // ======== hooks ======== //
        // instance hooks here and call them by prefix .use 
        use() {
            const [counter, setCounter] = useState(100)
    
            return ({ counter, setCounter })
        },
        
        // ======== selectors ======== //
        get: ({ use }) => [{
            multipliedCounter: multiplier => use.counter * multiplier
        }],
        
        // ======== callbacks ======== //
        act: ({ use }) => [{
            increment() {
                use.setCounter(x => x + 1)
            }
        }],

        // ======== effects ======== //
        // put the side effects here
        efc({ use }) {
            // print a log whenever the useState's counter is changed
            useEffect(() => {
                console.log('Counter changed. New value is:', use.counter)
            }, [use.counter])
        }
    })

    // ================ render ================ //
    return (
        <div>
            {/* ======== HOOKS ======== */}
            <h1>Counter: {Calculator.counter}</h1>

            {/* ======== CALLBACKS ======== */}
            <button onClick={() => Calculator.increment()} >increment</button>
        </div>
    )
}

3) Consult more examples in ./models and ./pages folders in the source code.

Clone the repository and test it immediately with yarn dev in http://localhost:3000.

Bonus features:

1) Global structs

The library provides two extras resources:

  • <ScaffoldProvider /> component.
  • And useScaffold() hook.

"Scaffold" is just a alias for "Global Struct". These resoucers are a implementation of the <Context.Provider /> and useContext().

The value provided by <ScaffoldProvider /> is a root instance of the useStruct() hook. This instance can be recovered by any component in the application using useScaffold().

Implements it in 3 steps:

First, in the ./store folder, create a function that return a instance of useStruct() hook.

import useStruct from 'use-struct'

// the root struct
export default function Struct() {
    return useStruct({
        // ======== state ======== //
        val: {
            greetings: 'Hello World! :)'
        },
    })
}

Second. Wrap your application with the <ScaffoldProvider struct={struct} /> passing the function above for struct attribute: See:

import { ScaffoldProvider } from 'use-struct'

// the root struct
import struct from '../store/struct.js'

export default function ContainerApp() {
    return (
        <ScaffoldProvider struct={struct} >
            <RootApp />
        </ScaffoldProvider>
    )
}

Third. Recovery the useStruct() instance in some component with the useScaffold().

import { useScaffold } from 'use-struct'

export default function SomeComponent() {

    // the root struct
    const Struct = useScaffold()

    return (
        <div>
            <h1>Greetings: {Struct.greetings}</h1>
        </div>
    )
}

2) Memorizing and updating Selectors and Callbacks

Such that useMemo and useCallback, selectors get and callbacks act can be memorized too. Just add an array of dependencies after definitions.

const Calculator = useStruct({
    // ======== state ======== //
    val: {
        counter: 123456,
    },
            
    // ======== selectors ======== //
    get: ({ val }) => [{
        calculatedCounter: () => veryVerySlowFoo(val.counter),
    }, [val.counter]], // all selectors will be recalculated whenever counter state changes.
    

    // ======== callbacks ======== //
    act: ({ get }) => [{
        logCalculatedCounter: () => {
            console.log('new value:'. val.greetings)
        },
    }, [get.calculatedCounter]], // all callbacks will be updated whenever that calculatedCounter selector changes.
})

If none dependencies array was added all selectors and callbacks will react at each change of state.

3) Wrapping others structs

A struct can wrap another and all properties and methods of the wrapred structs will are avaliable inside and outside for the wraper struct. See:

const AdditionModule = useStruct({
    // ======== state ======== //
    val: {
        description: 'This is the Addition Module',
        counter: 0,
    },

    // ======== reducer ======== //
    set: ({ val }) => ({
        increment: () => produce(val, val => {
            val.counter++
        }),
    }),
})
const SubtractionModule = useStruct({
    // ======== state ======== //
    val: {
        description: 'This is the Subtraction Module',
        counter: 100,
    },

    // ======== reducer ======== //
    set: ({ val }) => ({
        decrement: () => produce(val, val => {
            val.counter--
        }),
    }),
})

Wrapped structs inside of the wrapper

You can access the wrapped structs by prefix .str.

  • str.AdditionModule.blabla
  • str.SubtractionModule.blabla
const Calculator = useStruct({
    // ======== children ======== //
    str: {
        AdditionModule,
        SubtractionModule,
    },
    
    // ======== state ======== //
    val: {
        description: 'This is the Calculator Struct',
    },

    // ======== selectors ======== //
    get: ({ str }) => [{
        additionCounter: () => str.AdditionModule.counter,
        subtractionCounter: () => str.SubtractionModule.counter,
    }, [str.AdditionModule.val, str.SubtractionModule.val]],
    
    // ======== callbacks ======== //
    act: ({ str }) => [{
        incrementAndDecrementAllCounters: () => {
            str.AdditionModule.set.increment()
            str.SubtractionModule.set.decrement()
        },
    }, [str.AdditionModule.val, str.SubtractionModule.val]]
})

Wrapped structs outside of the wrapper:

You can access the wrapped structs like as child property.

  • Calculator.AdditionModule.blabla
  • Calculator.SubtractionModule.blabla
export default function Component() {

    const SubtractionModule = useStruct({ ...inputs })
    const AdditionModule = useStruct({ ...inputs })
    
    const Calculator = useStruct({ 
        str: {
            AdditionModule,
            SubtractionModule,
        },
        ...inputs
    })

    return (
        <div>
            {/* ======== STATES ======== */}
            <h1>Description: {Calculator.description}</h1>

            <h3>Addition counter: {Calculator.AdditionModule.counter}</h3>
            <h3>Subtraction counter: {Calculator.SubtractionModule.counter}</h3>


            {/* ======== SELECTORS ======== */}
            <h3>Addition counter: {Calculator.get.additionCounter()}</h3>
            <h3>Subtraction counter: {Calculator.get.subtractionCounter()}</h3>


            {/* ======== CALLBACKS ======== */}
            <button onClick={() => Calculator.AdditionModule.set.increment()} >Increment</button>
            <button onClick={() => Calculator.SubtractionModule.set.decrement()} >Decrement</button>

            <button onClick={() => Calculator.incrementAndDecrementAllCounters()} >
                Increment and decrement all counters
            </button>
        </div>
    )
}

4) str, val, get, set, act are as a alias for this

In a Class others properties and methods can be accessed by prefix this. Similarly in a struct sibling properties and methods can be accessed by prefixes str, val, get, set, act.

See this full example:

const Struct = useStruct({
    // ======== children ======== //
    str: {
        ...blabla
    },

    // ======== state ======== //
    val: {
        ...blabla
    },
            
    // ======== getters ======== //
    get: ({ str, val, get }) => [{
        ...blabla
    }],
    
    // ======== reducer ======== //
    set: ({ str, val, get, set }) => ({
        ...blabla
    }),

    // ======== callbacks ======== //
    act: ({ str, val, get, set, act }) => [{
        ...blabla
    }],
})

5) Persistence

The state of each useStruct can be easy persisted in local storage adding a key: 'STRUCT_NAME' and pst: true in the hook input. See:

// persisted struct
const Struct = useStruct({

    key: 'STRUCT_NAME',
    pst: true,

    val: {
        greetings: 'Hello World from localStorage :)',
    },
})

6) Extending others structs

Similarly to a Class a struct can extend many others structs and all their properties and methods will are DIRECTLY available for the extended struct WITHOUT PREFIX .str.

import useStruct from 'use-struct'
import produce from 'immer'

const AdditionModule = useStruct({
    val: {
        addCounter: 0,
    },
    set: ({ val }) => ({
        increment: () => produce(val, val => {
            val.addCounter++
        })
    }),
})

const SubtractionModule = useStruct({
    val: {
        subCounter: 100,
    },
    set: ({ val }) => ({
        decrement: () => produce(val, val => {
            val.subCounter--
        }),
    }),
})

Accessing the extended features by inside

const Calculator = useStruct({ 
    ext: [
        AdditionModule,
        SubtractionModule
    ],
    get: ({ val }) => [{
        diffCounter: () => val.subCounter - val.addCounter
    }],
    act: ({ set }) => [{
        incrementAndDecrementAllCounters: () => {
            set.increment()
            set.decrement()
        },
    }],
})

Accessing the extended features by outside

export default function Component() {

    // ================ structs ================ //
    const SubtractionModule = useStruct({ ...inputs })
    const AdditionModule = useStruct({ ...inputs })
    
    const Calculator = useStruct({ 
        ext: [
            AdditionModule,
            SubtractionModule
        ],
        ...inputs
    })
    
    
    // ================ render ================ //
    return (
        <div>
            {/* ======== STATES ======== */}
            <h1>Addition Module Counter: {Calculator.addCounter}</h1>
            <h1>Subtraction Module Counter: {Calculator.subCounter}</h1>
            
            <h2>Diff between subCounter and addCounter {Calculator.get.diffCounter()}</h2>

            {/* ======== CALLBACKS ======== */}
            <button onClick={() => Calculator.incrementAndDecrementAllCounters()} >
                increment and decrement all counters
            </button>
        </div>
    )
}

As explained, set.increment(), set.decrement(), val.subCounter and val.addCounter are directly invoked by inside. And Calculator.addCounter and Calculator.subCounter are too directly invoked by outside.

In both cases looks that these props and methods was created by Calculator struct, but actually, their was created by extended modules and inherited by Calculator struct.

ATTENTION:

** To extend a struct not means create a deepClone and modify their copy, but means TO CONTROL IT DIRECTLY. All change of state will put definitely in all original states, not in deepClones. **

** In extends operations occurs a merge of props and methods. So be careful for not create nothing with the same name. If a match occurs, the wraper struct will override the features of the extension structs . **

Details:

1) Shortcuts

  • States val, Callbacks act and Wrapped Structs str can be accessed directly by view without the prefixs .val, .act, and .str.
  • But all selectors get and reducer set needs be prefixed (Inside or outside of the hook).

Example with Hello World:

export default function Component() {

    const Calculator = useStruct({ ...inputs })

    return (
        <div>
            {/* ======== STATE ======== */}
            <h1>Greetings (without .val prefix): {Calculator.greetings}</h1>
            <h1>Greetings (with .val prefix): {Calculator.val.greetings}</h1>

            {/* ======== CALLBACKS ======== */}
            <button onClick={() => Calculator.sayHello()} >Say hello (without .act prefix)</button><br />
            <button onClick={() => Calculator.act.sayHello()} >Say hello (with .act prefix)</button><br />
        </div>
    )
}

2) Forbidden keywords

Don't create none props or methods with the following keywords: key, pst, ext, str, val, get, set and act. They are reserved keywords for this hook and if you do that, bad things can to happen.