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-auto-controlled

v0.2.4

Published

Component autonomous state control utilities in React class methods and React Hooks

Downloads

5,611

Readme

react-auto-controlled

A collection of React component libraries (both class methods and Hooks) for self-governing state values.

Why?

  1. You've the need to write (most likely generic) components which contain simple controls to their internal state, such as visibility toggling or number incrementing which should be controllable by the component without having to provide a prop and event handler externally.
  2. You've discovered that helpers in the wild does roughly what you want but have shortcomings:
  1. You don't want to reimplement the wheel every single time, especially on class components.

Enter this library. Its utilities behave roughly in the manner you'd expect if you were to use <AutoControlledComponent>, with some differences. Include support for a Hook-based counterpart and you get react-auto-controlled.

Installation

react-auto-controlled

You'll need React preinstalled in order to use this library.

NPM

npm install --save react
npm install --save react-auto-controlled

Yarn

yarn add react
yarn add react-auto-controlled

This package contains definitions for TypeScript. You don't need to install a @types/react-auto-controlled module. All examples are written in TypeScript. Pure JavaScript developers are encouraged to read along.

Usage

The examples below assume a component called <Counter> will be used this way:

import { Counter } from './Counter';

export function App() {
  // Details:
  // 1. Without a `prop`, the counter component starts at `0`, incrementing itself when its button is clicked.
  // 2. With a `defaultProp`, the counter component starts at `20`, incrementing itself when its button is clicked.
  // 3. With a `value`, the counter component will not update its value unless the user provides a `value` prop.
  return (
    <div>
      <Counter />
      <Counter defaultValue={20} defaultName="Cody" />
      <Counter value={10} name="Charlie" />
    </div>
  );
}

Hook Function

For React v16.8+

Demo: https://github.com/andrewsantarin/react-16-auto-controlled-hook/tree/master/

Unlike a State Manager Class, a Hook only manages one slice of the state, similar to the React useState hook. By using Hooks, you gain more control over which state you'd prefer to update. However, in the spirit of useState, you have to invoke the state modifer one by one if you want to update multiple states at once.

import React, { useCallback } from 'react';
import { useAutoControlled } from 'react-auto-controlled';

interface CounterProps {
  otherProp?: string;
  value?: number;
  defaultValue?: number;
  name?: string;
  defaultName?: string;
}

export function Counter(props) {
  const [
    value,      // original React.useState pattern
    setValue,   // original React.useState pattern
    trySetValue,
    getDerivedValueFromProp
  ] = useAutoControlled(0, {
    prop: props.value,                // optional
    defaultProp: props.defaultValue,  // optional
  });
  const [
    name,       // original React.useState pattern
    setName,    // original React.useState pattern
    trySetName,
    getDerivedNameFromProp
  ] = useAutoControlled('Andrew', {
    prop: props.name,                 // optional
    defaultProp: props.defaultName,   // optional
  });

  getDerivedValueFromProp();  // Similar to getDerivedStateFromProps, except no argument and state slice only.
  getDerivedNameFromProp();   // Similar to getDerivedStateFromProps, except no argument and state slice only.

  const handleClick = useCallback(() => {
    trySetValue(value + 1);
    trySetName('Bob');
  }, [ trySetValue, value ]);

  return (
    <div>
      <button onClick={handleClick}>
        Value: <strong>{value}</strong>
      </button>
      <div>
        Hello, {name}!
      </div>
    </div>
  );
}

State Manager Class (modern)

For React v16.3+

Demo: https://github.com/andrewsantarin/react-16-auto-controlled-class/tree/master/

The Class State Manager offers a set of methods in order to autocontrol state. Unlike <AutoControlledComponent>, it's not an extension of the React Component class. You'll need to declare your own class component separately.

This approach was taken in order to reduce static attribute pollution on the component class, which can also be potentially overridden by an unattentive developer. The manager's methods are immutable (Object.freeze(this)).

If you use earlier versions of React which aren't shipped with the static getDerivedStateFromProps(nextProps, prevState) component function, refer to the legacy example instead.

import React, { Component } from 'react';
import { AutoControlledManager, AutoControlled } from 'react-auto-controlled';

interface CounterProps = {
  otherProp?: string;
  value?: number;
  defaultValue?: number;
  name?: string;
  defaultName?: string;
};

type CounterState = Required<
  Pick<CounterProps, 'value' | 'name'>
>;

const counterAutoControlledManager = new AutoControlledManager<AppState, AppProps>(
  [
    // A list of class component props you want to auto-control
    'value',
    'name',
  ],
  {
    // State initializer
    getInitialAutoControlledState() {
      return {
        active: false,
        name: 'Andrew',
        level: 0,
      };
    }
  }
);

export class Counter 
  extends Component<CounterProps, CounterState>
  implements AutoControlled<CounterState> // Enforce the `.trySetState(maybeState, callback?)` class method.
{
  // Use `constructor` if you have custom logic in the state / attribute / method assignment.
  constructor(props: CounterProps) {
    super(props);

    this.state = counterAutoControlledManager.getInitialAutoControlledStateFromProps(props);
  }

  // Take this approach if you have zero custom logic. Just use `this.props`. Convenient!
  state = counterAutoControlledManager.getInitialAutoControlledStateFromProps(this.props);

  // Apply the manager functions to the corresponding component methods.
  static getDerivedStateFromProps = counterAutoControlledManager.getDerivedStateFromProps;
  trySetState = counterAutoControlledManager.trySetState;

  handleClick = () => {
    // Replace `this.setState()` with `this.trySetState()` to achieve auto-control.
    this.trySetState({
      value: this.state.value + 1,
      name: 'Bob',
    });
  }

  render() {
    const { value, name } = this.state;

    return (
      <div>
        <button onClick={this.handleClick}>
          Value: <strong>{value}</strong>
        </button>
        <div>
          Hello, {name}!
        </div>
      </div>
    );
  }
}

State Manager Class (legacy)

For React 15.0 ~ React 16.2

⚠️ This library has been tested with React v16.8.x. ⚠️

The maintainer(s) can't guarantee that this approach will always work.

Consider upgrading your project's React dependency to at least v16.3.

Demo: https://codesandbox.io/s/github/andrewsantarin/react-15-auto-controlled-class/tree/master/

The Class State Manager offers a set of methods in order to autocontrol state.

The static getDerivedStateFromProps(nextProps, prevState) component function, which this library intends to enhance, is unavailable to you in earlier version of React. You can subsitute it with a combination of:

  • componentWillReceiveProps(nextProps) lifecycle function
  • this.state in the lifecycle implementation.
import React, { Component } from 'react';
import { AutoControlledManager, AutoControlled } from 'react-auto-controlled';

interface CounterProps = {
  otherProp?: string;
  value?: number;
  defaultValue?: number;
  name?: string;
  defaultName?: string;
};

type CounterState = Required<
  Pick<CounterProps, 'value' | 'name'>
>;

const counterAutoControlledManager = new AutoControlledManager<AppState, AppProps>(
  [
    // A list of class component props you want to auto-control
    'value',
    'name',
  ],
  {
    // State initializer
    getInitialAutoControlledState() {
      return {
        active: false,
        name: 'Andrew',
        level: 0,
      };
    }
  }
);

export class Counter 
  extends Component<CounterProps, CounterState>
  implements AutoControlled<CounterState> // Enforce the `.trySetState(maybeState, callback?)` class method.
{
  // Use `constructor` if you have custom logic in the state / attribute / method assignment.
  constructor(props: CounterProps) {
    super(props);

    this.state = counterAutoControlledManager.getInitialAutoControlledStateFromProps(props);
  }

  // Take this approach if you have zero custom logic. Just use `this.props`. Convenient!
  state = counterAutoControlledManager.getInitialAutoControlledStateFromProps(this.props);

  componentWillReceiveProps(nextProps: CounterProps) {
    const newState = counterAutoControlledManager.getDerivedStateFromProps(
      nextProps,
      this.state // This should behave roughly like the `prevState` callback parameter.
    );

    if (newState === null) {
      return;
    }

    // Apply the state changes.
    this.setState(newState);
  }

  // Apply the manager functions to the corresponding component methods.
  trySetState = counterAutoControlledManager.trySetState;

  handleClick = () => {
    // Replace `this.setState()` with `this.trySetState()` to achieve auto-control.
    this.trySetState({
      value: this.state.value + 1,
      name: 'Bob',
    });
  }

  render() {
    const { value, name } = this.state;

    return (
      <div>
        <button onClick={this.handleClick}>
          Value: <strong>{value}</strong>
        </button>
        <div>
          Hello, {name}!
        </div>
      </div>
    );
  }
}

Contribution

This library is still very much a work in progress was hastily started so that it can be used immediately in bigger projects.

Ideas & support are more than welcome!

  1. Fork the project.
  2. Run the project in development mode: yarn develop.
  3. Make changes to the code.
  4. Add new tests.
  5. Test the code: yarn test. If the tests fail, check the code again. Make sure the tests pass!
  6. Lint the code: yarn lint. Clean up all linting errors.
  7. Update this readme with regards to the new API changes.
  8. Commit the code.
  9. Create a pull request.

License

See: LICENSE.md

MIT.