Skip to content

Instantly share code, notes, and snippets.

@AlexandrHoroshih
Created August 20, 2021 19:35
Show Gist options
  • Save AlexandrHoroshih/4ca98a746ec2bed163cfe11552e2d938 to your computer and use it in GitHub Desktop.
Save AlexandrHoroshih/4ca98a746ec2bed163cfe11552e2d938 to your computer and use it in GitHub Desktop.
Effector history wrapper
// an option to create bindings to history
// usage is like
//
// export const historyUpdated = createEvent<HistoryUpdate>();
// export const clocks = createHistoryClocks();
//
// clocks have fields like `push, replace, go` and so on
// can be used like clocks.push({ to: "path" })
//
// wrapHistory({
// historySource: $history, // put history instance to store
// clocks,
// target: historyUpdated.prepend<HistoryUpdate>((update) => klona(update)),
// // history is mutable, it is much safer having an immutable copy of every update
// });
//
import type { Action, History, Location, LocationState, Path } from "history";
import type { Domain, Event, Store } from "effector";
import { createEvent, sample, scopeBind } from "effector";
export type ToParams<S extends LocationState = LocationState> = {
to: Path;
LocationState?: S;
};
export type HistoryUpdate = { action: Action; location: Location };
export type Clocks<S extends LocationState> = {
push: Event<ToParams<S>>;
replace: Event<ToParams<S>>;
go: Event<number>;
back: Event<unknown>;
forward: Event<unknown>;
};
type Config<S extends LocationState = LocationState> = {
historySource: Store<History<S> | null>;
clocks: Clocks<S>;
target: Event<HistoryUpdate>;
};
const checkHistory = <S extends LocationState>(
history?: History<S> | null,
): history is History<S> => {
const historyProvided = Boolean(history);
if (!historyProvided) {
console.warn("No history was provided");
return false;
}
return historyProvided;
};
export const createHistoryClocks = (domain?: Domain) => {
if (domain) {
return {
push: domain.createEvent<ToParams>(),
replace: domain.createEvent<ToParams>(),
go: domain.createEvent<number>(),
back: domain.createEvent<unknown>(),
forward: domain.createEvent<unknown>(),
};
}
return {
push: createEvent<ToParams>(),
replace: createEvent<ToParams>(),
go: createEvent<number>(),
back: createEvent<unknown>(),
forward: createEvent<unknown>(),
};
};
export const wrapHistory = <S extends LocationState = LocationState>(
config: Config<S>,
) => {
const { historySource, clocks, target } = config;
historySource.updates.watch((history) => {
// Hacky way to support both in and out of scope listeners
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let listener = (_: any) => console.warn("History listener is not set");
try {
listener = scopeBind(target);
} catch (e) {
listener = target;
}
if (!checkHistory<S>(history)) return;
listener({ location: history.location, action: history.action });
history.listen(() => {
listener({ location: history.location, action: history.action });
});
});
// push
const historyPushed = sample({
source: historySource,
clock: clocks.push,
fn: (history, params) => ({ history, params }),
});
historyPushed.watch(
({ history, params }) =>
checkHistory(history) && history.push(params.to, params.LocationState),
);
// replace
const historyReplaced = sample({
source: historySource,
clock: clocks.replace,
fn: (history, params) => ({ history, params }),
});
historyReplaced.watch(
({ history, params }) =>
checkHistory(history) && history.replace(params.to, params.LocationState),
);
// go
const historyGo = sample({
source: historySource,
clock: clocks.go,
fn: (history, params) => ({ history, params }),
});
historyGo.watch(
({ history, params }) => checkHistory(history) && history.go(params),
);
// back
const historyBack = sample({
source: historySource,
clock: clocks.back,
fn: (history) => history,
});
historyBack.watch((history) => checkHistory(history) && history.goBack());
// forward
const historyForward = sample({
source: historySource,
clock: clocks.forward,
fn: (history) => history,
});
historyForward.watch(
(history) => checkHistory(history) && history.goForward(),
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment