Skip to content

Commit

Permalink
Fixes computed property caching.
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlplusb committed Feb 27, 2021
1 parent f263698 commit 5c4c0e8
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 27 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "easy-peasy",
"version": "5.0.2",
"version": "5.0.3",
"description": "Vegetarian friendly state for React",
"license": "MIT",
"main": "dist/index.cjs.js",
Expand Down
29 changes: 18 additions & 11 deletions src/computed-properties.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import { get, memoizeOne } from './lib';
import { areInputsEqual } from './lib';

export function createComputedPropertyBinder(parentPath, key, def, _r) {
const memoisedResultFn = memoizeOne(def.fn);
let previousValue;
let runOnce = false;
let prevInputs = [];
let prevValue;
return function createComputedProperty(parentState, storeState) {
Object.defineProperty(parentState, key, {
configurable: true,
enumerable: true,
get: () => {
if (previousValue !== undefined && _r._i._cS.isInReducer) {
const inputs = def.stateResolvers.map((resolver) =>
resolver(parentState, storeState),
);
if (
runOnce &&
(areInputsEqual(prevInputs, inputs) ||
(_r._i._cS.isInReducer &&
new Error().stack.match(/shallowCopy/gi) !== null))
) {
// We don't want computed properties resolved every time an action
// is handled by the reducer. They need to remain lazy, only being
// computed when used by a component or getState call.
return previousValue;
return prevValue;
}
const state = get(parentPath, storeState);
const inputs = def.stateResolvers.map((resolver) =>
resolver(state, storeState),
);
previousValue = memoisedResultFn(...inputs);
return previousValue;
prevInputs = inputs;
prevValue = def.fn(...inputs);
runOnce = true;
return prevValue;
},
});
};
Expand Down
30 changes: 15 additions & 15 deletions src/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,21 +175,21 @@ export function areInputsEqual(newInputs, lastInputs) {
return true;
}

export function memoizeOne(resultFn) {
let lastArgs = [];
let lastResult;
let calledOnce = false;

return function memoized(...args) {
if (calledOnce && areInputsEqual(args, lastArgs)) {
return lastResult;
}
lastResult = resultFn(...args);
calledOnce = true;
lastArgs = args;
return lastResult;
};
}
// export function memoizeOne(resultFn) {
// let lastArgs = [];
// let lastResult;
// let calledOnce = false;

// return function memoized(...args) {
// if (calledOnce && areInputsEqual(args, lastArgs)) {
// return lastResult;
// }
// lastResult = resultFn(...args);
// calledOnce = true;
// lastArgs = args;
// return lastResult;
// };
// }

export function useMemoOne(
// getResult changes on every call,
Expand Down
55 changes: 55 additions & 0 deletions tests/computed.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,61 @@ import {
StoreProvider,
} from '../src';

/*
test.only('issue#633', () => {
// ARRANGE
const createModel = (type) => ({
items: Array(100)
.fill({})
.map((_, index) => ({
id: `${type}-${index}`,
type,
done: index % 2 === 0,
})),
completedItems: computed((state) =>
state.items.filter((i) => i.done === true),
),
setItems: action((state, payload) => {
state.items = payload;
}),
removeCompletedItems: action((state) => {
console.log('foo');
const completedIds = state.completedItems.map((i) => i.id);
console.log(completedIds);
state.items = state.items.filter(
(item) => !completedIds.includes(item.id),
);
}),
});
const store = createStore({
abc: createModel('abc'),
def: createModel('def'),
allCompletedItems: computed(
[(_, storeState) => storeState.abc, (_, storeState) => storeState.def],
(abcItems, defItems) => {
console.log(abcItems.completedItems.length);
return [...abcItems.completedItems, ...defItems.completedItems];
},
),
});
// ACT
store.getActions().abc.removeCompletedItems();
store.getActions().def.removeCompletedItems();
// ASSERT
expect(store.getState().abc.items.length).toBe(50);
expect(store.getState().abc.completedItems.length).toBe(50);
expect(store.getState().def.items.length).toBe(50);
expect(store.getState().allCompletedItems.length).toBe(100);
});
*/

test('accessing computed properties within an action', () => {
const store = createStore({
firstName: 'Mary',
Expand Down

0 comments on commit 5c4c0e8

Please sign in to comment.