immer#applyPatches TypeScript Examples
The following examples show how to use
immer#applyPatches.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.ts From core with MIT License | 6 votes |
function topReducer(state: any, action: any) {
switch (action.type) {
case 'PATCH': {
return produce(state, (draftState: any) => {
const patch = {
...action.payload.patch,
path: jsonPatchPathToImmerPath(action.payload.patch.path),
};
draftState[action.payload.subtree].state = applyPatches(
draftState[action.payload.subtree].state,
[patch]
);
draftState[action.payload.subtree].patches.push({
patch: action.payload.patch,
inversePatch: action.payload.inversePatch,
});
});
}
case 'CREATE_SUBTREE': {
return produce(state, (draftState: any) => {
draftState[action.payload.subtree] = {
state: action.payload.initialState,
patches: [],
};
});
}
default:
return state;
}
}
Example #2
Source File: useStateTracking.ts From baleen3 with Apache License 2.0 | 5 votes |
export function useStateTracking<T>(initialState: T): {
current: T
modify: (mutator: Mutator<T>) => void
initialize: (initializer: Initializer<T>) => void
undo: () => void
redo: () => void
canUndo: boolean
canRedo: boolean
} {
const [current, setCurrent] = useState<T>(initialState)
const undoStack = useRef<
Array<{ patches: Patch[]; inversePatches: Patch[] }>
>([])
const undoStackPointer = useRef(-1)
const undo = useCallback((): void => {
if (undoStackPointer.current < 0) {
return
}
setCurrent(
(state) =>
applyPatches(
state,
undoStack.current[undoStackPointer.current].inversePatches
) as T
)
undoStackPointer.current--
}, [])
const redo = useCallback((): void => {
if (undoStackPointer.current === undoStack.current.length - 1) {
return
}
undoStackPointer.current++
setCurrent(
(state) =>
applyPatches(
state,
undoStack.current[undoStackPointer.current].patches
) as T
)
}, [])
const modify = useCallback((mutator: Mutator<T>): void => {
setCurrent((state) =>
produce(state, mutator, (patches, inversePatches) => {
// ignore empty change
if (patches.length > 0) {
const pointer = ++undoStackPointer.current
undoStack.current.length = pointer
undoStack.current[Number(pointer)] = { patches, inversePatches }
}
})
)
}, [])
const initialize = useCallback((initializer: Initializer<T>): void => {
setCurrent(initializer)
undoStackPointer.current = -1
undoStack.current.length = 0
}, [])
return {
current,
initialize,
modify,
undo,
redo,
canUndo: undoStackPointer.current > -1,
canRedo: undoStackPointer.current < undoStack.current.length - 1,
}
}
Example #3
Source File: history.ts From textbus with GNU General Public License v3.0 | 4 votes |
private apply(historyItem: HistoryItem, back: boolean) {
let operations = historyItem.operations
if (back) {
operations = [...operations].reverse()
}
operations.forEach(op => {
const path = [...op.path]
const isFindSlot = path.length % 2 === 1
const actions = back ? op.unApply : op.apply
if (isFindSlot) {
const slot = this.selection.findSlotByPaths(path)!
actions.forEach(action => {
if (action.type === 'retain') {
if (action.formats) {
const formats: Formats = []
Object.keys(action.formats).forEach(i => {
const formatter = this.registry.getFormatter(i)
if (formatter) {
formats.push([formatter, action.formats![i]])
}
})
slot.retain(action.offset, formats)
} else {
slot.retain(action.offset)
}
return
}
if (action.type === 'delete') {
slot.delete(action.count)
return
}
if (action.type === 'apply') {
slot.updateState(draft => {
applyPatches(draft, action.patches)
})
return
}
if (action.type === 'insert') {
if (typeof action.content === 'string') {
if (action.formats) {
const formats: Formats = []
Object.keys(action.formats).forEach(i => {
const formatter = this.registry.getFormatter(i)
if (formatter) {
formats.push([formatter, action.formats![i]])
}
})
slot.insert(action.content, formats)
} else {
slot.insert(action.content)
}
} else {
const component = this.translator.createComponent(action.content as ComponentLiteral)!
slot.insert(component)
}
}
})
} else {
const component = this.selection.findComponentByPaths(path)!
actions.forEach(action => {
if (action.type === 'retain') {
component.slots.retain(action.offset)
return
}
if (action.type === 'delete') {
component.slots.delete(action.count)
return
}
if (action.type === 'insertSlot') {
const slot = this.translator.createSlot(action.slot)
component.slots.insert(slot)
}
if (action.type === 'apply') {
component.updateState(draft => {
return applyPatches(draft, action.patches)
})
return
}
})
}
})
}
Example #4
Source File: index.test.ts From reactant with MIT License | 4 votes |
test('base model with `useValue` and `enablePatches`', () => {
const todoList: string[] = [];
const todoModel = model({
name: 'todo',
state: {
todoList,
},
actions: {
add: (todo: string) => (state) => {
state.todoList.push(todo);
},
},
});
@injectable()
class Foo {
constructor(@inject('todo') public todo: typeof todoModel) {}
add(todo: string) {
this.todo.add(todo);
}
get todoList() {
return this.todo.todoList;
}
}
const actionFn = jest.fn();
class Logger extends PluginModule {
middleware: ReactantMiddleware = (store) => (next) => (_action) => {
actionFn(_action);
return next(_action);
};
}
const ServiceIdentifiers = new Map();
const modules = [Foo, { provide: 'todo', useValue: todoModel }, Logger];
const container = createContainer({
ServiceIdentifiers,
modules,
options: {
defaultScope: 'Singleton',
},
});
const foo = container.get(Foo);
const store = createStore({
modules,
container,
ServiceIdentifiers,
loadedModules: new Set(),
load: (...args: any[]) => {
//
},
pluginHooks: {
middleware: [],
beforeCombineRootReducers: [],
afterCombineRootReducers: [],
enhancer: [],
preloadedStateHandler: [],
afterCreateStore: [],
provider: [],
},
devOptions: {
enablePatches: true,
},
});
const originalTodoState = foo.todo[stateKey]!;
expect(Object.values(store.getState())).toEqual([{ todoList: [] }]);
expect(actionFn.mock.calls.length).toBe(0);
foo.add('test');
expect(Object.values(store.getState())).toEqual([{ todoList: ['test'] }]);
expect(actionFn.mock.calls.length).toBe(1);
expect(actionFn.mock.calls[0][0]._patches).toEqual([
{
op: 'add',
path: ['todoList', 0],
value: 'test',
},
]);
expect(actionFn.mock.calls[0][0]._inversePatches).toEqual([
{
op: 'replace',
path: ['todoList', 'length'],
value: 0,
},
]);
expect(
applyPatches(originalTodoState, actionFn.mock.calls[0][0]._patches)
).toEqual(foo.todo[stateKey]);
expect(
applyPatches(originalTodoState, actionFn.mock.calls[0][0]._patches) ===
foo.todo[stateKey]
).toBe(false);
expect(
applyPatches(foo.todo[stateKey]!, actionFn.mock.calls[0][0]._inversePatches)
).toEqual(originalTodoState);
});
Example #5
Source File: action.test.ts From reactant with MIT License | 4 votes |
describe('@action', () => {
test('base', () => {
@injectable()
class Counter {
@state
count = 0;
@action
increase() {
this.count += 1;
}
}
const ServiceIdentifiers = new Map();
const modules = [Counter];
const container = createContainer({
ServiceIdentifiers,
modules,
options: {
defaultScope: 'Singleton',
},
});
const counter = container.get(Counter);
const store = createStore({
modules,
container,
ServiceIdentifiers,
loadedModules: new Set(),
load: (...args: any[]) => {
//
},
pluginHooks: {
middleware: [],
beforeCombineRootReducers: [],
afterCombineRootReducers: [],
enhancer: [],
preloadedStateHandler: [],
afterCreateStore: [],
provider: [],
},
});
counter.increase();
expect(counter.count).toBe(1);
expect((counter as any)[enablePatchesKey]).toBe(false);
expect(Object.values(store.getState())).toEqual([{ count: 1 }]);
});
test('enable `autoFreeze` in devOptions', () => {
@injectable({
name: 'counter',
})
class Counter {
@state
count = 0;
@state
sum = { count: 0 };
@action
increase() {
this.sum.count += 1;
}
increase1() {
this.sum.count += 1;
}
increase2() {
this.count += 1;
}
}
const ServiceIdentifiers = new Map();
const modules = [Counter];
const container = createContainer({
ServiceIdentifiers,
modules,
options: {
defaultScope: 'Singleton',
},
});
const counter = container.get(Counter);
const store = createStore({
modules,
container,
ServiceIdentifiers,
loadedModules: new Set(),
load: (...args: any[]) => {
//
},
pluginHooks: {
middleware: [],
beforeCombineRootReducers: [],
afterCombineRootReducers: [],
enhancer: [],
preloadedStateHandler: [],
afterCreateStore: [],
provider: [],
},
devOptions: { autoFreeze: true },
});
expect(() => {
store.getState().counter.sum.count = 1;
}).toThrowError(/Cannot assign to read only property/);
counter.increase();
for (const fn of [
() => {
store.getState().counter.sum.count = 1;
},
() => counter.increase1(),
() => counter.increase2(),
]) {
expect(fn).toThrowError(/Cannot assign to read only property/);
}
});
test('inherited module with stagedState about more effects', () => {
@injectable({
name: 'foo0',
})
class Foo0 {
@state
count0 = 1;
@state
count1 = 1;
@action
increase() {
this.count0 += 1;
}
@action
decrease() {
this.count0 -= 1;
}
@action
decrease1() {
this.count0 -= 1;
}
}
@injectable({
name: 'foo',
})
class Foo extends Foo0 {
count = 1;
add(count: number) {
this.count += count;
}
@action
increase() {
// inheritance
super.increase();
// change state
this.count0 += 1;
// call other action function
this.increase1();
// call unwrapped `@action` function
this.add(this.count);
}
@action
increase1() {
this.count1 += 1;
}
@action
decrease() {
super.decrease();
this.count0 -= 1;
}
decrease1() {
super.decrease1();
}
}
@injectable()
class FooBar {
constructor(public foo: Foo, public foo0: Foo0) {}
}
const ServiceIdentifiers = new Map();
const modules = [FooBar];
const container = createContainer({
ServiceIdentifiers,
modules,
options: {
defaultScope: 'Singleton',
},
});
const fooBar = container.get(FooBar);
const store = createStore({
modules,
container,
ServiceIdentifiers,
loadedModules: new Set(),
load: (...args: any[]) => {
//
},
pluginHooks: {
middleware: [],
beforeCombineRootReducers: [],
afterCombineRootReducers: [],
enhancer: [],
preloadedStateHandler: [],
afterCreateStore: [],
provider: [],
},
});
const subscribe = jest.fn();
store.subscribe(subscribe);
fooBar.foo.increase();
expect(fooBar.foo.count0).toBe(3);
expect(fooBar.foo.count1).toBe(2);
expect(fooBar.foo.count).toBe(2);
fooBar.foo0.increase();
expect(fooBar.foo.count0).toBe(3);
expect(fooBar.foo.count1).toBe(2);
expect(fooBar.foo.count).toBe(2);
// merge the multi-actions changed states as one redux dispatch.
expect(subscribe.mock.calls.length).toBe(2);
// inheritance
fooBar.foo.decrease();
expect(fooBar.foo.count0).toBe(1);
fooBar.foo.decrease1();
expect(fooBar.foo.count0).toBe(0);
});
test('across module changing state', () => {
@injectable()
class Foo {
@state
textList: string[] = [];
@action
addText(text: string) {
this.textList.push(text);
}
}
@injectable()
class Counter {
constructor(public foo: Foo) {}
@state
count = 0;
@action
increase() {
this.foo.addText(`test${this.count}`);
this.count += 1;
this.foo.addText(`test${this.count}`);
this.count += 1;
this.foo.addText(`test${this.count}`);
}
}
const ServiceIdentifiers = new Map();
const modules = [Counter];
const container = createContainer({
ServiceIdentifiers,
modules,
options: {
defaultScope: 'Singleton',
},
});
const counter = container.get(Counter);
const store = createStore({
modules,
container,
ServiceIdentifiers,
loadedModules: new Set(),
load: (...args: any[]) => {
//
},
pluginHooks: {
middleware: [],
beforeCombineRootReducers: [],
afterCombineRootReducers: [],
enhancer: [],
preloadedStateHandler: [],
afterCreateStore: [],
provider: [],
},
});
const subscribeFn = jest.fn();
store.subscribe(subscribeFn);
counter.increase();
expect(subscribeFn.mock.calls.length).toBe(1);
expect(counter.count).toEqual(2);
expect(counter.foo.textList).toEqual(['test0', 'test1', 'test2']);
});
test('across module changing state with error', () => {
@injectable({
name: 'foo',
})
class Foo {
@state
list: number[] = [];
@action
addItem(num: number) {
if (num === 1) {
// eslint-disable-next-line no-throw-literal
throw 'something error';
} else {
this.list.push(num);
}
}
}
@injectable({
name: 'counter',
})
class Counter {
constructor(public foo: Foo) {}
@state
count = 0;
@action
increase() {
this.foo.addItem(this.count);
this.count += 1;
}
@action
increase1() {
this.foo.addItem(this.count + 1);
this.count += 1;
}
}
const ServiceIdentifiers = new Map();
const modules = [Counter];
const container = createContainer({
ServiceIdentifiers,
modules,
options: {
defaultScope: 'Singleton',
},
});
const counter = container.get(Counter);
const store = createStore({
modules,
container,
ServiceIdentifiers,
loadedModules: new Set(),
load: (...args: any[]) => {
//
},
pluginHooks: {
middleware: [],
beforeCombineRootReducers: [],
afterCombineRootReducers: [],
enhancer: [],
preloadedStateHandler: [],
afterCreateStore: [],
provider: [],
},
});
const subscribeFn = jest.fn();
store.subscribe(subscribeFn);
counter.increase();
expect(subscribeFn.mock.calls.length).toBe(1);
expect(() => {
counter.increase();
}).toThrowError('something error');
expect(getStagedState()).toBeUndefined();
expect(subscribeFn.mock.calls.length).toBe(1);
counter.increase1();
expect(subscribeFn.mock.calls.length).toBe(2);
expect(counter.count).toBe(2);
expect(counter.foo.list).toEqual([0, 2]);
expect(store.getState()).toEqual({
counter: { count: 2 },
foo: { list: [0, 2] },
});
});
test('base with `enablePatches`', () => {
interface Todo {
text: string;
}
@injectable({
name: 'todo',
})
class TodoList {
@state
list: Todo[] = [
{
text: 'foo',
},
];
@action
add(text: string) {
this.list.slice(-1)[0].text = text;
this.list.push({ text });
}
}
const actionFn = jest.fn();
const middleware: Middleware = (store) => (next) => (_action) => {
actionFn(_action);
return next(_action);
};
const ServiceIdentifiers = new Map();
const modules = [TodoList, applyMiddleware(middleware)];
const container = createContainer({
ServiceIdentifiers,
modules,
options: {
defaultScope: 'Singleton',
},
});
const todoList = container.get(TodoList);
const store = createStore({
modules,
container,
ServiceIdentifiers,
loadedModules: new Set(),
load: (...args: any[]) => {
//
},
pluginHooks: {
middleware: [],
beforeCombineRootReducers: [],
afterCombineRootReducers: [],
enhancer: [],
preloadedStateHandler: [],
afterCreateStore: [],
provider: [],
},
devOptions: {
enablePatches: true,
},
});
const originalTodoState = store.getState();
expect(Object.values(store.getState())).toEqual([
{ list: [{ text: 'foo' }] },
]);
expect(actionFn.mock.calls.length).toBe(0);
todoList.add('test');
expect(Object.values(store.getState())).toEqual([
{ list: [{ text: 'test' }, { text: 'test' }] },
]);
expect(actionFn.mock.calls.length).toBe(1);
expect(actionFn.mock.calls[0][0]._patches).toEqual([
{
op: 'replace',
path: ['todo', 'list', 0, 'text'],
value: 'test',
},
{
op: 'add',
path: ['todo', 'list', 1],
value: {
text: 'test',
},
},
]);
expect(actionFn.mock.calls[0][0]._inversePatches).toEqual([
{
op: 'replace',
path: ['todo', 'list', 0, 'text'],
value: 'foo',
},
{
op: 'replace',
path: ['todo', 'list', 'length'],
value: 1,
},
]);
expect(
applyPatches(originalTodoState, actionFn.mock.calls[0][0]._patches)
).toEqual(store.getState());
expect(
applyPatches(originalTodoState, actionFn.mock.calls[0][0]._patches) ===
store.getState()
).toBe(false);
expect(
applyPatches(store.getState(), actionFn.mock.calls[0][0]._inversePatches)
).toEqual(originalTodoState);
});
});