Skip to content

Commit

Permalink
use proxyequal to check changes (ref. #1)
Browse files Browse the repository at this point in the history
  • Loading branch information
dai-shi committed Nov 17, 2018
1 parent 6e4099d commit 9205519
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 16 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Change Log

## [Unreleased]
### Added
- Use proxyequal for deep change detection

## [0.1.0] - 2018-11-15
### Added
Expand Down
24 changes: 24 additions & 0 deletions examples/03_deep/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react';
import { createStore } from 'redux';

import { ReduxProvider } from '../../src/index';

import { reducer } from './state';

import Counter from './Counter';
import Person from './Person';

const store = createStore(reducer);

const App = () => (
<ReduxProvider store={store}>
<h1>Counter</h1>
<Counter />
<Counter />
<h1>Person</h1>
<Person />
<Person />
</ReduxProvider>
);

export default App;
25 changes: 25 additions & 0 deletions examples/03_deep/Counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from 'react';

import { bailOutHack, useReduxDispatch, useReduxState } from '../../src/index';

import { Action, State } from './state';

const Counter = bailOutHack(() => {
const state = useReduxState<State>([]);
const dispatch = useReduxDispatch<Action>();
return (
<div>
{Math.random()}
<div>
<span>
Count:
{state.counter}
</span>
<button type="button" onClick={() => dispatch({ type: 'increment' })}>+1</button>
<button type="button" onClick={() => dispatch({ type: 'decrement' })}>-1</button>
</div>
</div>
);
});

export default Counter;
74 changes: 74 additions & 0 deletions examples/03_deep/Person.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from 'react';

import { bailOutHack, useReduxDispatch, useReduxState } from '../../src/index';

import { Action, State } from './state';

const TextBox: React.SFC<{ text: string }> = ({ text }) => {
// tslint:disable-next-line:no-console
console.log('rendering text:', text);
return <span>{text}</span>;
};

const PersonFirstName = bailOutHack(() => {
const state = useReduxState<State>([]);
const dispatch = useReduxDispatch<Action>();
return (
<div>
First Name:
<TextBox text={state.person.firstName} />
<input
value={state.person.firstName}
onChange={(event) => {
const firstName = event.target.value;
dispatch({ firstName, type: 'setFirstName' });
}}
/>
</div>
);
});

const PersonLastName = bailOutHack(() => {
const state = useReduxState<State>([]);
const dispatch = useReduxDispatch<Action>();
return (
<div>
Last Name:
<TextBox text={state.person.lastName} />
<input
value={state.person.lastName}
onChange={(event) => {
const lastName = event.target.value;
dispatch({ lastName, type: 'setLastName' });
}}
/>
</div>
);
});

const PersonAge = bailOutHack(() => {
const state = useReduxState<State>([]);
const dispatch = useReduxDispatch<Action>();
return (
<div>
Age:
<input
value={state.person.age}
onChange={(event) => {
const age = Number(event.target.value) || 0;
dispatch({ age, type: 'setAge' });
}}
/>
</div>
);
});

const Person = () => (
<>
<PersonFirstName />
<PersonLastName />
<PersonAge />
</>
);

export default Person;
9 changes: 9 additions & 0 deletions examples/03_deep/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<html>
<head>
<title>react-hooks-easy-redux example</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="bundle.js"></script>
</html>
6 changes: 6 additions & 0 deletions examples/03_deep/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as React from 'react';
import { render } from 'react-dom';

import App from './App';

render(React.createElement(App), document.getElementById('app'));
52 changes: 52 additions & 0 deletions examples/03_deep/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const initialState = {
counter: 0,
person: {
age: 0,
firstName: '',
lastName: '',
},
};

export type State = typeof initialState;

export type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'setFirstName', firstName: string }
| { type: 'setLastName', lastName: string }
| { type: 'setAge', age: number };

export const reducer = (state = initialState, action: Action) => {
switch (action.type) {
case 'increment': return {
...state,
counter: state.counter + 1,
};
case 'decrement': return {
...state,
counter: state.counter - 1,
};
case 'setFirstName': return {
...state,
person: {
...state.person,
firstName: action.firstName,
},
};
case 'setLastName': return {
...state,
person: {
...state.person,
lastName: action.lastName,
},
};
case 'setAge': return {
...state,
person: {
...state.person,
age: action.age,
},
};
default: return state;
}
};
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"tslint": "tslint --project .",
"tsc-test": "tsc --project . --noEmit",
"examples:minimal": "DIR=01_minimal EXT=js webpack-dev-server",
"examples:typescript": "DIR=02_typescript webpack-dev-server"
"examples:typescript": "DIR=02_typescript webpack-dev-server",
"examples:deep": "DIR=03_deep webpack-dev-server"
},
"keywords": [
"react",
Expand All @@ -30,7 +31,9 @@
"pure"
],
"license": "MIT",
"dependencies": {},
"dependencies": {
"proxyequal": "^2.0.1"
},
"devDependencies": {
"@babel/cli": "^7.1.5",
"@babel/core": "^7.1.5",
Expand Down
22 changes: 8 additions & 14 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
useRef,
useState,
} from 'react';
import { proxyState, proxyEqual } from 'proxyequal';

// global context

Expand Down Expand Up @@ -41,33 +42,26 @@ export const useReduxState = (inputs) => {
const state = useContext(reduxStateContext);
const prevState = useRef(null);
const prevInputs = useRef([]);
// We assume state is an object.
// Checking only one depth for now.
const used = useRef({});
const trapped = useRef(null);
const changed = !prevState.current
|| !inputs
|| !inputs.every((x, i) => inputs[i] === prevInputs.current[i])
|| Object.keys(used.current).find(key => state[key] !== prevState.current[key]);
|| !proxyEqual(prevState.current, state, trapped.current.affected);
if (!changed) throw new Error('bail out');
prevState.current = state;
prevInputs.current = inputs;
const handler = {
get: (target, name) => {
used.current[name] = true;
return target[name];
},
};
return new Proxy(state, handler);
trapped.current = proxyState(state);
return trapped.current.state;
};

export const bailOutHack = FunctionComponent => (props) => {
const ref = useRef({});
const element = useRef(null);
try {
ref.current.lastElement = FunctionComponent(props);
element.current = FunctionComponent(props);
} catch (e) {
if (e.message !== 'bail out') {
throw e;
}
}
return ref.current.lastElement;
return element.current;
};

0 comments on commit 9205519

Please sign in to comment.