Coder Social home page Coder Social logo

tng-hooks's Introduction

TNG-Hooks

Build Status npm Module Dependencies devDependencies Coverage Status

Overview

TNG-Hooks (/ˈting ho͝oks/) provides hooks (i.e., useState(..), useReducer(..), useEffect(..), etc) for decorating regular, standalone functions with useful state and effects management. Custom hooks are also supported.

TNG is inspired by the conventions and capabilities of React's Hooks, so much of TNG resembles React Hooks. The growing collections of information and examples about React's Hooks will also be useful in sparking ideas for TNG usage.

However, this is a separate project with its own motivations and specific behaviors. TNG will remain similar to React Hooks where it makes sense, but there will also be deviations as appropriate.

Articulated Functions

An Articulated Function is the TNG equivalent of a React function component: a regular, standalone function decorated with a TNG hooks-context, which means hooks are valid to use during its invocation.

Unlike a normal pure function, which takes all its inputs and computes output(s) without producing any side-effects, the most straightforward way to think about an Articulated Function is that it is stateful (maintains its own state) and effectful (spins off side-effects).

These will often be used to model the rendering of UI components, as is seen with React components. But Articulated Functions are useful for tracking any kind of state, as well as applying various side effects (asynchrony, Ajax calls, database queries, etc).

Similar to React's "Custom Hooks", TNG's Articulated Functions can also invoke other non-Articulated Functions, which allows those function calls to adopt the active hooks-context and use any current hooks, as if they were Articulated. These non-articulated-but-hooks-capable functions are TNG's Custom Hooks.

Quick Examples

One of the most common TNG hooks is the useState(..) hook, which stores persistent (across invocations) state for an Articulated Function, essentially the same as React's useState(..) hook does for a function component.

For example:

// generating Articulated Functions (aka, wrapping with TNG hooks-context)
[renderUsername,onClickUsername] = TNG(renderUsername,onClickUsername);

function renderUsername(username) {
    // using the `useState(..)` hook
    var [activated,setActivated] = useState(false);

    usernameElem.innerHTML = username;

    // only run this code the first time
    if (!activated) {
        setActivated(true);
        usernameElem.addEventListener("click",onClickUsername,false);
    }
}

function onClickUsername() {
    // using the `useState(..)` hook
    var [expanded,setExpanded] = useState(false);

    // toggles based on `expanded` state
    if (!expanded) {
        setExpanded(true);
        renderUsername(user.longName);
    }
    else {
        setExpanded(false);
        renderUsername(user.shortName);
    }
}

// ...

var usernameElem = document.getElementById("username");
var user = { shortName: "KS", longName: "Kyle Simpson", };
renderUsername(user.shortName);

Run Demo

In the above snippet, activated is persistent (across invocations) state for the renderUsername(..) Articulated Function, and expanded is separate persistent state for the onClickUsername(..) Articulated Function.

activated in the above snippet demonstrates how to perform an action just once, such as attaching a click handler to a DOM element. That works, but it's not ideal.

A much cleaner approach for handling side-effects conditionally is with the useEffect(..) hook, which is inspired by React's useEffect(..) hook.

For example:

function renderUsername(username) {
    var [usernameElem,setElem] = useState(null);

    // using the `useEffect(..)` hook
    useEffect(function onActivate(){
        usernameElem = document.getElementById("username");
        usernameElem.addEventListener("click",onClickUsername,false);

        setElem(usernameElem);
    },[]);

    // using the `useEffect(..)` hook
    useEffect(function onUpdate(){
        usernameElem.innerHTML = username;
    },[username]);
}

function onClickUsername() {
    var [expanded,setExpanded] = useState(false);

    if (!expanded) {
        setExpanded(true);
        renderUsername(user.longName);
    }
    else {
        setExpanded(false);
        renderUsername(user.shortName);
    }
}

// ...

var user = { shortName: "KS", longName: "Kyle Simpson", };
renderUsername(user.shortName);

Run Demo

In this snippet, the first useEffect( .. , [] ) passes an empty array ([]) for its list of conditional state guards, which means that effect will only ever run the first time. The second useEffect( .., [username] ) passes [username] for its list of conditional state guards, which ensures that its effect will only run if the username value is different from the previous applied invocation of that effect.

TNG hooks can also be used in a non-Articulated Function, which implies it will be treated essentially like a React "Custom Hook"; to have a TNG hooks-context available, the non-Articulated Custom Hook Function must be called from an Articulated Function, or an error will be thrown.

For example:

// Custom Hook (adopt the TNG hooks-context from `showNav()`)
function useName(defaultName) {
    var [name,setName] = useState(defaultName);
    // ..
}

// Articulated Function
function showNav() {
    useName("user");
    // ..
}

showNav = TNG(showNav);
showNav();

See TNG Custom Hooks below for more information.

There are also some IMPORTANT RULES to keep in mind with using TNG hooks in your Articulated Functions and Custom Hooks.

API

TNG provides hooks which deliberately resemble React's hooks. However, as TNG is a separate project, there are some important nuances and differences to pay close attention to.

TNG(..)

TNG(..) is a utility to produce Articulated Functions from normal, stanadlone functions. Articulated Functions adopt an active hooks-context to enable hooks capabilities.

For example:

// wrap one function at a time
foo = TNG(foo);

// or, wrap multiple functions at once
[bar,baz] = TNG(bar,baz);

function foo(..) { .. }
function bar(..) { .. }
function baz(..) { .. }

The same function can actually be Articulated multiple times, with each one getting its own separate TNG hooks-context:

function foo(..) { .. }

var [A,B] = TNG(foo,foo);
var C = TNG(foo);

// later:
A();        // own separate TNG hooks-context
B();        // ditto
C();        // ditto

Articulated Functions have the same signature as the functions they wrap, including any arguments, return value, and the ability to be invoked with a this context if desired.

Resetting Hooks-Context

Articulated Functions also have a method defined on them called reset(). The reset() method resets the internal TNG hooks-context of an Articulated Function, including any state slots, effects, etc.

For example:

function hit() {
    var [count,updateCount] = useState(0);

    count++;
    updateCount(count);

    console.log(`Hit count: ${count}`);
}

hit = TNG(hit);

hit();       // Hit count: 1
hit();       // Hit count: 2
hit();       // Hit count: 3

hit.reset();

hit();       // Hit count: 1

Also, if an Articulated Function has any pending effect cleanup functions, reset() will trigger them. See useEffect(..) for more information on effects and cleanups.

useState(..) Hook

The TNG useState(..) hook, like React's useState(..) hook, allows an Articulated Function to persist a unit of state across multiple invocations, without relying on global variables or having to manually create a closure to store that state.

For example:

function hit() {
    var [count,updateCount] = useState(0);

    count++;
    updateCount(count);

    console.log(`Hit count: ${count}`);
}

hit = TNG(hit);

hit();       // Hit count: 1
hit();       // Hit count: 2
hit();       // Hit count: 3

The useState(..) hook function takes either a direct value, or a function which returns that value. Whichever way it's provided, this value is used only the first time as the initial value for that unit of state.

The return value of useState(..) is a tuple (2-element array) containing the current value of that unit of state, as well as a function to use to set/update that unit of state. You can name this unit of state whatever is appropriate, and also name the set/update function whatever is appropriate.

In the above snippet, we used array destructuring to set count and updateCount from the tuple returned from useState(..).

The setter/updater (updateCount(..) in the above snippet) normally receives a single value. Alternatively, you can pass a function, which will receive the current value of that state unit as its only argument, and which should return the new value for that state unit.

For example:

function hit() {
    var [count,updateCount] = useState(0);

    updateCount(onUpdateCount);

    console.log(`Hit count: ${count+1}`);
}

function onUpdateCount(oldCount) {
    return oldCount + 1;
}

hit = TNG(hit);

hit();       // Hit count: 1
hit();       // Hit count: 2
hit();       // Hit count: 3

This approach is helpful for determining the new state unit value based on its current value, especially if, as shown above, the setter/updater function is not inside the closure and cannot access the current state unit value directly.

In this particular example, the line updateCount(onUpdateCount) could also have been written with the same outcome as:

updateCount( onUpdateCount(count) );

The onUpdateCount(count) is passed the current count value manually, which returns an updated value; that updated value is passed directly to updateCount(..) to be set.

useReducer(..) Hook

Like React's useReducer(..) hook, the TNG useReducer(..) hook is like a special case of TNG's useState(..) hook in that it also provides for persistent state storage across invocations; but it's especially helpful for certain cases when the state updates are more involved.

useReducer(..) expects a reducer function and an initial value for its state unit.

For example:

function hit(amount = 1) {
    var [count,incCounter] = useReducer(updateCounter,0);
    incCounter(amount);

    console.log(`Hit count: ${count+amount}`);
}

function updateCounter(prevCount,val) {
    return prevCount + val;
}

hit = TNG(hit);

hit();       // Hit count: 1
hit();       // Hit count: 2
hit(8);      // Hit count: 10

Optionally, you can pass a third argument to useReducer(..) (value 5 in the following snippet), which specifies a value to be used in invoking the reducer immediately on this initial pass:

function hit(amount = 1) {
    var [count,incCounter] = useReducer(updateCounter,0,5);
    incCounter(amount);

    console.log(`Hit count: ${count+amount}`);
}

function updateCounter(prevCount,val) {
    return prevCount + val;
}

hit = TNG(hit);

hit();       // Hit count: 6
hit();       // Hit count: 7
hit(3);      // Hit count: 10

The line useReducer(updateCounter,0,5) immediately invokes updateCounter(0,5), which returns 5, and the state unit (named count here) is then initially set to that 5 value.

useEffect(..) Hook

Like React's useEffect(..) hook, the TNG useEffect(..) hook will conditionally run side-effect code "after" the current Articulated Function completes its invocation.

For example:

function hit() {
    var [count,updateCount] = useState(0);

    updateCount(onUpdateCount);

    useEffect(function logAfter(){
        console.log(`Hit count: ${count+1}`);
    });

    console.log("Hit!");
}

function onUpdateCount(oldCount) {
    return oldCount + 1;
}

hit = TNG(hit);

hit();       // Hit!
             // Hit count: 1
hit();       // Hit!
             // Hit count: 2
hit();       // Hit!
             // Hit count: 3

Notice in the above snippet that despite the lexical ordering, the console.log("Hit!") is actually executed before the effect has a chance to run and log its message. That's because an effect, which is generally useful for side-effects, is run after the current invocation of the Articulated Function is complete, as if it appeared in a finally { .. } clause.

This doesn't mean async (or sync) behavior, only that it's "deferred" until "after" the Articulated Function completes. These relative terms are deliberately being left abstract at present, to allow for future evolution of TNG's functionality.

CRITICAL NOTE: DO NOT rely on any observed synchronous/asynchronous behavior of effects, nor any observed ordering between effects. Effects should always be treated as completely independent of each other. In the future, some effects may actually run asynchronously, which would likely affect the ordering between effects.

Conditional Effects

A conditional effect is invoked only under certain conditions, which can be quite useful in a variety of scenarios.

The most common scenario is when an effect involves costly DOM operations; for performance reasons, you'd only want those DOM operations to be processed if that part of the DOM actually needed to be updated because some related state values had changed. If the state values haven't changed, a conditional effect prevents the unnecessary DOM operations by skipping the effect.

The useEffect(..) utility accepts an optional second argument, which is a list of values to guard whether the effect should be invoked. See also the related discussion of the input-guards list for useMemo(..).

useEffect(..)'s guards list is optional because sometimes effects should be invoked every time. As shown above, if the guards list is omitted, the effect is always invoked:

function updateCounter(count) {
    useEffect(function onUpdate(){
        // unconditional effect, runs every time
    });
}

updateCounter = TNG(updateCounter);

But in some cases, conditional guards can be quite helpful for performance optimizations (e.g., preventing unnecessary invocations of an effect).

If the guards list is provided and includes any values, the list's current values are compared to the previous guards list values provided when the effect was last invoked; the conditional effect is invoked only if a value in the guards list has changed from before, or if this is the first invocation of that conditional effect; otherwise the effect invocation is skipped.

As a special case of this conditional guards list behavior, passing an empty list ([]) every time is the most straight-forward way to ensure an effect runs only once, the first time:

function renderButton(label) {
    // only run this effect once, initially
    useEffect(function onSetup(){
        buttonElem.addEventListener("click",onClick);
    },[]);

    // ..
}

renderButton = TNG(renderButton);

The list of values you pass as the conditional guards should be any (and all!) state values that the effect depends on.

For example, if an effect function closes over (uses) two variables, name and age, then the effect's conditional guards list should include both of them (as [name,age]). Thus, the effect will only be invoked if either name or age (or both) have changed since the last time the effect was actually invoked.

function renderPerson(person) {
    var { name, age } = person;

    useEffect(function onChanged(){
        nameElem.innerText = name;
        ageElem.innerText = age;
    },[name,age]);
}

renderPerson = TNG(renderPerson);

As stated, the use of the guards list is optional. But if you choose to pass the guards list, it's a very good idea and best practice to always pass the same fixed guards list to each invocation of a conditional effect (even though the values in the list will change).

In other words, avoid dynamically constructing and passing different guards lists (or sometimes no guards list at all) to the same conditional effect across different invocations. This would lead to very confusing behavior and be more susceptible to bugs. Moreover, it's likely to be rare for an effect to depend on different state values on subsequent invocations; try to avoid this if possible, perhaps by breaking into separate conditional effects, each with their own fixed guards list.

Effect Cleanups

Effects do not receive any arguments, and their return values are generally ignored, with one exception. If an effect returns another function, that function is assumed to be a "cleanup function" for the effect. In other words, each effect can optionally define a cleanup function, which performs any necessary cleanup before the next invocation of that effect.

For example, if an effect assigns a DOM event handler, and the effect may run multiple times, subsequent invocations of the effect would otherwise be duplicating the event handling (which is likely to lead to bugs). To avoid this problem, define a cleanup function for the effect:

function renderButton(label) {
    useEffect(function onSetup(){
        buttonElem.addEventListener("click",onClick);

        return function onCleanup(){
            buttonElem.removeEventListener("click",onClick);
        };
    });

    // ..
}

renderButton = TNG(renderButton);

The first time the Articulated Function renderButton(..) is invoked, the onSetup() effect will subscribe its event listener. The onCleanup() cleanup function returned from the effect will be saved by TNG internally. The next time renderButton(..) is invoked (and thus the onSetup() effect is invoked), the saved previous onCleanup() function will first be triggered -- in this example, unsubscribing the event listener and preventing a subsequent double event subscription.

Note: Since effects are not invoked until after the Articulated Function is complete, that means the cleanup function saved from the previous invocation of an effect will also not be triggered until after the current invocation of the Articulated Function is complete.

Each invocation of an effect triggers its own previous cleanup (if any). But the "final" invocation of a cleanup -- whenever the Articulated Function (and its effects) won't be invoked anymore -- would obviously not have anything to trigger it. If the cause of this final state is the end of the lifetime of the program/browser page, this is likely not a problem.

But if you need to ensure manually that any pending final cleanup(s) are actually triggered, the reset() method of the Articulated Function will trigger any pending cleanups.

For example:

renderButton("Click Me");

// ..

// operation pending, change button to an "undo"
renderButton("Undo...");

// ..

// operation complete, button being disabled/removed
renderButton.reset();

Keep in mind that reset() also resets the internal TNG hooks-context of the Articulated Function, including all state slots, effects, etc.

useMemo(..) Hook

Like React's useMemo(..) hook, the TNG useMemo(..) hook will invoke a function and return its value. But additionally, this return value is memoized (aka "remembered") so that if the same function (exact same reference!) is evaluated again, the function won't actually be invoked, but its memoized value will be returned.

Memoization of a function's return value can be a very helpful performance optimization, preventing the function from being called unnecessarily when the same value would be returned anyway. Memoization should only be used when a function is likely to be called multiple times (with the same output returned), where this performance optimization will be beneficial.

Keep in mind that memoization means TNG stores the last return value output for each memoized function, which could have implications on memory usage and/or GC behavior. Only memoize functions if they match this intended usage and performance pattern.

Note: useMemo(..) does not pass any arguments when invoking the function. The memoized function must therefore already have access to any necessary "inputs", either by closure or some other means, and should use only those inputs to produce its output. A memoized function should always return the same output given the same state of all its inputs. Otherwise, any expected differing output would not be returned, which would almost certainly cause bugs in the program.

For example:

function computeMeaningOfLife() {
    // ..
    console.log("Computing...");
    return 42;
}

function askTheQuestion() {
    var v = useMemo(computeMeaningOfLife);
    return v;
}

askTheQuestion = TNG(askTheQuestion);

askTheQuestion();       // Computing...
                        // 42
askTheQuestion();       // 42

In this snippet, the first invocation of askTheQuestion() invokes the computeMeaningOfLife() function. But on the second invocation of askTheQuestion(), the memoized 42 output is returned without invoking computeMeaningOfLife().

In that above snippet, across both invocations, the exact same function reference of computeMeaningOfLife is passed to useMemo(..). But each time a different function reference is passed, it will be invoked.

In the following snippet, the computeMeaningOfLife() is a nested function -- in this case, an inline function expression -- and is thus different for each invocation of askTheQuestion(). As a result, computeMeaningOfLife() is always invoked, defeating the whole point of memoization:

function askTheQuestion() {
    var v = useMemo(function computeMeaningOfLife() {
        // ..
        console.log("Computing...");
        return 42;
    });
    return v;
}

askTheQuestion = TNG(askTheQuestion);

askTheQuestion();       // Computing...
                        // 42
askTheQuestion();       // Computing...
                        // 42

It appears as if nested (inside the Articulated Function) functions -- whether inline expressions or just inner function declarations -- cannot be usefully memoized, which seems like a major drawback!

However, this nested function drawback can be addressed. Similar to conditional effects via the optional second argument to useEffect(..), useMemo(..) accepts an optional second argument: an input-guards list.

While the input-guards list is strictly optional, you will probably want to use it most of the time, especially since it enables proper memoization of nested functions.

For example:

function getW(x,y) {
    var z = 3 * (x + y);

    var w = useMemo(function computeW(){
        return x * y / z;
    },[x,y,z]);

    return w;
}

getW = TNG(getW);

getW(3,5);      // 0.625
getW(3,5);      // 0.625 -- memoized!
getW(4,6);      // 0.8

The [x,y,z] array in this snippet acts as the input-guards list for the memoized computeW() nested function.

The first invocation of getW(..) passes [3,5,24] as the input-guards list to useMemo(..), and which invokes the function, producing the 0.625 output. The second invocation of getW(..) passes the same input-guards list values (3, 5, and 24) into useMemo(..), so the computeW() function is not invoked, and the previous return value of 0.625 is simply returned. The third invocation of getW(..) passes in [4,6,30] as the input-guards list, so computeW() is now invoked again, this time producing 0.8.

Though it may be tempting to think of the input-guards list as "conditional memoization", similar to conditional effects based on their guards list, the meaning here is slightly different. It is still conditional invocation, but with a different motivation.

The memoization input-guards list should contain all the memoized function's "inputs": any value the function relies on, that might change over time. These values are not actually passed in as arguments; they just represent "inputs" conceptually, not directly.

The values in this list should not be thought of as conditionally invoking the memoized function, but rather as deciding if the function would produce a new value if invoked.

If any of the input-guards have changed, the assumption is that the memoized function would produce a new output, so it should be invoked to get that new output. But if they haven't changed, the assumption is that the already memoized output value is still the expected return value, so the memoized function can safely be skipped.

In other words, the better mental model here is: the input-guards list determines if the current memoized value is still valid or not.

Similar to useEffect(..), always passing an empty input-guards list [] to useMemo(..) ensures that the memoized function will only ever be invoked once. Also similar to the discussion of using the same guards list for useEffect(..), it's best practice that if you pass an input-guards list to useMemo(..), always pass the same list (even though its values may change).

Note: As shown above, passing an input-guards list produces the memoization behavior (conditional skipping) even for a nested function, which addresses the previously discussed drawback. Further, if you omit the input-guards list (not just passing the [] empty list!), the function reference itself becomes the only input-guard. So, if the function is exactly the same reference each time, its memoized output value will always be returned. But if the function reference is different each time (as it is with nested functions), it always has to be invoked. Bottom Line: Only omit the input-guards list if you will always be passing the same function reference.

useCallback(..) Hook

Like React's useCallback(..) hook, the TNG useCallback(..) hook conditionally selects (via memoization) either the previous version of a function, if a set of input-guards haven't changed, or the new version of the function if the input-guards have changed.

For example:

function requestData(data) {
    var cb = useCallback(
        function onData(resp){
            console.log(`User (${data.userID}) data: ${resp}`);
        },
        [data.userID]
    );
    ajax(API_URL,data,cb);
}

requestData = TNG(requestData);

requestData({ userID: 1 });
// User (1): ...

requestData({ userID: 1 });
// User (1): ...

requestData({ userID: 2 });
// User (2): ...

In this snippet, the onData(..) function is conditionally guarded by the input-guards list [data.userID], which means that cb will remain the same instance of that onData(..) function as long as data.userID stays the same. In other words, the first invocation of requestData(..), with data.userID of 1, defines (and memoizes) an onData(..) nested function expression, and assigns it to cb.

For the second invocation of requestData(..), where data.userID is still 1, the new onData(..) nested function expression is just discarded, and the previously saved onData(..) function reference is returned to cb instead.

Once the data.userID changes (from 1 to 2) for the third invocation of requestData(..), that new onData(..) nested function expression replaces the previous function reference, and is returned to cb.

Note: This particular example is only illustrative of how the useCallback(..) hook works, but would actually have worked the same if the hook had not been used. It takes more complicated examples to illustrate where this hook creates clear benefits.

useRef(..) Hook

Similar to React's useRef(..) hook, the TNG useRef(..) hook creates an object stored persistently in a state slot (via the useState(..) hook), and creates a property on it called current which holds a specified initial value, if any.

For example:

function hit() {
    var counter = useRef(0);

    counter.current++;

    console.log(`Hit count: ${counter.current}`);
}

hit = TNG(hit);

hit();       // Hit count: 1
hit();       // Hit count: 2
hit();       // Hit count: 3

It may be more convenient to pass around the reference to this persistent object, and make any updates to its current property (or add/remove other properties), than to have to pass around both a state value and its updater function.

Custom Hooks

If any TNG hooks are used in a non-Articulated Function, it behaves essentially like a React "Custom Hook". A TNG Custom Hook must be called, directly or indirectly, from an Articulated Function, so that it has an active TNG hooks-context available to use.

For example:

// a Custom Hook, ***not*** an Articulated Function
function useHitCounter() {
    // inherited TNG hooks-context
    var [count,updateCount] = useState(0);

    count++;
    updateCount(count);

    return count;
}

// will be TNG(..) Articulated twice, once as
// each button's click handler
function onClick(evt) {
    // using a Custom Hook
    var hitCount = useHitCounter();

    console.log(`Button #${evt.target.id}: ${hitCount}`);
}

var fooBtn = document.getElementById("foo-btn");
var barBtn = document.getElementById("bar-btn");

// each click handler is an Articulated `onClick()`
fooBtn.addEventListener("click",TNG(onClick),false);
barBtn.addEventListener("click",TNG(onClick),false);

Run Demo

Note: Unlike React, TNG does not require naming Custom Hooks in the format useWHATEVER(..) with a use prefix. You can do so if you prefer, as we did in the above snippet. See the rules of TNG hooks below.

The useHitCounter(..) Custom Hook -- again, just a normal non-Articulated Function that uses a TNG hook like useState(..)! -- inherits the TNG hooks-context of the Articulated Function that invoked it. In this example, the invoking Articulated Function is either one of the two click handlers (produced via the two TNG(..) calls) that were bound, respectively, as each button's click handler.

In other words, the line var [count,updateCount] = useState(0); acts as if it had actually been called inside of one of the click handlers, even though it's in the separate useHitCounter(..) function; that's what makes useHitCounter(..) a Custom Hook, meaning it can be called from any Articulated Function.

Hook Call Rules

Similar to the rules of React's hooks, there are some rules/guides that you should keep in mind when using TNG-Hooks.

  1. All TNG hooks must always be called in the same order within an Articulated Function (and any Custom Hooks it calls). That is, you must never have an invocation of an Articulated Function that skips over an earlier hook call and tries to invoke one of the subsequent hook calls. THIS WILL BREAK!

    However, it is still technically possible to have hook calls in conditional situations (or even loops!), as long as you are very careful to never skip calls in an unsafe ordering manner.

    If you have three hook calls (A, B, and C) in a function, these are the valid call ordering scenarios:

    • A, B, C
    • A, B
    • A

    Even though stopping short in the calling order is possible, it's still a best practice for reducing confusion to always call A, B, and C; avoid stopping short if possible.

    Moreover, these are invalid calling order scenarios that definitely will break:

    • B, C
    • A, C
    • B
    • C
  2. To avoid tripping on the intricasies of those calling order scenarios, it is strongly recommended that you only call TNG hooks from the top-level of the function, not inside of any loops or conditional statements.

    This is considered a best practice in terms of readability of your functions. But it also happens to be the easiest way to ensure that the hooks are always called, and thus always called in the same order, which is critical as described above.

  3. Custom Hooks do not have to be named like useXYZ(..) with a use prefix. However, it's a good suggestion to do so, because it keeps in line with the conventions from React's "Custom Hooks".

Environment Support

This utility uses ES6 (aka ES2015) features. If you need to support environments prior to ES6, transpile it first (with Babel, etc).

npm Package

npm install tng-hooks

And to require it in a node script:

var { TNG, useState, useReducer, /* .. */ } = require("tng-hooks");

Builds

Build Status npm Module

The distribution library file (dist/tng-hooks.js) comes pre-built with the npm package distribution, so you shouldn't need to rebuild it under normal circumstances.

However, if you download this repository via Git:

  1. The included build utility (scripts/build-core.js) builds (and minifies) dist/tng-hooks.js from source. The build utility expects Node.js version 6+.

  2. To install the build and test dependencies, run npm install from the project root directory.

    • Note: This npm install has the effect of running the build for you, so no further action should be needed on your part.
  3. To manually run the build utility with npm:

    npm run build
    
  4. To run the build utility directly without npm:

    node scripts/build-core.js
    

Tests

A comprehensive test suite is included in this repository, as well as the npm package distribution. The default test behavior runs the test suite using src/tng-hooks.src.js.

  1. You can run the tests in a browser by opening up tests/index.html (requires ES6+ browser environment).

  2. The included Node.js test utility (scripts/node-tests.js) runs the test suite. This test utility expects Node.js version 6+.

  3. Ensure the test dependencies are installed by running npm install from the project root directory.

    • Note: Starting with npm v5, the test utility is not run automatically during this npm install. With npm v4, the test utility automatically runs at this point.
  4. To run the test utility with npm:

    npm test
    

    Other npm test scripts:

    • npm run test:dist will run the test suite against dist/tng-hooks.js instead of the default of src/tng-hooks.src.js.

    • npm run test:package will run the test suite as if the package had just been installed via npm. This ensures package.json:main properly references dist/tng-hooks.js for inclusion.

    • npm run test:all will run all three modes of the test suite.

  5. To run the test utility directly without npm:

    node scripts/node-tests.js
    

Test Coverage

Coverage Status

If you have Istanbul already installed on your system (requires v1.0+), you can use it to check the test coverage:

npm run coverage

Then open up coverage/lcov-report/index.html in a browser to view the report.

To run Istanbul directly without npm:

istanbul cover scripts/node-tests.js

Note: The npm script coverage:report is only intended for use by project maintainers. It sends coverage reports to Coveralls.

License

All code and documentation are (c) 2019 Kyle Simpson and released under the MIT License. A copy of the MIT License is also included.

tng-hooks's People

Contributors

aarongarciah avatar getify avatar tannerlinsley avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tng-hooks's Issues

Support: `useRef(..)` hook

This: https://reactjs.org/docs/hooks-reference.html#useref

And specifically: https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables

One place we might deliberately diverge from React here is not to name the default property on the reference (object) as current. Since TNG doesn't have a ref JSX attribute, there's no magic population of some DOM element context. A better name for the property that better matches how this will likely be used in TNG is value I think.

So the initialValue passed in can just be added as .value to the reference (object).

We can just mention in the docs that you can add whatever other property(s) you want to this reference (object), including .current if preferred.

IIUC, I think useRef(..) can just be a super simple wrapper around useState(..):

function useRef(initialValue) {
   var [ref] = useState({ value: initialValue, });
   return ref;
}

Feature: `useRender(..)`

This hook is basically exactly like useEffect(..) except that it specifically defers its behavior asynchronously until the render frame (via requestAnimationFrame(..)).

Support: useEffect(..) hook

Support a useEffect(..) hook similar in spirit to React's useEffect(..) hook. Potentially rename ours to useDeferred(..) to better reflect what it does.

The asynchronous deferral that should occur will not be as controlled as with React, since they base its execution on the React lifecycle. It could be as simple as setTimeout(..0) / setImmediate(..) / process.nextTick(..), or Promise.resolve().then(..) (microtask), or possibly just requestAnimationFrame(..). Need to figure out which async deferral strategy makes sense, or which combination of them for cross-platform.

It's critical that the deferred effect is given the same hook context as the parent function, so that it can use useState(..), etc.

Similar to React's implementation, useEffect(..) needs to take a list of state variables to determine if the deferred effect should run that time or not. It only runs if the list is omitted, or if the list contains different state values than the last time it ran. If the list is empty, the hook only runs the first time.

For example:

function foo(text) {
   var [el,setEl] = useState(null);

   useEffect(
      function onInitialRender(){
         var [el,setEl] = useState(null);
         setEl(el = document.createElement("span"));
         document.body.appendChild(el);
         el.addEventListener(.....);

         // manually trigger a re-render
         foo(text);
      },
      []  // only run this hook once at the beginning
   );

   useEffect(
      function onReRender() {
         el.innerHTML = text;
      },
      [el,text]   // only run when either text or el have changed
   );
}

Also, if the deferred effect itself returns a cleanup function, this function will be saved and run right before the next deferred effect runs.

One tricky thing to work out: when will the "last" cleanup function be run? Since we don't have a lifecycle, there's no event to trigger that. It could be that we just never fire it, or that we need to provide some programmatic way to trigger "finalization" of a function. Maybe all TNG-wrapped functions have an additional .finalize() on them?

Change: externalize TNG hooks-context

[Updated with suggestions from down-thread]

Proposal: External TNG Hooks-Context

Going along with some of the ideas proposed around hooks being designed as "algebraic effects",

Inspired by the idea of an "IO Monad", I'm contemplating the idea of changing TNG hooks-context to work externally to the Articulated Function (AF) rather than being saved internally. This would seemingly make hooks both more compatible with FP, but also make them more testable and debuggable.

I'm opening this issue to start sketching out ideas for what that might look like, and take feedback/debate on if that's a useful direction for TNG to go.

Here's a basic example as TNG-hooks currently works:

function foo(val = 3) {
   var [x,updateX] = useState(val);
   updateX(v => v + 1);
   useEffect(function(){
      console.log(`x: ${x + 1}`);
   });
}

foo = TNG(foo);

foo();     // x: 4
foo();     // x: 5
foo();     // x: 6

Overview

So here's what I'm now considering instead. If you call an AF directly (aka, with no context), and don't apply the effects from its resulting hooks-context, the AF itself still runs, but there's nothing observable as output.

function foo(val = 3) {
   var [x,updateX] = useState(val);
   updateX(v => v + 1);
   useEffect(function(){
      console.log(`x: ${x + 1}`);
   });
}

foo = TNG(foo);

foo();    // hooks-context object returned, but nothing printed to console
foo();    // ditto
foo();    // ditto

The effects didn't run, so that's why there were no log statements.

Each time you invoke an AF, it returns a resulting TNG hooks-context object. If you call effects() on it, its pending effects will be applied:

var context = foo();
context.effects();  // x: 4
context = foo();
context.effects();  // x: 4
context = foo();
context.effects();  // x: 4

But notice that each direct invocation of an AF without a hooks-context object passed in as the first argument will then start with its own brand new hooks-context (thus printing "x: 4" each time). :(

So, if you provide a hooks-context object as the first argument, the AF will adopt that hooks-context initially:

context = foo().effects();    // x: 4
context = foo(context).effects();    // x: 5
context = foo(context).effects();    // x: 6

Here, the second call to foo(..) included passed along the resulting context hooks-context from the previous foo() invocation, meaning it adopted that context state to start from.

Note: The effects() method returns the same hooks-context object as well, to make chaining of these method calls more ergonomic.

this === Current Hooks Context

One impact of this change will be that, to avoid intruding on the function signature (parameter list) of the original (non-Articulated) function, we're instead hijacking its this binding to "pass in" the (new) current TNG hooks-context.

Consider:

// Note: original `foo(..)` signature here doesn't have to change to 
// include the hooks-context object
function foo(x,y,z) {
   var hooksContext = this;    // `this` is the current hooks-context

   var [sum,updateSum] = useState(0);
   sum += x + y + z;
   updateSum(sum);
   console.log(`sum: ${sum}`);
}

foo = TNG(foo);

var context = foo(1,2,3);   // sum: 6

// Note: by passing a hooks-context as the first argument,
// it'ss captured by TNG, and set to the underlying `this`, instead
// of being passed in as a formal parameter.
context = foo(context,3,4,5);   // sum: 18

There are certainly trade-offs here, and it probably won't be a popular decision. But at the moment I think it's the right balance.

The benefit of the this approach is that the original foo(..) signature doesn't have to change to accommodate passing in the hooks-context. The downside is that AFs can't use their own this context. That shouldn't be too much of a limitation, though, as TNG is really designed to be used on stand-alone functions, not this-aware methods, anyway.

Return Values

We also obscure the ability to have AFs return values, since they implicitly always return a hooks-context object. We'll solve this by setting a return property on the context object which holds whatever value (if any) that is returned from that AF call.

Auto Wrapped Context

It's also a bit more inconvenient to have to pass the context at each call-site and call .effects() after, just to keep an AF stateful. So, for convenience, we can produce an automatically context-wrapped version of an AF, so that it works as it did in the original TNG design:

function foo(val = 3) {
   var [x,updateX] = useState(val);
   updateX(v => v + 1);
   useEffect(function(){
      console.log(`x: ${x + 1}`);
   });
}

foo = TNG.auto(foo);     // <-- see .auto(..)

foo();   // x: 4
foo();   // x: 5
foo();   // x: 6

This would generally be discouraged (given the downsides to testability and debuggability that motivated this whole change), but still provided for convenience and legacy reasons. And just for illustrative purposes here, auto() is basically a simple helper like:

TNG.auto = function auto(fn){
   var context;
   fn = TNG(fn);
   return function wrapped(...args){
      context = fn(context,...args).effects();
   };
}

Example: Using Hooks-Context to re-render

function hitCounter(btnID) {
   // accessing the current TNG hooks-context
   var hooksContext = this;

   var [count,updateCount] = useState(0);
   var elem = useRef();

   useEffect(function onInit(){
      elem.current = document.getElementById(btnID);
      elem.current.addEventListener("click",function onClick(){
         updateCount(x => x + 1);

         // re-render the button
         hitCounter(hooksContext,btnID).effects();
      },false);
   },[btnID]);

   useEffect(function onRender(){
      elem.value = count;
   },[count]);
}

hitCounter = TNG(hitCounter);

hitCounter("the-btn").effects();   // button initially says: "0"

// click the button, it now says: "1"
// cilck the button again, it now says: "2"
// cilck the button yet again, it now says: "3"

Note: an AF has its this bound to its own new current TNG hooks-context (not the previous context that was used to invoke the AF). In the above snippet, that value is saved as hooksContext for internal access, in this case for the re-render that the click handler does later; hooksContext will be the same object that's returned from the current invocation of the AF.

Hook Events

Also, to address the concerns of #15, we'd need a way to be "notified" in some way of all the state changes, effects, and cleanups.

We'll expose a subscribe(..) on each AF if you want to be notified of any of these. These events are fired asynchronously (on the next microtask tick).

Note: subscribe(..) is useful for a variety of tasks, from debugging, to testing, to wiring up lifecycle management for "components".

Consider:

function foo(val = 3) {
   var [x,updateX] = useState(val);
   updateX(v => v + 1);
   useEffect(function(){
      updateX(v => v + 1);
      console.log(`x is now: ${x + 2}`);
      return function(){ console.log(`cleaning things up: ${x + 2}`); };
   });
}

function onStateChange(hooksContext,hookIdx,prevValue,newValue) {
   console.log("** state:",hookIdx,prevValue,newValue);
}

function onEffect(hooksContext,effectIdx,effectFn) {
   console.log("** effect:",effectIdx);
}

function onCleanup(hooksContext,cleanupIdx,cleanupFn) {
   console.log("** cleanup:",cleanupIdx);
}

foo = TNG(foo);
foo.subscribe({ state: onStateChange, effect: onEffect, cleanup: onCleanup, });

var context = foo().effects();
// x is now: 5

context = foo(context).effects();
// cleaning things up: 5
// x is now: 7

context.reset();
// cleaning things up: 7
// ** state: 0 3 4
// ** effect: 0
// ** state: 0 4 5
// ** state: 0 5 6
// ** cleanup: 0
// ** effect: 0
// ** state: 0 6 7
// ** cleanup: 0

Note: There's an unsubscribe(..) to undo subscriptions to an AF's events.

Hooks-Context Lifecycle

Because the state of a hooks-context can be mutated asynchronously (via state updaters), especially from effects, this introduces a lot uncertainty in race conditions between one version of the state context and the next. This chaos needs to be avoided (avoidable).

A hooks-context must have a defined set of lifecycle states, with clear progression; certain operations must only be allowed for certain states.

The lifecycle of a hooks-context object is:

  1. Open: A new hooks-context object is implicitly created in an Open state whenever an AF is invoked without a hooks-context. Also, calling reset() on any non-Open hooks-context object transitions it back to the Open state.

    • can be passed to an AF as its hooks-context
    • hooks can register new slots (useState(..), useEffect(..), etc)
    • state updaters can modify state slot values
    • reset() should not be called; will silently do nothing
    • effects() cannot be called; will throw an exception
  2. Active: If an AF is invoked with a previously Ready hooks-context object, it transitions to the Active state and remains in that state throughout the execution of the AF.

    • cannot be passed to an AF as its hooks-context; will throw an exception
    • hooks cannot register new slots; will throw an exception
    • state updaters can modify state slot values
    • reset() can be called
    • effects() cannot be called; will throw an exception
  3. Pending: A hooks-context object transitions to the Pending state at the end of an AF's execution, if any pending effects scheduled on that hooks-context.

    • cannot be passed to an AF as its hooks-context; will throw an exception
    • hooks cannot register new slots; will throw an exception
    • state updaters can modify state slot values
    • reset() can be called
    • effects() must be called to invoke pending effects and transition out of the Pending state
  4. Ready: A hooks-context object transitions to Ready immediately at the end of an AF's execution, but only if no pending effects were scheduled on that hooks-context. Also, a Pending hooks-context object transitions to Ready once effects() is called, and all effects have been invoked.

    • can be passed to an AF as its hooks-context
    • hooks cannot register new slots; will throw an exception
    • state updaters can modify state slot values
    • reset() can be called
    • effects() cannot be called; will throw an exception

Feature: `usePromisify(..)`

The usePromisify(..) hook would wrap/lift a callback-expecting function as a promise-returning function. It would expect callback-last, and err-first-callback.

Similar to Node's util.promisify(..), this hook would allow overriding of that assumption via a Symbol added to the function in question, to define its own promisified version. In that case, the usePromisify(..) hook would just return that value and do nothing else.

The benefits of this as a hook:

  1. Since it would store the promisified version in a state slot, it allows you to promisify inline function expressions safely without unnecessary work (the wrapping would only happen the first time).

    Just like useMemo(..) and useEffect(..), we'd allow optional guards to override and re-define, if closure was required.

  2. It doesn't require polluting the outer/global namespace with a promisified version of the function. In other words, it allows you to only locally create a promisified override without affecting the rest of the program.

Support: function passed to a setter/updater

Support passing a function to a setter/updater (like setX(..) below), and if passed, it's invoked with the previous value of that state unit, and its return value is used as the new value for that state unit.

function foo() {
   var [x,setX] = useState(0);

   setX(function onSet(prevX) { return prevX + 1; });
}

Feature: hooks fire events

I have an idea for a couple of features that I think would make debugging and testing of hooks much better. Want to sketch them out here and get feedback.

  1. First, I'm thinking these features would be turned off by default, mostly because they have some performance implications.

    One way of toggling this on and off would be to just have separate builds of the library. So, like a "debug" build of the code has all the extra bits in it, but the production version wouldn't have those bits included.

    Another option is to have a flag (like TNG.debug) that you set to true to trigger the behavior.

  2. Once this mode is on, TNG would fire an event every time a hook is called, or some other thing alters one of the internal state contexts, such as an effect or cleanup running, or a state-slot updater running, etc. The event would include references to the parent Articulated Function instance, which hook, what kind of event it was, and any relevant provided values.

    The idea behind this is that you could audit a hook, including basically knowing the internal state of its hooks-context by replaying all these events. This would allow you to easily create verification tests to ensure your hooks are doing what they're supposed to be doing.

  3. It also would be helpful if there was a way to provide a list of these events to an Articulated Function and have it preload its context state. This lets you basically re-create a specific hooks context at will. That makes creating tests even more powerful.

    What I'm thinking is, this list of events could be an optional argument passed to an Articulated Function's reset(..) method.

So... thoughts?

useEffect that works with DOM/UI

I understand the general purpose of this library (pretty identical to augmentor) but useEffect as it is makes no sense for anything UI related, since it requires machinery around.

dom-augmentor uses augmentor flexibility to provide such usability, is TNG also planning to offer DOM/UI related hooks that work when nodes are connected or disconnected * from the DOM?

* connected and disconnected as in Custom Elements definition

Feature: `useDeferred()`

The useDeferred() hook creates and stores in a state slot (via useState(..)) an object, commonly called a deferred, which is a promise and also its resolve(..) / reject(..) capabilities.

function foo() {
   var { pr, resolve, reject } = useDeferred();
}

Unclear if there's any pattern that would benefit from passing a value to useDeferred(..). Need to further consider.

why `tngStack`

If I am correct, the size of tngStack is never larger that 1, so why not put something like currentTng? why use an array and push and pop every time?

useState(..) Hook example uses ++ in a confusing way

In the following code the ++count is used only to print 1 instead of 0, 2 instead of one, since the actual state changing occurs on updateCount.
If that's on purpose, then it should be replaced with count + 1

As is this example is just confusing because ++count looks like an attempt to change state

function hit() {
    var [count,updateCount] = useState(0);

    updateCount(onUpdateCount);

    console.log(`Hit count: ${++count}`);
}

function onUpdateCount(oldCount) {
    return oldCount + 1;
}

hit = TNG(hit);

hit();       // Hit count: 1
hit();       // Hit count: 2
hit();       // Hit count: 3

Feature: `useDebounce(..)` and `useThrottle(..)`

For posterity sake, let's clarify that throttling and debouncing are in fact distinct, while still clearly being related.

But both of these seem like worthwhile decorative hooks to add, which allow you to ensure that an Articulated Function is invoking another function only under certain time-based thresholds.

For example, it could be useful to wrap an effect with one of these hooks.

Here's how I envision them working:

function init() {
   var theBtn = useState(null);
   var throttled = useThrottle(tap,100);
   var debounced = useDebounce(hit,300);

   useEffect(function onInit(){
      theBtn = document.getElementById("the-button");
      theBtn.addEventListener("mousedown",throttled,false);
      theBtn.addEventListener("mouseup",debounced,false);
   },[]);
}

function tap() {
   console.log("tap!");
}

function hit() {
   console.log("hit!");
}

init = TNG(init);

In this code, as you clicked on the button really fast, you would see "tap!" printed in the console at most once per 100ms, but the "hit!" message wouldn't be printed until all it had been at least 300ms since the last "mouseup".

These two hooks also need to support an optional third parameter, a guards-list, similar to useEffect(..) and useMemo(..), which conditionally redefines the debounced/throttled function if the guards have changed (aka, if closure is required).


Implementation wise, I see useThrottle(..) and useDebounce(..) as convenience wrappers around useState(..), in that they create and store a throttled/debounced function in a state slot (so that work only happens the first time), and thereafter just keep returning a reference to that same function. That makes it "safe" even for inline/nested functions -- unlike the useMemo(..) hook (which reacts to different function references even if they're "the same function").

Support: useReducer(..) hook

Support something like React's useReducer(..) hook, but without the abstraction of dispatch(..) and actions, so it's basically a bit of sugar for the functionality provided by passing a function to a state unit's setter/updater.

IOW, this:

function foo() {
   var [activated,setActivated] = useState(false);
   var [el,setEl] = useState(null);
   var [x,setX] = useState(1);

   if (!activated) {
      setActivated(true);
      setEl(el = document.createElement("span"));
      document.body.appendChild(el);
      el.addEventListener("click",function onClick(){
         setX( function increaseX(prevX) { return prevX + 3; } );
      },false);
   }
}

Could then instead be done a little bit nicer, like this:

function foo() {
   var [activated,setActivated] = useState(false);
   var [el,setEl] = useState(null);
   var [x,increaseX] = useReducer( function computeNewX(prevX,value) { return prevX + value; }, 1 );

   if (!activated) {
      setActivated(true);
      setEl(el = document.createElement("span"));
      document.body.appendChild(el);
      el.addEventListener("click",function onClick(){ increaseX(3); },false);
   }
}

TNG name

Wondering why the decision was made to make TNG all caps? It's feels slightly unidiomatic, requires holding down the shift key, etc. I saw there was the name bikeshedding issue, but just wondering what the reasoning was and if it's really necessary? This is such a cool idea and it seems like a worthy aim to keep things as vanilla as possible and slightly more literal. Like what is "tng"? Why not "makeHooks" or import { useState } from 'fn-hooks'? etc. To invoke everything with "TNG" doesn't seem meaningful language-wise.

Further clarification for useEffect() hook

IMHO the phrase below should be rephrased as it causes confusions for some.

While not required, it's a very good idea and best practice to always pass the same guard list to an effect (even though the values can and do change)

React docs have this api better worded, specifically mentioning the usage (only) for performance optimizations. Most people see a "best practice" as a standard way of doing something, but this is not necessarily true about this api.

Maybe just by not including "best practice" we can avoid any confusions but I'll let you handle it.

confusing phonetic transcription

I'm supposing hooks is pronounced /hʊks/ as usual but now I'm not certain how to pronounce TNG, is that notation supposed to be IPA or is it some more obscure standard?

Name Bikeshedding

I would like to come up with a specific branded name for TNG-wrapped function, because that phrase is too cumbersome and not well-descriptive of its nature. The analog of it, in React, is a "component", or more specifically "stateful function component".

I don't imagine that making TNG-wrapped functions will be limited to rendering components. But that's certainly one of the many possible use cases. So I don't necessarily want to limit them with "component" or "component function" or whatever.

Some names to consider:

  • "TNG function" -- don't really like that, since TNG(..) is a specific utility, a function, that wraps the functions in question
  • "stateful function" -- this is decent, and describes its main characteristic well, but it's also kinda blah
  • "TNG context" / "TNG context function" / "contextual function" -- I describe the "state" that a TNG-wrapped function gets, including its state slots, as a "context"; this may be a bad idea to mix with the more typical this meaning for the term "context"

Other thoughts?

Refactor useState (or useReducer)

useState could be implemented using useReducer (and vice versa). Using one to implement the other may make the code easier to read/maintain.

React does something like this:

function useState(initialVal) {
  return useReducer(
    (state, action) => (typeof action === "function" ? action(state) : action),
    initialVal
  );
}

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.