Skip to content

Instantly share code, notes, and snippets.

@msteckyefantis
Created May 13, 2018 19:21
Show Gist options
  • Save msteckyefantis/dcf296f1f2864e02283cd4509f4eec02 to your computer and use it in GitHub Desktop.
Save msteckyefantis/dcf296f1f2864e02283cd4509f4eec02 to your computer and use it in GitHub Desktop.
upcoming ReduxX

ReduxX

npm version Build Status

ReduxX

Similar to SpaceX and iPhoneX, ReduxX is the next generation React state management tool.

(with 100% code coverageπŸ˜‰πŸ‘πŸΏπŸ‘πŸ½πŸ‘πŸ»)

ReduxX is a lightweight yet super-powerful, very easy to learn, and very easy to set up React state management library.

Table of Contents:

Why Use ReduxX:

In larger React apps, it's really nice to have global state (global as in globally accessible, NOT stored as a global browser variable). But when normally dealing with a global state object, you may encounter a problem, your state may look something like this:

...
accountSettingsEmailVerified: true
user: 'dandan69'
userProfileFirstName: 'Danny'
userProfileMainPicture: null
userProfileMainPictureIsPublic: true
...

And when getting and setting the global state, it looks something like this:

// the old way: getting value from the global state
const mainPicture = (

    globalStateStorageInstance.state.userProfileMainPicture
);

// the old way: setting a value in the global state
globalStateStorageInstance.setState({

    userProfileMainPicture: 'https://image.png'
});

Alternatively, you can use nested React state to have nicer variable names to deal with. The problem with that is it gets unnecessarily confusing and messy looking:

/*
  the old way: updating a nested component

    in this example, assume the state key "pictures"
    has an associated value which is an
    object with several properties


    Below is how you would update
    a single property of the pictures object
    while preserving the other properties
*/
globalStateStorageInstance.setState( previousState => {

    return {

        pictures: Object.assign(

            {},

            previousState.pictures,

            {
                mainPicture: 'https://image.png'
            }
        )
    };
});

πŸ™ˆEwww, that's not very nice.😰

Don't fear, ReduxX is here to save the day!πŸ‰πŸ¬πŸ™

with ReduxX, the same state as above is automatically generated and it will look something like this:

...
accountSettings-EmailVerified: true
user: 'dandan69'
user-profile-firstName: 'Danny'
user-profile-mainPicture: null
user-profile-mainPicture-isPublic: true
...

and to get and set the state, you just need to do this:

// cleanly get a global state value with ReduxX:

const mainPicture = reduxX.getState( 'user', 'profile', 'mainPicture' );


// smoothly set a global state value with ReduxX:

reduxX.setState( [ 'user', 'profile', 'mainPicture' ], 'https://image.png' );

Notes:

a) with ReduxX, you can use any number of keys

b) Check out the Alternate Input Formats for the ReduxX setState and getState Functions section for other ways to use the getState and setState functions

Why is ReduxX better than the other old fashioned React state management libraries?

ReduxX is incredibly simple to install and learn. Everything you need to know is contained in this README.md file. For ReduxX you only need to follow the 3 simple steps in the How ReduxX Works Section below! Essentially, you only need to be concerned with two functions: reduxX.getState, and reduxX.setState. These functions work exactly like normal React state, except you can use them anywhere in your code to access a global state, wow!


How ReduxX works:

Step 1: Install ReduxX and Set Your Initial Global State

To install ReduxX, input the following npm command:

npm install reduxx --save

In the directory of your main React component, the most parent component that contains all your other components, add the following reduxx.js file:

'use strict';

const ReduxX = require( 'reduxx' );


module.exports = ReduxX({

    initialState: [

        {
            keys: [ 'monkey', 'favoriteFood' ],

            value: 'banana',
        },
        {
            // can put a string for the "keys" for single key values
            keys: 'monkey',

            // the value can be of any type,
            // just like using regular React state
            value: { name: 'Curious George', bff: 'Donkey Kong' },
        },
        {
            keys: [ 'monkey', 'height' ],

            value: '69cm',
        },
        {
            keys: [ 'hippo', 'status', 'mood' ],

            value: 'hungry',
        }
    ]    
});

Notes:

a) you can use any number of keys

b) technically you can put this reduxx.js file anywhere, but it makes the most sense to put it in your root folder based on how you access it (in Step 3)

c) you can alternatively use the Object Form for Initial State

Step 2: Set up ReduxX

In the most parent component itself, the component that contains all your other components, set up the ReduxX global state like the following:

'use strict';

const React = require( 'react' );

// Step 2: a) Import the following setupReduxX function
//            from the file you created in Step 1.
// Note: this particular path assumes the reduxx.js file
//       is in the same directory as this file
const { setupReduxX } = require( './reduxx.js' );


// Your "most parent" component
module.exports = class App extends React.Component {

    constructor( props ) {

        super( props );

        // Step 2: b) Add the setupReduxX function here,
        //            pass in "this" as the first argument
        setupReduxX( this );
    }

    render() {

        ...
    }
}

Step 3: Easily Get and Set Values to the Global State

Now anywhere you normally do a React setState, you can now setState with ReduxX to access a global state and never have to worry about collisions, so exciting!:

'use strict';

// some other module

const React = require( 'react' );

const reduxX = require( /*path to reduxx.js file, the file created in Step 1*/ );


function handleClick() {

    /*
       ReduxX Effortless State Setting and Getting
    */

    // set the global state for one or more items like this:

    reduxX.setState(

        {
            keys: [ 'monkey', 'favoriteFood' ],

            value: 'apple',
        },
        {
            keys: [ 'hippo', 'status', 'mood' ],

            value: 'full'
        }
    );

    // get the global state for an item like this:

    const monkeyHeight = reduxX.getState( 'monkey', 'height' );

    console.log(

        `The reduxX monkey is ${ monkeyHeight } tall!`
    );

    // should log: The reduxX monkey is 69cm tall!
}


module.exports = class SomeDiv extends React.Component {

    render() {

        return <div onClick={ handleClick } />;
    }
}

This example also includes a reduxX.getState invocation. You can use this function anywhere and this function gets the requested value from the global state! Extreme #swaggyπŸΈπŸŽ…πŸΏπŸ‘³πŸ½πŸ‰!



Wow that's it, so easy!

All you need to do is require your ./reduxx.js file you created in Step 1 and then use reduxX.setState and reduxX.getState to manage your global state (like in Step 3).

ReduxX Rocks!


Bonus Features

Alternate Input Formats for the ReduxX setState and getState Functions

For your convenience, and for better code readability, the ReduxX setState and getState functions offer several ways to set and get values to and from the global state.

'use strict';

const {

    getState,
    setState,

} = require( /*path to reduxx.js file, the file created in Step 1*/ );

/*
   Different input formats for reduxX.getState
*/

//  The following four getState invocations are equivalent:
getState( 'user' );
getState( [ 'user' ] );
getState({ keys: 'user' });
getState({ keys: [ 'user' ] });


//  The following three getState invocations are equivalent:
getState( 'user', 'profile' );
getState( [ 'user', 'profile' ] );
getState({ keys: [ 'user', 'profile' ] });


/*
   Different input formats for reduxX.setState
*/

//  The following four setState invocations are equivalent:
setState( 'user', { id: 69 } );
setState( [ 'user' ], { id: 69 } );
setState({ keys: 'user', value: { id: 69 } });
setState({ keys [ 'user' ], value: { id: 69 } });

//  The following two setState invocations are equivalent:
//  (these invocations involve setting multiple values at once)
setState(

    [ 'user', 'name' ], 'Danny',
    'user', { id: 69 },
    [ 'user', 'game' ], 'React state library author'
);
setState(

    {
        keys: [ 'user', 'name' ],  
        value: 'Danny'
    },
    {
        keys: [ 'user' ],  
        value: { id: 69 }
    },
    {
        keys: [ 'user', 'game' ],  
        value: 'React state library author'
    }
);

Obscure Your State Keys

Recalling our state from before:

...
accountSettings-EmailVerified: true
user: 'dandan69'
user-profile-firstName: 'Danny'
user-profile-mainPicture: null
user-profile-mainPicture-isPublic: true
...

You can obscure your state keys by automatically turning them into GUIDs so that your state will look something like this:

...
206ca40e-e643-17b6-da3c-616b90800f4e: true
335a8849-9bff-bd2c-9a1a-6f26d61e0f88: 'dandan69'
ae771d1a-f563-c876-3132-b958e62ca205: 'Danny'
1721716f-4d92-ea42-56ca-5df95c175413: null
e04659d7-38a6-bae8-b513-36cfcb300ba7: true
...

To obscure your keys, simply set up your ReduxX (Step 1), like this:

'use strict';

const ReduxX = require( 'reduxx' );


module.exports = ReduxX({

    // adding this will obscure your state keys
    obscureStateKeys: true,

    initialState: [

        ... // the same initial state objects go here
    ],    
});

Object Form for Initial State

In Step 1, we set up our initial state with an array of objects that each have a keys and a value property. We can alternatively set up our initial state with an object. This can be more suitable for ReduxX apps with a larger number of state keys because it easily allows you to organize your initial state in a modular way (it easily allows you to put sections of your initial state in different files if you want).

The below code shows you how to use the Object Form for Initial State to set the same initial state as in Step 1.

'use strict';

const ReduxX = require( 'reduxx' );

// use the ReduxX VALUE key to set the initial state value for the specified key
const { VALUE } = ReduxX;


module.exports = ReduxX({

    initialState: {

        monkey: {

            [VALUE]: { name: 'Curious George', bff: 'Donkey Kong' },

            height: {

                [VALUE]: 'banana'
            },

            height: {

                [VALUE]: '69cm'
            }
        },

        hippo: {

            status: {

                mood: {

                    [VALUE]: 'hungry'
                }
            }
        }
    }
});

Old Fashioned State Management

You can also access and alter the global state manually. Use reduxX.getStore() to get the global state storage instance (the store) with its state. The store is just a normal React Component instance that has a normal React state.

'use strict';

// some other module

const React = require( 'react' );

const reduxX = require( /*path to reduxx.js file, the file created in Step 1*/ );


function handleClick() {

    /*
       Old Fashioned State Setting and Getting:
    */

    const store = reduxX.getStore();

    // You can get and set regular React state keys
    // in the global state storage component instance:

    store.setState({ hello: 'world' });

    setTimeout( () => {

        const world = store.state.hello;

        console.log( 'hello:', world, 'πŸŒπŸ™πŸΌπŸ‘½πŸ²πŸŒ' );

        // should log: hello: world πŸŒπŸ™πŸΌπŸ‘½πŸ²πŸŒ

    }, 0 );

    /*
        Using the stateKeyMapper and the KEY,
        you can work with regular ReduxX type keys
        OR obscured state keys.
        See the "Obscure Your State Keys" section above
        for more information about that topic.
    */

    const { stateKeyMapper, KEY } = reduxX;

    // setting the state:
    // this will produce the same state change as in Step 3

    const newState = {};

    const monkeyFavFoodKey = stateKeyMapper.monkey.favoriteFood[ KEY ];

    newState[ monkeyFavFoodKey ] = 'apple';

    const hippoMoodKey = stateKeyMapper.hippo.status.mood[ KEY ];

    newState[ hippoMoodKey ] = 'full';

    store.setState( newState );

    // getting the state:
    // once again it's the same as getting the state like in Step 3

    const monkeyHeightKey = stateKeyMapper.monkey.height[ KEY ];

    const monkeyHeight = store.state[ monkeyHeightKey ];

    console.log( `The reduxX monkey is ${ monkeyHeight } tall!` );

    // should log: The reduxX monkey is 69cm tall!
}

Optimize Your ReduxX App

One of ReduxX's principles is to be very easy to learn and to work in a similar way to React's regular state. As a result, optimizing your web app that uses ReduxX is similar to optimizing any other React app.

One thing to note is having a global state in the most parent component means that all the children of that parent component can re-render upon any state change. This re-rendering occurs even if those children don't need to be changed themselves due to a state change (the component will render the same element but it won't change the DOM because the newly rendered element was the same as the last). This results in "wasted" renders. As noted in the Official React Optimization Documentation, these "wasted" renders in many cases don't affect performance.

In the case where you would like to optimize your ReduxX app, here is a way to optimize your components to avoid unnecessary re-rendering:

Example: Optimizing a "p" Based Component that Displays Text

Suppose you have a "p" (html paragraph) based component that displays text. Next suppose that text is based on the global state value whose key is pText and you only want that "p" component to re-render when pText changes.

First of all, here is what our original unoptimized "p" component looks like, let's call it the MegaPComponent:

'use strict';

const React = require( 'react' );

const { getStore } = require( /* path to reduxx.js file created in Step 1 */ );


module.exports = class MegaPComponent extends React.Component {

    render() {

        const store = getStore();

        const { pText } = store.state;

        return <p> { pText } </p>;
    }
}

Here is what you do, put that "p" component in a div container, lets call that container Container:

'use strict';

const React = require( 'react' );

const MegaPComponent = require( '...path to MegaPComponent' );

const { getStore } = require( /* path to reduxx.js file created in Step 1 */ );


module.exports = class Container extends React.Component {

    render() {

        const store = getStore();

        const { backgroundColor } = store.state;

        const divStyle = { backgroundColor };

        return(
            <div style={ divStyle }>

                <MegaPComponent/>

            </div>
        );
    }
}

This is still unoptimized, the MegaPComponent will do a no-op render (a "wasted" render) if the backgroundColor state value or any other global state value changes.

To avoid this, what you need to do is set the pText as a prop of the MegaPComponent, and extend the MegaPComponent from the React.PureComponent class like this:

'use strict';

const React = require( 'react' );


module.exports = class MegaPComponent extends React.PureComponent {

    render() {

        const { pText } = this.props;

        return <p> { pText } </p>;
    }
}

and change your Container to be like this:

'use strict';

const React = require( 'react' );

const MegaPComponent = require( '...path to MegaPComponent' );

const { getStore } = require( /* path to reduxx.js file created in Step 1 */ );


module.exports = class Container extends React.Component {

    render() {

        const store = getStore();

        const { backgroundColor, pText } = store.state;

        const divStyle = { backgroundColor };

        return(
            <div style={ divStyle }>

                <MegaPComponent pText={ pText }/>

            </div>
        );
    }
}

In the MegaPComponent, by setting the pText value as a prop, and by making the MegaPComponent extend from the React.PureComponent class, it will now only do a render when the pText value changes. You can apply this optimization technique to any component where you want to avoid "wasted" renders. In some cases, you may need to adjust the shouldComponentUpdate React component method in order to achieve the same effect because React PureComponents only do a shallow comparison of the previous and next props and state. Here is a more detailed explanation of how this works from the Official React Documentation on Pure Components.

Examples of ReduxX Usage


Check out LessonShop.net to take or teach coding lessons! Promote your personal brand as a developer or as a developer teacher for free!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment