1.0.26 • Published 6 months ago

react-state-monad v1.0.26

Weekly downloads
-
License
GPL-3.0
Repository
-
Last release
6 months ago

React State Monad

npm Build Status License

A set of hooks to manage/transform/filter states with monads in React.

Description

react-state-monad provides a set of monadic state management utilities, specifically designed to work seamlessly with React's state hooks. It allows you to manage, transform, and filter states in a functional and declarative way using monads like Maybe and Option.

This library leverages the power of monads to encapsulate state changes, enabling a cleaner, more predictable way to handle various state conditions in React.

Features

  • Manage state using monads like Maybe and Option.
  • Simplify handling of undefined or null values in state.
  • Leverage functional programming patterns in React state management.
  • Support for TypeScript with full type definitions.

Installation

You can install react-state-monad using npm or yarn:

npm install react-state-monad

or

yarn add react-state-monad

Usage

Here's an example of how you can use the hooks in your React components:

useStateObject<T>

This hook initializes a StateObject with the provided initial value. It uses React's useState hook to manage the internal state and returns a StateObject that represents the current state.

Parameters: initialState: The initial value of the state. This value can be of any type, determined by the generic type T.

Returns a StateObject of type T representing the initialized state. This StateObject is an instance of ValidState, which encapsulates the state value and provides a setValue function to update it.

useEmptyState<T>

This hook initializes a StateObject with an empty state. It is useful as a fallback when no valid state is available.

Parameters: None.

Returns a StateObject of type T representing an empty state. This StateObject is an instance of EmptyState, which encapsulates the absence of a state value.

useElementState<T>

This hook allows you to derive and update a specific element in an array within a StateObject. It is useful for managing state at a granular level within an array.

Parameters:

  • state: The StateObject containing an array.
  • index: The index of the element to be derived.

Returns a StateObject of type T representing the element at the given index. If the index is out of bounds or the state is empty, it returns an instance of EmptyState. Otherwise, it returns an instance of ValidState, which encapsulates the element value and provides a setValue function to update it.

useFieldState<TOriginal, TField>

This hook derives a field from the state object and creates a new StateObject for the field's value. It is useful for managing state at a granular level within an object.

Parameters:

  • state: The StateObject containing the original state.
  • field: The field name to be derived from the state.

Returns a StateObject of type TField representing the derived field. This StateObject is an instance of ValidState, which encapsulates the field value and provides a setValue function to update it.

For example:

import React from 'react';
import {useStateObject, useFieldState} from 'react-state-monad';

const MyComponent = () => {
    const userState = useStateObject({
        name: 'John Doe',
        age: 30,
    });

    const nameState = useFieldState(userState, 'name');
    const ageState = useFieldState(userState, 'age');

    return (
        <div>
            <input
                type="text"
                value={nameState.value}
                onChange={(e) => nameState.value = e.target.value}
            />
            <input
                type="number"
                value={ageState.value}
                onChange={(e) => ageState.value = parseInt(e.target.value, 10)}
            />
        </div>
    );
};

export default MyComponent;

useRemapArray<T>

This hook maps each element in an array within a StateObject to a new StateObject, allowing for independent updates of each element while keeping the overall array state synchronized.

Parameters:

  • state: The StateObject containing an array.

Returns an array of new StateObjects, each representing an element in the original array. This allows individual updates while keeping the array state synchronized. If the state has no value, it returns an empty array.

useNullSafety<TOrigin>

This hook ensures a StateObject contains a defined, non-null value. If the StateObject's value is undefined or null, it returns an EmptyState. Otherwise, it returns a ValidState with the value and a setter to update the value.

Parameters:

  • state: The StateObject which may contain a value, undefined, or null.

Returns a StateObject of type TOrigin representing the value if it is defined and non-null, otherwise an EmptyState.

useRemapKeysState<TOriginal, TField>

This hook remaps the keys of a state object to a record of StateObjects, allowing for independent updates of each key while keeping the overall object state synchronized.

Parameters:

  • state: The StateObject containing the original state.

Returns a record where each key is mapped to a new StateObject representing the value of that key, allowing individual updates while keeping the object state synchronized. If the state has no value or is an array, it returns an empty object.

Complete Example

Here's a more complete example demonstrating the usage of useStateObject, useFieldState, useElementState, and useRemapArray hooks in a React component:

const AgeField = (props: { ageState: StateObject<number> }) => <div>
    <label>Age:</label>
    <input
        type="number"
        value={props.ageState.value}
        onChange={x => props.ageState.value = parseInt(x.target.value, 10)}
    />
</div>;


const NameField = (props: { nameState: StateObject<string> }) => {
    return <div>
        <label>Name:</label>
        <input
            type="text"
            value={props.nameState.value}
            onChange={x => props.nameState.value = x.target.value}
        />
    </div>;
}

const HobbyField = (props: { hobbyState: StateObject<string> }) => {
    return <div>
        <input
            type="text"
            value={props.hobbyState.value}
            onChange={x => props.hobbyState.value = x.target.value}
        />
    </div>;
}

const HobbiesField = (props: { hobbiesState: StateObject<string[]> }) => {

    const hobbyStates: StateObject<string>[] = useRemapArray(props.hobbiesState);

    const addHobby = () => {
        // Always use the setter to update arrays, do not modify them directly to ensure React state consistency.
        // Immutability is key 💗
        props.hobbiesState.value = [...props.hobbiesState.value, ''];
    }

    return <div>
        <label>Hobbies:</label>
        {
            hobbyStates.map((hobbyState, index) => <HobbyField key={index} hobbyState={hobbyState}/>)
        }
        <button onClick={addHobby}>Add Hobby</button>
    </div>;


};

export const UserProfile = () => {

    type DudeData = {
        name: string;
        age: number;
        hobbies: string[];
    }
    // Initialize state with an object containing user details and an array of hobbies
    const userState: StateObject<DudeData> = useStateObject({
        name: 'John Doe',
        age: 30,
        hobbies: ['Reading', 'Traveling', 'Cooking'],
    });

    // Derive state for individual fields
    const nameState: StateObject<string> = useFieldState(userState, 'name');
    const ageState: StateObject<number> = useFieldState(userState, 'age');

    // Derive state for hobbies array
    const hobbiesState: StateObject<string[]> = useFieldState(userState, 'hobbies');


    return (
        <div>
            <h1>User Profile</h1>
            <NameField nameState={nameState}/>
            <AgeField ageState={ageState}/>
            <HobbiesField hobbiesState={hobbiesState}/>
        </div>
    );
};

Contributing

Contributions are welcome! If you'd like to contribute to this library, please fork the repository and submit a pull request.

How to Contribute Fork the repository.

  • Create a new branch for your feature git checkout -b feature-name
  • Commit your changes git commit -am 'Add new feature'
  • Push to the branch git push origin feature-name
  • Open a pull request. I'll be happy to review it!

License

This project is licensed under the GPL-3.0 License.

Author

Marcos Alvarez

1.0.26

6 months ago

1.0.25

6 months ago

1.0.24

6 months ago

1.0.23

6 months ago

1.0.22

6 months ago

1.0.21

6 months ago

1.0.20

6 months ago

1.0.19

6 months ago

1.0.18

6 months ago

1.0.17

6 months ago

1.0.16

6 months ago

1.0.15

6 months ago

1.0.14

6 months ago

1.0.13

6 months ago

1.0.12

6 months ago

1.0.11

6 months ago

1.0.10

6 months ago

1.0.9

6 months ago

1.0.8

6 months ago

1.0.7

6 months ago

1.0.6

6 months ago

1.0.5

6 months ago

1.0.4

6 months ago

1.0.3

6 months ago

1.0.2

6 months ago

0.0.1

6 months ago