├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── package.json
└── src
└── react-delegate.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | lib
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 |
9 | # Runtime data
10 | pids
11 | *.pid
12 | *.seed
13 | *.pid.lock
14 |
15 | # Directory for instrumented libs generated by jscoverage/JSCover
16 | lib-cov
17 |
18 | # Coverage directory used by tools like istanbul
19 | coverage
20 |
21 | # nyc test coverage
22 | .nyc_output
23 |
24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
25 | .grunt
26 |
27 | # Bower dependency directory (https://bower.io/)
28 | bower_components
29 |
30 | # node-waf configuration
31 | .lock-wscript
32 |
33 | # Compiled binary addons (http://nodejs.org/api/addons.html)
34 | build/Release
35 |
36 | # Dependency directories
37 | node_modules/
38 | jspm_packages/
39 |
40 | # Typescript v1 declaration files
41 | typings/
42 |
43 | # Optional npm cache directory
44 | .npm
45 |
46 | # Optional eslint cache
47 | .eslintcache
48 |
49 | # Optional REPL history
50 | .node_repl_history
51 |
52 | # Output of 'npm pack'
53 | *.tgz
54 |
55 | # Yarn Integrity file
56 | .yarn-integrity
57 |
58 | # dotenv environment variables file
59 | .env
60 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | npm-debug.log*
2 |
3 | node_modules/
4 | .npm
5 | .eslintcache
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Swipes Incorporated
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-delegate
2 |
3 | Fixing the relationship between parent and child in react to avoid callback hells and over-parenting. :)
4 |
5 | # Installation
6 | ```
7 | npm install --save react-delegate
8 | ```
9 |
10 | # Basic idea (Parent)
11 | The delegate turns a TaskList component (parent):
12 | ```jsx
13 | /* BEFORE */
14 | class TaskList extends React.Component {
15 | constructor(props) {
16 | super(props);
17 | this.onTaskComplete = this.onTaskComplete.bind(this);
18 | this.onTaskDelete = this.onTaskDelete.bind(this);
19 | this.onTaskSchedule = this.onTaskSchedule.bind(this);
20 | }
21 | onTaskComplete() { /* completing stuff */ }
22 | onTaskDelete() { /* deleting stuff */ }
23 | onTaskSchedule() { /* scheduling stuff */ }
24 |
25 | render() {
26 | const { tasks } = this.props;
27 |
28 | return tasks.map(task => (
29 |
35 | ));
36 | }
37 | }
38 | ```
39 |
40 | Into looking like that (parent):
41 | ```jsx
42 | /* AFTER */
43 | class TaskList extends React.Component {
44 | constructor(props) {
45 | super(props);
46 | }
47 | onTaskComplete() { /* completing stuff */ }
48 | onTaskDelete() { /* deleting stuff */ }
49 | onTaskSchedule() { /* scheduling stuff */ }
50 |
51 | render() {
52 | const { tasks } = this.props;
53 |
54 | return tasks.map(task => (
55 |
59 | ));
60 | }
61 | }
62 | ```
63 |
64 | # Simple setup (child)
65 | And it is super easy to set up in the Task component (child)
66 | ```jsx
67 | import { setupDelegate } from 'react-delegate';
68 | class Task extends React.Component {
69 | constructor(props) {
70 | super(props);
71 | setupDelegate(this, 'onTaskComplete', 'onTaskDelete', 'onTaskSchedule');
72 | }
73 |
74 | render() {
75 | const { task } = this.props;
76 | return (
77 |
78 | {task.title}
79 |
80 | )
81 | }
82 | }
83 | ```
84 |
85 | OBS: if the delegate does not implement a function nothing will happen and we will ignore the call.
86 |
87 | # Advanced setup (child)
88 |
89 | Sometimes you need to send properties back to the parent like taskId or the task itself.
90 |
91 | TaskList wants to receive arguments (parent)
92 | ```jsx
93 | class TaskList extends React.Component {
94 | ...
95 | onTaskComplete(taskId, task, e) {
96 | // here you get the arguments from below
97 | }
98 | ...
99 | }
100 | ```
101 | This can be achieved in two ways.
102 | ## 1. setGlobals (child)
103 | You can call setGlobals and prepend arguments to all the delegate calls.
104 | This is useful in a class like Task that want to send which task is being completed, deleted or scheduled.
105 | ```jsx
106 | import { setupDelegate } from 'react-delegate';
107 | class Task extends React.Component {
108 | constructor(props) {
109 | super(props);
110 | setupDelegate(this, 'onTaskComplete', 'onTaskDelete', 'onTaskSchedule').setGlobals(task.id, task);
111 | // Now all calls to these methods will send task.id and task as the first two arguments.
112 | }
113 |
114 | render() {
115 | const { task } = this.props;
116 | return (
117 |
118 | {task.title}
119 |
120 | )
121 | }
122 | }
123 | ```
124 |
125 | ## 2. ...Cached version (child)
126 | Each method you setup gets a cached version as well that you can pass parameters to.
127 | This is great if you need to reuse a callback for multiple purposes or multiple rows.
128 | ```jsx
129 | import { setupDelegate } from 'react-delegate';
130 | class Task extends React.Component {
131 | constructor(props) {
132 | super(props);
133 | setupDelegate(this, 'onTaskComplete', 'onTaskDelete', 'onTaskSchedule');
134 | // This sets up a this.onTaskCompleteCached that take arguments.
135 | }
136 |
137 | render() {
138 | const { task } = this.props;
139 | return (
140 |
141 | {task.title}
142 |
143 | )
144 | }
145 | }
146 | ```
147 | The arguments will be added before any standard arguments like the event (e).
148 |
149 | OBS: The first argument to the cached methods acts as a key. This is to avoid creating a new function all the time on render. So make sure that if you render a list of tasks etc, to use a unique key as the first argument.
150 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-delegate",
3 | "version": "1.0.2",
4 | "description": "React delegate pattern for better callbacks to fix the parent/child relationship",
5 | "main": "./lib/react-delegate.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/swipesapp/react-delegate.git"
9 | },
10 | "keywords": [
11 | "react",
12 | "delegate",
13 | "callback"
14 | ],
15 | "author": "Kasper Pihl Tornoe",
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/swipesapp/react-delegate/issues"
19 | },
20 | "homepage": "https://github.com/swipesapp/react-delegate#readme",
21 | "devDependencies": {
22 | "babel-cli": "^6.24.1",
23 | "babel-plugin-add-module-exports": "^0.2.1",
24 | "babel-plugin-transform-object-rest-spread": "^6.23.0",
25 | "babel-preset-es2015": "^6.24.1"
26 | },
27 | "peerDependencies": {
28 | "react": ">=0.15"
29 | },
30 | "babel": {
31 | "presets": [
32 | "es2015"
33 | ],
34 | "plugins": [
35 | "transform-object-rest-spread",
36 | "add-module-exports"
37 | ]
38 | },
39 | "scripts": {
40 | "compile": "babel -d lib/ src/",
41 | "prepublish": "npm run compile"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/react-delegate.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function setupCachedCallback(method, ctx) {
4 | const cachedMethod = {};
5 | return function cachedCallback(id) {
6 | id = id || '';
7 | if(typeof(id) === 'object') {
8 | id = JSON.stringify(id);
9 | }
10 | if (!cachedMethod[id]) {
11 | const args = Array.from(arguments);
12 | cachedMethod[id] = method.bind(ctx, ...args);
13 | }
14 | return cachedMethod[id];
15 | };
16 | }
17 |
18 | export function setupDelegate(obj, ...delegateMethods) {
19 | let globals = [];
20 |
21 | const delegate = obj && (obj.delegate || (obj.props && obj.props.delegate));
22 | obj.callDelegate = function callDelegate(name, ...rest) {
23 | if (delegate && typeof delegate[name] === 'function') {
24 | return delegate[name](...globals.concat(rest));
25 | }
26 |
27 | return undefined;
28 | };
29 |
30 | delegateMethods.forEach((funcName) => {
31 | if(typeof funcName === 'string'){
32 | obj[funcName] = obj.callDelegate.bind(null, funcName);
33 | obj[`${funcName}Cached`] = setupCachedCallback(obj[funcName]);
34 | }
35 | })
36 |
37 | return {
38 | setGlobals: (...globalArgs) => {
39 | globals = globals.concat(globalArgs);
40 | }
41 | }
42 | }
43 |
44 | export function withDelegate(methods) {
45 | return (component) => {
46 | class withDelegate extends React.Component {
47 | constructor(props) {
48 | super(props);
49 | this.methods = { delegate: props.delegate };
50 | setupDelegate(this.methods, ...methods);
51 | }
52 | render() {
53 | const { delegate, ...otherProps } = this.props;
54 | const { delegate: d, ...allMethods } = this.methods;
55 |
56 | return React.createElement(component, { ...otherProps, ...allMethods });
57 | }
58 | }
59 |
60 | return withDelegate;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------