Coder Social home page Coder Social logo

concubine's Introduction

concubine

concubine provides the framework necessary to create a "Hooks" system with full TypeScript typings, like the one React uses.

Usage

concubine cooperates with an instance class you define which maintains the state across hook calls. (These are analagous to the fiber nodes in React, if you know what those are).

You create a "hooks system" by calling concubine's makeHooksSystem function with an instance type and a map of functions that return user-facing hook functions, given the current instance:

// Your instance type. This is a contrived example.
class Instance {
  tagName: string = "div";
}

const hooksSystem = makeHooksSystem<Instance>()({
  useTagName: (instance) => () => instance.tagName,
});

The returned hooksSystem has two properties on it: withInstance and hooks. withInstance is a function that you call to set the current instance and do some work, and hooks is an Object with all your user-facing hook functions on it.

To use React terminology: hooks has functions on it like useState, useRef, etc, and withInstance is what you use to set the current fiber node and render.

// The `hooks` property contains your user-facing API.
const { useTagName } = hooksSystem.hooks;

export { useTagName };

// The `withInstance` property is your internal API.
const node = new Node();
hooksSystem.withInstance(node, () => {
  // call user code (hook-using code) here.
});

In practice, you will probably want to store some state in your instances, so that on subsequent "render"s, you can retrieve that state. See "Examples" below to see how to do that.

Before and After callbacks

If you want to always run some code before and/or after each withInstance call, you can pass a config Object with the keys prepareInstance and/or releaseInstance on it as the second argument to makeHooksSystem:

// Your instance type. This is a contrived example.
class Instance {
  tagName: string = "div";
}

const hooksSystem = makeHooksSystem<Instance>()(
  {
    useTagName: (instance) => () => instance.tagName,
  },
  {
    prepareInstance: (instance) => {
      // This runs before each withInstance callback
    },
    releaseInstance: (instance) => {
      // This runs after each withInstance callback
    },
  }
);

Error messaging

If a user tries to use a hook function, but they're not doing so from within a stack where you called withInstance, then an Error will be thrown. By default, the Error message is: "Attempted to use a hook function, but there was no active instance.". This error message is pretty generic, so you should probably customize it by passing a config Object with the key hookUsedOutsideOfWithInstanceErrorMessage on it as the second argument to makeHooksSystem:

const hooksSystem = makeHooksSystem<Instance>()(
  {
    useTagName: (instance) => () => instance.tagName,
  },
  {
    hookUsedOutsideOfWithInstanceErrorMessage:
      "Attempted to use a hook function outside of a Component's render method.",
  }
);

Examples

Here is an example of an instance that has some state slots, and a useState hook that reads from and writes to those state slots (just like React's).

class Instance {
  stateSlots: Map<number, any> = new Map();
  currentStateSlot: number = 0;
}

const hooksSystem = makeHooksSystem<Instance>()(
  {
    useState: (instance) => <T>(
      initialValue: T
    ): [T, (nextValue: T) => void] => {
      const slot = instance.currentStateSlot;

      let value;
      if (instance.stateSlots.has(slot)) {
        value = instance.stateSlots.get(slot);
      } else {
        instance.stateSlots.set(slot, initialValue);
        value = initialValue;
      }

      const setValue = (nextValue: T) => {
        instance.stateSlots.set(slot, nextValue);
      };

      instance.currentStateSlot++;

      return [value, setValue];
    },
  },
  {
    prepareInstance(instance) {
      instance.currentStateSlot = 0;
    },
  }
);

FAQ

Is this what React uses internally for their hooks system?

No, and if you're on the React team and you're reading this, please don't use this for React's hooks system. I don't want the attention. Fork it if you want though.

Why is it called concubine?

It's a pun. Concubine is a synonym for "hooker", and this package sets up the hooks. So it's the "hooker".

License

MIT

concubine's People

Contributors

suchipi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.