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-usedoa

v2.1.0

Published

Allows the mutation of objects and prop objects using standard left-to-right field assignments (eg. fruit.name = "Apple"). Only works with React.js and React Native.

Downloads

9

Readme

Table of Contents

About

useDOA, or use Dynamic Object Assignment is a React hook designed to allow programmers to mutate state objects (which includes class instances, arrays, and prop objects) without cloning said object (excluding the initial clone) in order to change just one field (or multiple). Mutating prop objects with useDOA is possible and does not trigger a re-render of the parent component. useDOA allows you to use standard left-to-right object field assignment (eg. fruit.name = "Apple") inside the component render function (or return block when using hooks) to change object fields. You can choose whether or not to trigger a re-render when updating objects, something that can be useful when dealing with internal values in complex class instances. useDOA works in both React.js and React Native, although the example project ONLY works in React.js, however, the same code templates can be used in React Native.

Installation

Run: npm i react-usedoa

Usage

Importing

Import with import useDOA from 'react-usedoa'

Hook

Use the hook with const [object, updateObject] = useDOA(initialObject, cloneInitialObject);

  • Where useDOA is function(initialObject: object | Array, cloneInitialObject = true)
  • Where typeof object === "object" (or null), typeof initialObject === "object" (or null), and typeof updateObject === "function"
  • Where updateObject is function(value: object | Array | Function, cloneObject = true)

Explanation

There are two ways of assigning new values to fields and one way of assigning new object reference values (excluding the initial state assignment) using DOA.

The useDOA hook takes an initial value to use as the state and a boolean which determines if the initial value should be cloned, and returns an array containing two elements. The first element is the exact same object passed in (or null if null was passed), and the second element is a function which when called will update the object state value. Remember, in JavaScript, arrays and class instances are also objects. This means that the useDOA function can take an object, class instance, and array.

The second element returned by useDOA should always have the prefix update in order to distinguish it from React's useState set___.

Showcase

// Standard React hook component
const DisplayBuilding = () => {
    const [building, updateBuilding] = useDOA({ name: "Tower", height: 200 });

    return (
        <div>
            <button
                onClick={() => {
                    building.height += 5;
                    updateBuilding();
                }}>
                Add 5 meters using standard left-to-right object field assignment.
            </button>
            <button
                onClick={() => updateBuilding(() => building.height += 5)}>
                Add 5 meters using an assignment function
            </button>
            <button
                onClick={() => updateBuilding({ name: "House", height: 7 })}>
                Assign a new building object to the building state.
            </button>
            <p>
                {building.name} height is {building.height} meters.
            </p>
        </div>
    );
}

// Standard export
export default DisplayBuilding;

Mutating prop objects without parent re-renders

Clicking the "Click to eat" button in a Fruit component will not cause a re-render of the Container component, only the Fruit component that the button is in. Also, remember to pass in a boolean value of false as the second argument with useDOA and the update function in order to avoid using a cloned reference to the prop object. This can be very useful behaviour when working with forms or a long list of objects.

const Fruit = props => {
    const [fruit, updateFruit] = useDOA(props.fruit, false);

    console.log("Render fruit");

    return (
        <li>
            <button onClick={() => updateFruit(() => fruit.name = "Ate " + fruit.name)}>
                Click to eat
            </button>
        </li>
    );
}

const fruits = [
    {
        id: 0,
        name: "Apple"
    },
    {
        id: 1,
        name: "Banana"
    },
    {
        id: 2,
        name: "Orange"
    }
]

const Container = () => {
    const [fruits, setFruits] = useState(fruits);

    console.log("Render fruit container");

    return (
        <ul>
            {
                fruits.map((f, i) => (
                    <Fruit
                        key={i}
                        fruit={f} />
                ))
            }
        </ul>
    );
}

Standard Left-to-Right Field Assignment

const [building, updateBuilding] = useDOA({ name: "Tower", height: 200 });

// Synchronously adds 10 meters to the building's height,
// however, does **NOT** cause a re-render.
building.height += 10;

console.log(building.height); // 210

// This is what causes a normal component re-render (asynchronous).
// Not calling this will avoid a component re-render, which might
// be what you want to do in some very rare cases.
updateBuilding();

Functional Assignment

const [building, updateBuilding] = useDOA({ name: "Tower", height: 200 });

// By passing a function instead of an object we synchronously
// add 10 meters to the building's height (just as before),
// **AND** we also cause a normal component re-render (asynchronous).
// If you want a one-liner, this is the assignment type to use.
updateBuilding(() => building.height += 10);

// Because the field assignment is synchronous,
// the building's height already has the value of 210.
console.log(building.height); // 210

// You can also pass a non-arrow function as well:
updateBuilding(function() { building.height += 10 });

console.log(building.height); // 220

New Object Value Assignment

const [building, updateBuilding] = useDOA({ name: "Tower", height: 200 });

// We want to create a new building object and assign that
// as our new building value instead. Using React's
// setState one would do it as such:
//  setState({ name: "Villa", height: 8 });

// With useDOA, we do the exact same thing, but with
// the update function instead:
updateBuilding({ name: "Villa", height: 8 });

// We've now synchronously assigned a new object value
// to the DOA and queued a component re-render, however,
// we haven't gotten the re-render yet, which means that
// the building reference still points to the old building
// object, as we'll see below:
console.log(building); // { name: "Tower", height: 200 } (!!!)

// If we want to instantly get back our new object value
// and skip creating a temporary object reference, we just
// assign our building reference to the returned reference
// passed back by the update function:
building = updateBuilding({ name: "Villa", height: 8 });

console.log(building); // { name: "Villa", height: 8 }

As you can see, useDOA allows for much less code which is also easier to read and maintain. React's setState way of mutating objects would be:

setBuilding({ ...building, height: building.height + 5 });

which is neither very efficient (especially on larger objects) nor easy to read and maintain.

Tests

Both the useDOA function and the update function take a boolean value as the second parameter. If the argument is the value of true, which is also its default value, the initial object is cloned and the clone is given the same prototype as the initial object. Passing an argument of value false makes it so that the object returned is of the same instance as the given object.

let _initialFruit = { name: "Apple", price: 20 };
let _initialDrink = { name: "Water", volume: 3 };

const [fruit, updateFruit] = useDOA(_initialFruit);
const [drink, updateDrink] = useDOA(_initialDrink, false);

console.log(fruit === _initialFruit); // false
console.log(drink === _initialDrink); // true

updateFruit(() => fruit.price = 30);
updateDrink(() => drink.volume = 5);

console.log(fruit === _initialFruit); // false
console.log(drink === _initialDrink); // true

console.log(fruit); // { name: "Apple", price: 30 }
console.log(drink); // { name: "Water", volume: 5 }

let updatedFruit = updateFruit(() => fruit.price = 45);
let updatedDrink = updateDrink(() => drink.volume = 9);

console.log(fruit === updatedFruit); // true
console.log(drink === updatedDrink); // true

console.log(fruit); // { name: "Apple", price: 45 }
console.log(drink); // { name: "Water", volume: 9 }

// Because the object assignment is asynchronous, we pretend we
// haven't gotten the re-render yet. The "false" on the second update
// is to show that there is no difference in passing true or false
// whenever updating with new object instances.
updateFruit({ name: "Banana", length: 4 });
updateDrink({ name: "Juice", strength: 30 }, false);

console.log(fruit); // { name: "Apple", price: 45 }
console.log(drink); // { name: "Water", volume: 9 }

// Same thing here, although we instantly use the newly assigned objects
// and save them in updatedFruit and updatedDrink.
updatedFruit = updateFruit({ name: "Orange", volume: 20 });
updatedDrink = updateDrink({ name: "Milk", calcium: 8 });

// The fruit and drink variables still hold all the old data from before
// just as a setState call would (until a re-render is triggered).
console.log(fruit === updatedFruit); // false
console.log(drink === updatedDrink); // false

console.log(updatedFruit); // { name: "Orange", volume: 20 }
console.log(updatedDrink); // { name: "Milk", calcium: 8 }

Benefits

  • Easier to read.
  • Easier to maintain.
  • Fewer lines of code and less code clutter.
  • Better performance (larger objects).
  • The ability to mutate an object's field(s) without causing a re-render if one wants.
  • The ability to mutate objects sent in by props without using a callback.
  • The ability to mutate objects sent in by props without causing the parent to re-render itself.
  • Calling the update function repeatedly does not cause stacked re-renders.

Limitations

  • Using the update function to assign a new value to the DOA does not update the prop's value. To do this, you have to do a remap yourself.

License

MIT. Copyright (c) 2020 Emil Engelin.