Skip to content

Commit d13e501

Browse files
committed
Initial commit!
0 parents  commit d13e501

File tree

10 files changed

+487
-0
lines changed

10 files changed

+487
-0
lines changed

.editorconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
root = true
2+
3+
[*]
4+
indent_style = tab
5+
end_of_line = lf
6+
charset = utf-8
7+
trim_trailing_whitespace = true
8+
insert_final_newline = true
9+
10+
[{package.json,.*rc,*.yml}]
11+
indent_style = space
12+
indent_size = 2
13+
14+
[*.md]
15+
trim_trailing_whitespace = false
16+
indent_style = space
17+
indent_size = 2

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/dist
2+
/polyfill.js
3+
/polyfill.js.map
4+
/node_modules
5+
/npm-debug.log
6+
.DS_Store

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
language: node_js
2+
node_js:
3+
- latest

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017 Jason Miller
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# linkState
2+
3+
> Create an Event handler function that sets a given state property. Works with [preact] and [react].
4+
5+
- **Tiny:** less than **300 bytes** of [ES3](https://unpkg.com/linkstate) gzipped
6+
- **Familiar:** it's just a function that does what you would have done manually
7+
- **Standalone:** one function, no dependencies, works everywhere
8+
9+
> 🤔 **Why?**
10+
>
11+
> linkState() is memoized: it only creates a handler once for each `(key, eventPath)` combination.
12+
>
13+
> This is important for performance, because it prevents handler thrashing and avoids allocations during render.
14+
15+
* * *
16+
17+
## Table of Contents
18+
19+
- [Installation](#installation)
20+
- [How It Works](#how-it-works)
21+
- [Usage](#usage)
22+
- [Contribute](#contribute)
23+
- [License](#license)
24+
25+
* * *
26+
27+
## Installation
28+
29+
```sh
30+
npm install --save linkstate
31+
```
32+
33+
The [UMD](https://github.com/umdjs/umd) build is also available on [unpkg](https://unpkg.com/linkstate/dist/linkstate.umd.js):
34+
35+
```html
36+
<script src="//unpkg.com/linkstate/dist/linkstate.umd.js"></script>
37+
```
38+
39+
This exposes the `linkState()` function as a global.
40+
41+
* * *
42+
43+
## How It Works
44+
45+
It's important to understand what linkState does in order to use it comfortably.
46+
47+
**`linkState(component, statePath, [valuePath])`**
48+
49+
- `component`: the Component instance to call `setState()` on
50+
- `statePath`: a key/path to update in state - can be dot-notated for deep keys
51+
- `valuePath`: _optional_ key/path into the event object at which to retrieve the new state value
52+
53+
It's easiest to understand these arguments by looking at a simplified implementation of linkState itself:
54+
55+
```js
56+
function linkState(component, statePath, valuePath) {
57+
return event => {
58+
let update = {};
59+
update[statePath] = event[valuePath];
60+
component.setState(update);
61+
};
62+
}
63+
```
64+
65+
In reality, accounting for dot-notated paths makes this trickier, but the result is the same.
66+
67+
Here's two equivalent event handlers, one created manually and one created with linkState:
68+
69+
```js
70+
handleInput = e => {
71+
this.setState({ foo: e.target.value })
72+
}
73+
74+
handleInput = linkState(this, 'foo')
75+
```
76+
77+
Notice how we didn't specify the event path - if omitted, `linkState()` will use the `checked` or `value` property of the event target, based on its type.
78+
79+
## Usage
80+
81+
Standard usage is simply a function that returns an event handler to update state:
82+
83+
```js
84+
import linkState from 'linkstate';
85+
86+
class Foo extends Component {
87+
state = {
88+
text: ''
89+
};
90+
render(props, state) {
91+
return (
92+
<input
93+
value={state.text}
94+
onInput={linkState(this, 'text')}
95+
/>
96+
);
97+
}
98+
}
99+
```
100+
101+
You can also use it as a [**polyfill**](https://ponyfill.com/#polyfill). This emulates the behavior of Preact 7.x, which provided `linkState()` as a method on its `Component` class. Since you're then calling `linkState()` as a method of the component instance, you won't have to pass in `component` as an argument:
102+
103+
```js
104+
import 'linkstate/polyfill';
105+
106+
// Component.prototype.linkState is now installed!
107+
108+
class Foo extends Component {
109+
state = {
110+
text: ''
111+
};
112+
render(props, state) {
113+
return (
114+
<input
115+
value={state.text}
116+
onInput={this.linkState('text')}
117+
/>
118+
);
119+
}
120+
}
121+
```
122+
123+
124+
* * *
125+
126+
## Contribute
127+
128+
First off, thanks for taking the time to contribute!
129+
Now, take a moment to be sure your contributions make sense to everyone else.
130+
131+
### Reporting Issues
132+
133+
Found a problem? Want a new feature? First of all see if your issue or idea has [already been reported](../../issues).
134+
If it hasn't, just open a [new clear and descriptive issue](../../issues/new).
135+
136+
### Submitting pull requests
137+
138+
Pull requests are the greatest contributions, so be sure they are focused in scope, and do avoid unrelated commits.
139+
140+
> 💁 **Remember: size is the #1 priority.**
141+
>
142+
> Every byte counts! PR's can't be merged if they increase the output size much.
143+
144+
- Fork it!
145+
- Clone your fork: `git clone https://github.com/<your-username>/linkstate`
146+
- Navigate to the newly cloned directory: `cd linkstate`
147+
- Create a new branch for the new feature: `git checkout -b my-new-feature`
148+
- Install the tools necessary for development: `npm install`
149+
- Make your changes.
150+
- `npm run build` to verify your change doesn't increase output size.
151+
- `npm test` to make sure your change doesn't break anything.
152+
- Commit your changes: `git commit -am 'Add some feature'`
153+
- Push to the branch: `git push origin my-new-feature`
154+
- Submit a pull request with full remarks documenting your changes.
155+
156+
## License
157+
158+
[MIT License](LICENSE.md) © [Jason Miller](https://jasonformat.com/)

package.json

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
{
2+
"name": "linkstate",
3+
"amdName": "linkState",
4+
"version": "1.0.0",
5+
"description": "Bind events to state. Works with Preact and React.",
6+
"main": "dist/linkstate.js",
7+
"module": "dist/linkstate.es.js",
8+
"jsnext:main": "dist/linkstate.es.js",
9+
"umd:main": "dist/linkstate.umd.js",
10+
"scripts": {
11+
"test": "eslint src test && mocha -r jsdom-global/register --compilers js:babel-register test/**/*.js",
12+
"build": "npm-run-all --silent clean -p transpile:* -s size",
13+
"clean": "rimraf dist && mkdirp dist",
14+
"transpile:umd": "rollup -c --environment FORMAT:umd",
15+
"transpile:cjs": "rollup -c --environment FORMAT:cjs",
16+
"transpile:es": "rollup -c --environment FORMAT:es",
17+
"transpile:polyfill": "rollup -c --environment FORMAT:cjs src/polyfill.js -o polyfill.js",
18+
"transpile:polyfill-umd": "rollup -c --environment FORMAT:umd src/polyfill.js -o dist/polyfill.umd.js",
19+
"minify:cjs": "uglifyjs $npm_package_main -cm toplevel -o $npm_package_main -p relative --in-source-map ${npm_package_main}.map --source-map ${npm_package_main}.map",
20+
"minify:umd": "uglifyjs $npm_package_umd_main -cm -o $npm_package_umd_main -p relative --in-source-map ${npm_package_umd_main}.map --source-map ${npm_package_umd_main}.map",
21+
"size": "echo \"Gzipped Size: $(strip-json-comments --no-whitespace $npm_package_main | gzip-size)\"",
22+
"prepublish": "npm run -s build",
23+
"release": "npm run build -s && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
24+
},
25+
"repository": "developit/linkstate",
26+
"keywords": [
27+
"preact",
28+
"react",
29+
"state",
30+
"linkstate",
31+
"linked state"
32+
],
33+
"homepage": "https://github.com/developit/linkstate",
34+
"authors": [
35+
"Jason Miller <jason@developit.ca>"
36+
],
37+
"license": "MIT",
38+
"files": [
39+
"src",
40+
"dist",
41+
"polyfill.js"
42+
],
43+
"babel": {
44+
"presets": [
45+
"es2015",
46+
"stage-0"
47+
]
48+
},
49+
"eslintConfig": {
50+
"parser": "babel-eslint",
51+
"extends": "eslint:recommended",
52+
"env": {
53+
"browser": true,
54+
"mocha": true,
55+
"node": true,
56+
"es6": true
57+
},
58+
"globals": {
59+
"expect": true
60+
}
61+
},
62+
"bundledDependencies": {
63+
"dlv": "^1.1.0"
64+
},
65+
"devDependencies": {
66+
"babel-core": "^6.9.1",
67+
"babel-eslint": "^7.1.1",
68+
"babel-preset-es2015": "^6.9.0",
69+
"babel-preset-stage-0": "^6.5.0",
70+
"babel-register": "^6.9.0",
71+
"chai": "^3.5.0",
72+
"cross-env": "^3.1.4",
73+
"dlv": "^1.1.0",
74+
"eslint": "^3.13.1",
75+
"gzip-size-cli": "^1.0.0",
76+
"jsdom": "^9.12.0",
77+
"jsdom-global": "^2.1.1",
78+
"mkdirp": "^0.5.1",
79+
"mocha": "^3.2.0",
80+
"npm-run-all": "^2.1.1",
81+
"rimraf": "^2.5.2",
82+
"rollup": "^0.41.4",
83+
"rollup-plugin-alias": "^1.2.1",
84+
"rollup-plugin-buble": "^0.15.0",
85+
"rollup-plugin-node-resolve": "^3.0.0",
86+
"rollup-plugin-post-replace": "^1.0.0",
87+
"rollup-plugin-uglify": "^1.0.1",
88+
"sinon": "^1.17.4",
89+
"sinon-chai": "^2.8.0",
90+
"strip-json-comments-cli": "^1.0.1",
91+
"uglify-js": "^2.6.2"
92+
}
93+
}

rollup.config.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import fs from 'fs';
2+
import alias from 'rollup-plugin-alias';
3+
import buble from 'rollup-plugin-buble';
4+
import uglify from 'rollup-plugin-uglify';
5+
import replace from 'rollup-plugin-post-replace';
6+
import nodeResolve from 'rollup-plugin-node-resolve';
7+
8+
let pkg = JSON.parse(fs.readFileSync('./package.json'));
9+
10+
let format = process.env.FORMAT;
11+
12+
export default {
13+
useStrict: false,
14+
sourceMap: true,
15+
exports: 'default',
16+
format,
17+
moduleName: pkg.amdName,
18+
dest: format==='es' ? pkg.module : format==='umd' ? pkg['umd:main'] : pkg.main,
19+
external: ['preact'],
20+
entry: 'src/index.js',
21+
plugins: [
22+
alias({
23+
linkstate: 'src/index.js'
24+
}),
25+
nodeResolve({
26+
jsnext: true,
27+
main: true
28+
}),
29+
buble(),
30+
format==='cjs' && replace({
31+
'module.exports = index;': '',
32+
'var index =': 'module.exports ='
33+
}),
34+
format==='umd' && replace({
35+
'return index;': '',
36+
'var index =': 'return'
37+
}),
38+
format!=='es' && uglify({
39+
output: { comments: false },
40+
mangle: {
41+
topLevel: format==='cjs'
42+
}
43+
})
44+
]
45+
};

src/index.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import delve from 'dlv';
2+
3+
/** Create an Event handler function that sets a given state property.
4+
* @param {Component} component The component whose state should be updated
5+
* @param {string} key A dot-notated key path to update in the component's state
6+
* @param {string} eventPath A dot-notated key path to the value that should be retrieved from the Event or component
7+
* @returns {function} linkedStateHandler
8+
*/
9+
export default function linkState(component, key, eventPath) {
10+
let path = key.split('.');
11+
return function(e) {
12+
let t = e && e.target || this,
13+
state = {},
14+
obj = state,
15+
v = typeof eventPath==='string' ? delve(e, eventPath) : t.nodeName ? (t.type.match(/^che|rad/) ? t.checked : t.value) : e,
16+
i = 0;
17+
for ( ; i<path.length-1; i++) {
18+
obj = obj[path[i]] || (obj[path[i]] = !i && component.state[path[i]] || {});
19+
}
20+
obj[path[i]] = v;
21+
component.setState(state);
22+
};
23+
}

src/polyfill.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Component } from 'preact';
2+
import linkState from 'linkstate';
3+
4+
Component.prototype.linkState = function(key, eventPath) {
5+
return linkState(this, key, eventPath);
6+
};
7+
8+
export default linkState;

0 commit comments

Comments
 (0)