Git Product home page Git Product logo

nestedlink's Introduction

logo

Painless React forms, validation, and state management

NestedLink is useState React Hook on steroids providing an elegant callback-free solution for complex forms with input validation and making the React state a perfect state container. It's lightweight (6.5K minified) and designed to be used with both JS and TypeScript.

The Link is the object representing the writable reference to the member of the component's state encapsulating the value, function to update the value, and the validation error. Link class has a set of methods to perform useful transformations, such as $link.props generating the pair of standard { value, onChange } props.

NestedLink dramatically improves your React project's modularity and code readability.

import { useLink } from 'valuelink'
import { MyInput } from './controls.jsx'

const coolState = { some : { name : '' } };
const MyCoolComponent = () => {
    // Digging through the object to get a link to the `coolState.some.name`
    const $name = useLink( coolState ).at( 'some' ).at( 'name' )
    
    // applying the validation rules
    $name.check( x => x.length > 0, 'Name is required' ),
         .check( x => x.length > 2, 'Name is too short' );
         .check( x => x.length < 20, 'Name is too long' );

    return (
        <MyInput $value={$name} />
    )
}

// controls.jsx
import * as React from 'react'

// Custom form field with validation taking the link to the `value`
const MyInput = ({ $value }) => (
    <div>
        <input {...$value.props} className={ $value.error ? 'error' : '' } />
        <span>{ $value.error || '' }</span>
    </div>
)

Features

IMPORTANT! Version 2.x is not entirely backwards compatible with 1.x, see the release notes at the bottom.

  • Callback-free form controls binding to the component state.
  • Complete separation of the validation logic from the markup.
  • Easy handling of nested objects and arrays in the component state.
  • Complete support for the React Hooks API and functional components.
  • Pricise TypeScript typings.

Reference implementation of 'linked' UI controls (optional linked-controls npm package):

  • Standard tags: <Input />, <TextArea />, <Select />,
  • Custom tags: <Radio />, <Checkbox />, <NumberInput />
  • Validator functions: isNumber, isEmail, isRequred.

Tutorials

The rationale behind the design and a high-level overview of how amazing NestedLink is: React Hooks, form validation, and complex state

The series of 5-minute tutorials (with React.Component):

How to

Use it in your project

There are no side dependencies except react as peer dependency. Installation:

npm install valuelink --save-dev

Usage with React Hooks (check out the React Hooks starting boilerplate).

import React from 'react'
import { useLink } from 'valuelink'
...
// Instead of const [ name, setName ] = useState('')
const $name = useLink('');

Usage with React Component.

import React from 'react'
// Instead of React.Component...
import { LinkedComponent } from 'valuelink'
...
// In a render, do
const $name = this.$at('name');
// Or, to link all the state members at once...
const state$ = this.state$();

Refer to the databinding examples and the manual for the typical data binding scenarios.

Create your own data bound controls

Use linked-controls project as the starting boilerplate for your components.

Create the binding to the custom state container

NestedLink is an abstraction of the data binding independent on both the particular control and the state container. The default binding implemented in the library is for the standard React state. It's fairly easy to create your own.

You need to subclass React.Component and make your own $at and state$ methods. You can either use Link.value inside to create links dynamically, or extend the Link as it's done in /valuelink/src/component.ts.

Start hacking

design

It's a very simple library written with TypeScript, there's no any magic inside (except some scary type annotations). If you want to play with the examples, fix the bug, or whatever:

yarn - installs the dependencies.

yarn build - compiles everything including examples.

Release Notes

2.0

  • IMPORTANT: Repository is converted to the monorepository based on yarn worspaces.
  • IMPORTANT: valuelink/tags.jsx is moved to the dedicated package linked-controls.
  • Complete support of new React Hooks API.
    • useLink() to create the state link.
    • useIO() to perform promised IO on mount.
    • useLocalStorage() to persist links to the local storage (loaded on mount, saved on unmount).
  • $-notation for the link variables.
  • New React.Component API (this.linkAt -> this.$at, this.linkAll -> this.state$)
  • Group operations Link.getValues(), Link.setValues(), Link.getErrors()

v1.6

React Hooks support.

  • useLink( initValue ) - create linked state.
  • setLinks({ lnk1, lnk2, ... }, json ) - bulk set link values from an object.
  • linksValues({ lnk1, lnk2, ... }) - extract values object from links.
  • linksErrors({ lnk1, lnk2, ... }) - extract errors object from links.

v1.5

  • <input {...link.props} /> can be used to bind the link to any of the standard controls expecting value and onChange props.

usedby

nestedlink's People

Contributors

adi2412 avatar iterpugov 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

nestedlink's Issues

Allow side effects in link.update( )

When you update the link to object or array, you still have to return the value.

link.update( obj => { delete obj[ key ]; return obj; } )

Which is not good. Stupid code is always not good. It should look (and still work) like this:

link.update( obj => delete obj[ key ]  )

Then it will be perfect. In the next minor version.

Add custom validation pattern to the docs

The pattern is to override component.linkAll().

  • Call linkAll of the base class to produce the relevant links.
  • Add custom validation checks.
  • If needed, compute the validationError and add it to the cache object.
  • return cache object.

It's damn cool pattern which deserves to be reflected in tutorials.

export valuelink/tags.jsx as valuelink/tags

exporting a jsx file would require a compilation step for node_modules in other apps, other projects/react packages never export jsx file directly, they export the compiled version of jsx -> js, just like the export of valuelink.js

LinkAll with React.useState hook

Thanks for the great library!

I typically link all state by using inheritance from LinkedComponent. It is a way based on class inheritance. It works. However, I have found that React Hooks from (16.8) simplify code a lot but they require components to be functions, not classes. I would like to use something similar to this.linkAll() but within functions where I could combine the links with React.useState(). I am trying to achieve something similar to the following. How should I do it properly?

const MyComponent = (props: { data: { field1: string }}) => {
    const [state, setState] = React.useState(props.data);
    const links = AnalogyOfLinkAll(state, setState); // <----- How can I do this?
    return <input value={links.field1.value} onChange={(v) => links.field1.set(v)} />;
};

Could not find a declaration file for module 'valuelink/tags'

I use import { Select } from "valuelink/tags"; and get this typescript error

Error:(5, 24) TS7016: Could not find a declaration file for module 'valuelink/tags'. '/home/---/node_modules/valuelink/tags.js' implicitly has an 'any' type.
  Try `npm install @types/valuelink` if it exists or add a new declaration (.d.ts) file containing `declare module 'valuelink';`

Bug with Input, checkedLink and set/onChange/etc

So I have been debugging this for my coworker and I think I discovered a bug with Input.

The situation is that we have two checkboxes. If the first checkbox is ticked, it should set the second checkbox to unticked.

Functional code:

// componentLink = Link.state((parent_obj), 'component_data')
// componentLink.value = {
//   'first_checkbox_key': { 'enabled': [true/false] }
//   'second_checkbox_key': { 'enabled': [true/false] }
// }
const Container = (props) => {
  const { links } = props;  // Directly passing componentLink in props also works
  const { componentLink } = links;
  return
    <div>
      <FirstCheckbox links={links}/>
      <SecondCheckbox links={links} />
    </div>
};

const FirstCheckbox = (props) => {
  const { links } = props;
  const { componentLink } = links;
  const secondEnabledLink = componentLink.at('second_checkbox_key').at('enabled');
  const firstEnabledLink = componentLink.at('first_checkbox_key').at('enabled');
  const change = firstEnabledLink.action((old, event) => {
    const checked = event.target.checked;
    firstEnabledLink.set(checked);
    if (checked) {
      secondEnabledLink.set(false); 
    }
  });
  return <div>
      <input type='checkbox' value={firstEnabledLink.value} onChange={change}/>
  </div>
};

const SecondCheckbox = (props) => {
  const { links } = props;
  const { componentLink } = links;
  const secondLinkEnabled = componentLink.at('second_checkbox_key').at('enabled');
  return <div>
      <Input type='checkbox' checkedLink={secondLinkEnabled}/>
  </div>
};

tags.jsx/Input and CheckedLink doesn't work
Replacing the first checkbox with a checkedLink using Input from tags.jsx and an onChange doesn't work.

  const firstEnabledLink = componentLink.at('first_checkbox_key').at('enabled')
   .onChange((enabled) => {
     console.log("I was hit", enabled);
     if (enabled) {
       console.log("I am disabling thingy");
       secondLink.set(false);
       console.log(secondLink.value);  // Remains 'true'
    }
  });
  ...
  <Input type='checkbox' checkedLink={firstEnabledLink}/>

Adding an onChange function the normal way also doesn't work

 const handle_click = (firstLink) => {
   return function (e) {
     const val = e.target.value;
     if (val === 'false') {
       firstLink.value = false; //Also tried .set(false), .update((x)=> false), .action
       console.log(firstLink.value);
     }
   }
 };
 ...
 <Input type='checkbox' checkedLink={firstEnabledLink} onChange={handle_click(firstEnabledLink)}/>

Is there a way to handle `undefined` values?

For example:

class Edit extends LinkedComponent {
   state = {}
   render() {
   return (<div><Input valueLink={ this.linkAt('name')}</div>)
}

I would like to set the state when the name value is changed by the user

onChange is called before state is changed

When using valuelink on a dropdown with something like

this.linkRole = VLink.state(this, "role").onChange(this.onChangeRole);
<Dropdown selectedKey={valueLink.value} 
onChange={(event, option, index) => valueLink.set(option.key)} 

the onChange function is called before this.state.role is set, giving a value which is different from the one still in the state.
In some situations this is a problem because we know the value has changed but the state still has the old value - how can we wait for the state change to happen so that the state reflects the current drop down selection?

Link to ES6 object does not work

Following code do this. You can add car to the list of cars. You can change manufacturer name. When I have defined items in state as ES6 classes then I cannot change the manufacturer name. With literal objects everything works fine. Is this bug or I do something wrong?

import React from 'react';
import ReactDOM from 'react-dom';
import Link from 'valuelink';
import { Input } from 'valuelink/tags';

class CarModel {
    constructor() {
        this.manufacturer = 'default';
    }
}

class Car extends React.Component {
    render() {
        let carLink = this.props.carLink;
        let val = carLink.value.manufacturer;
        return (
            <li><Input type="text" valueLink={ carLink.at('manufacturer') } /> { val }</li>
        )
    }
}

class CarList extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            cars: []
        };
    }

    handleAdd() {
        let cars = this.state.cars;
        cars.push(new CarModel());

        this.setState({ cars })
    }

    render() {
        let cars = Link.state(this, 'cars').map((car, i) => (
            <Car key={ i } carLink={ car } />
        ));

        return (
            <div>
                <ul>{ cars }</ul>
                <button onClick={ this.handleAdd.bind(this) }>Add Car</button>
            </div>
        )
    }
}

ReactDOM.render(
    <CarList />,
    document.getElementById('root')
);

Uncaught SyntaxError: Unexpected token import

As in the docs, I use import { Input } from 'valuelink/lib/tags'
It's transpiling fine, but in the browser code I get:

Uncaught SyntaxError: Unexpected token import
BTW in your README.md it's still import { Input } from 'valuelink/tags.jsx', but that file does not exist anymore in the NPM?

Module not found: Can't resolve 'tslib'

I'm getting the "Module not found" build error after installing valueLink and linked-controls in my NextJS project. I have checked the module directory, and ./node_modules/valuelink doesn't have a node_modules subdirectory, so I wonder how it is supposed to work. Are we supposed to have tslib available globally? I believe it's added when you create a CRA project, but it's definitely not present in my NextJs project.

If we need to add it manually, do you want to update your instructions?

Here's the full output:

> [email protected] dev C:\Projects\Galaxias\file-storage-ui
> next dev

ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info  - Using webpack 4. Reason: future.webpack5 option not enabled https://nextjs.org/docs/messages/webpack5
event - compiled successfully
event - build page: /
wait  - compiling...
error - ./node_modules/valuelink/lib/link.js:1:0
Module not found: Can't resolve 'tslib'
null
Could not find files for / in .next/build-manifest.json
Could not find files for / in .next/build-manifest.json
Error from chokidar (C:\): Error: EBUSY: resource busy or locked, lstat 'C:\DumpStack.log.tmp'
Error from chokidar (C:\): Error: EBUSY: resource busy or locked, lstat 'C:\hiberfil.sys'
Error from chokidar (C:\): Error: EBUSY: resource busy or locked, lstat 'C:\pagefile.sys'
Error from chokidar (C:\): Error: EBUSY: resource busy or locked, lstat 'C:\swapfile.sys'
event - compiled successfully

Allow empty input on NumberInput

Hello,

I'm using NumberInput because it strips out letters and is convenient. However I noticed that if I delete the number it does not in fact update the link associated with it. I use validate.js against the json object everything links to when the user submits to validate input. Thus it can result in differences between what is shown on the screen and the data. It would be good to have a prop on NumberInput to allow empty input to change the link (can set it to undefined or null).

Thanks!

Bug: eMail input auto trims value and resets cursor position

Steps to reproduce:

  • set up an Input of type="email" (The code is basically the same as in issue #23, with the FormInput seperated into another component. You can see the code in the Github project.)
  • type in spaces
  • type in a letter

Behaviour
All leading and trailing whitespaces are removed, and the cursor is set in front of the input value.
This behaviour seems to appear for all inputs except for spaces, if there are leading or trailing whitespaces after the user input was applied.

Expected Behaviour

  • The cursor stays at the same position within the user input.
  • The spaces are handled consistently:
    • Either: removing all whitespaces
    • Or: don't remove any whitespaces

It might still be a problem related to the setup.

Btw: I'll try to implement the other things you suggested the next days. (There are e. g. functions implemented for the validation, even though they are currently not in use.)

And thank you for your help so far. ;)

Input looses focus

Found your library via Stackoverflow, it's very useful for my current project, so thanks for releasing it as Open Source Software!

I've copied your userlist-example over to my build-env and it works, mostly. There's just one issue - I loose focus after every letter entered in the input-fields.

I thought the problem might be caused by the modal-overlay - but after implementing nestedLink-methods in my own project, I have the same issue with loosing focus in the input-fields. And I don't use a modal.
After every letter I enter, the cursor is gone and I have to click in the input-field again to enter the next letter.

The issue is probably caused by the linkToObject.at-method, which I'm using to autosubmit the input-value in conjunction with onChange, in the first input-field in this example:

render() {
    const linked = Link.all ( this, 'sequence', 'command', 'info' ),
          infoLink   = this.props.sortData.at( 'info')

    const setValue = (x, e) => e.target.value

    return (
      <div { ...this.props }>
        <span className="sequence-order">{linked.sequence.value}</span>
          <input value={ linked.info.value } onChange={ infoLink.action( setValue ) }/>
          <input valueLink={ linked.info }/>
      </div>
    );
  }

The issue doesn't appear in the second-input-field, where I'm using a "simple" valueLink. But since I don't want to add a submit-button to commit its value to the parent, I prefer the more elaborate mechanism with onChange.

I'm on:

input element loses focus after typing

Hey i'm trying to use your valueLink approach to validate my user inputs.
The issue i get is that the input elements lose focus when i type one character. So i have to click in the form a second time to write again. This happens after each character.
But there are good news. The validation itself works perfect.
I'm new to react, so it is also possible that i missed a major point.

Invalid state for the new form

It's pretty uncommon to see error messages on empty new form.

Ex in user list example, as soon as Add user button clicked.

nestedreact todomvc

Is there any way in valuelink library to postpone the validation till user navigates out of the field, or press submit button?

Question about .at and .set vs direct .value assignment

Edited with a different question since I resolved my own previous question.

Is there a difference between using .at and .set vs .value assignment, or are the two behaviors the same?

    const rulesLink = Link.state(this, 'rules');  //An array of objects
    const activeLink = Link.state(this, 'rules_active')
      .onChange((enabled) => {
        rulesLink.map((ruleLink) => {
          ruleLink.at("active").set(enabled);
          return ruleLink.value; })
      });
    //activeLink is assigned to a checkbox input

versus

    const rulesLink = Link.state(this, 'rules');
    const activeLink = Link.state(this, 'rules_active')
      .onChange((enabled) => {
        rulesLink.map((ruleLink) => {
          ruleLink.value.active = enabled;
          return ruleLink.value; })
      });
    //rulesLink is mapped over and sub components are created for each ruleLink in rulesLink

I know the following code doesn't actually update the child subcomponents which have checkboxes mapped to 'active' on each child rule which is why I am wondering

    const rulesLink = Link.state(this, 'rules');
    const activeLink = Link.state(this, 'rules_active')
      .onChange((enabled) => {
        _.each(this.rules, (rule) => rule.active = enabled);
       })

Documentation about type="radio"

Add documentation about radio types..

How can I do this..

<Input type="radio" name="radio" checked checkedLink={linked.at('image')} /> Banner
<Input type="radio" name="radio" checkedLink={linked.at('image')} /> Logo

I want a property image be a string with..

image: 'banner' or 'logo'

Validation don't reevaluates when a dependent state changes

Hi there!

I have a validation for a link that uses a different state property (not the one associated to the link I'm validating). When that external state property changes, the validation doesn't rerun. This leaves the link error property in a not trustworthy state. The external state property may leave the link with an error but I won't know until the state associated with the link changes.

A workaround for this would be having a mechanism to run validation on a link manually, but I couldn't find a way to do so. Any other suggestions?

Thanks in advance!

TypeError: Cannot read property 'value' of undefined at Input (tags.js:64)

What I'm doing wrong ?

import Link, { LinkedComponent } from 'valuelink';
import { Input } from 'valuelink/tags';
export default class extends LinkedComponent {
  state = {};
  render() {
    <Input {...rest} />
  }
}

And I'm trying to use in another component ..

import Link, { LinkedComponent } from 'valuelink';
import MyInput from 'components/MyInput';
export default class extends LinkedComponent {
  render() {
    const linked = this.linkAll();
    return <MyInput valueLink={linked.fieldTest} />
  }
}

Don't show errors on mount

Hi! Is there a way to not show errors at the first render of a component?

render() {
    const nameLink = Link.state(this, 'name')

    nameLink.check(v => v, 'Name is required')

    return (
        <form>
            <Input valueLink={nameLink} />
            // Here will be Error shown when component will mounted
        </form>
    )
}

valuelink cannot be used server side

Trying to require valuelink server-side results in an error complaining that window is not defined:

$ node
> require('valuelink')
ReferenceError: window is not defined
    at /Users/jwalton/benbria/loop/node_modules/valuelink/dist/index.js:1:982
    at n (/Users/jwalton/benbria/loop/node_modules/valuelink/dist/index.js:1:131)

It looks like this is some kind of artifact of using rollup to bundle things, because window is only referenced here, which would never get executed server side in the first place.

ShallowRendering valueLink

Hi,

when I do shallow rendering in tests for components which use valuelink the snapshots are of enoumos size.
Can you propose any solution to this? Do I need to mock valueLink?

Please see the attached example snapshot
AccountSettings.txt

LinkeComponent.links is null

I'm using NestedLink with typescript.
When I extend LinkedComponent and I want to use links member in render method, links is null.

Enhancement: Convenience onX method binding pattern for Link.Component classes

Some React components have onHide/onChange/other functions which store the functions to the DOM (beyond our control, library components). Thus if we pass it something like:

class SomeComponent extends React.Component {
  render() {
    const { someLink } = this.props;
    return <Modal onHide={someLink.action((value, event) => !value)} />
  }
}

the function bound to the onHide can get out of sync with the state (it saved a reference to the old Link object to the DOM on first creation instead of re-rendering it).

The workaround for this is to do something like

class SomeComponent extends React.Component {
  boundFunction() {
    const { someLink } = this.props;
    someLink.set(false); // Some Value
  }

  render() {
    return <Modal onHide={this.boundFunction} />
  }
}

It would be nice to have a convenience function on LinkedComponent which can "generate this boundFunction for you". Maybe the usage interface would be something like:

class SomeComponent extends LinkedComponent {
  //Implicitly from LinkedComponent
  bindLink(somePropString, callback) {
    const propLink = this.props[somePropString];
    //Or some other way to make it more generalized?
    link.update(callback);
  }

  render() {
    const { someLink } = this.props;
    return <Modal onHide={this.bindLink(somePropString, () => {})} />
  }
}

Question: how to link a property which is an array of objects

I have got the state like the following:

state = {
    places: {
        name: string,
        nearByPOI: {
             name: string,
             location: {
                 latitude: number,
                 longitude: number
             }
        }[] 
    }[]
}

I wonder how to link, for example to state->places->nearByPOI->name properties?

Optimize for shouldComponentUpdate

One of the problems with links is that they break pure render optimization.

An upcoming release of React contains deprecation message for their crappy valueLinks, which actually is the great opportunity to improve links design. We don't have to keep backward compatibility any more.

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.