Last active
July 26, 2022 20:18
-
-
Save Thanaen/abfcc669f28765651f8e0ff59ea62527 to your computer and use it in GitHub Desktop.
Zustand Sentry Middleware
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { StateCreator, StoreMutatorIdentifier } from 'zustand'; | |
import { configureScope } from '@sentry/browser'; | |
type PopArgument<T extends (...a: never[]) => unknown> = T extends ( | |
...a: [...infer A, infer _] | |
) => infer R | |
? (...a: A) => R | |
: never; | |
interface SentryMiddlewareConfig<T> { | |
stateTransformer?: (state: T) => object; | |
} | |
type SentryMiddleware = < | |
T extends object, | |
Mps extends [StoreMutatorIdentifier, unknown][] = [], | |
Mcs extends [StoreMutatorIdentifier, unknown][] = [], | |
>( | |
f: StateCreator<T, Mps, Mcs>, | |
config?: SentryMiddlewareConfig<T>, | |
) => StateCreator<T, Mps, Mcs>; | |
type SentryMiddlewareImpl = <T extends object>( | |
f: PopArgument<StateCreator<T, [], []>>, | |
config?: SentryMiddlewareConfig<T>, | |
) => PopArgument<StateCreator<T, [], []>>; | |
const sentryMiddleware: SentryMiddlewareImpl = (config, sentryConfig) => (set, get, api) => | |
config( | |
(...args) => { | |
set(...args); | |
const newState = get(); | |
configureScope((scope) => { | |
if (newState) { | |
const transformedState = sentryConfig?.stateTransformer | |
? sentryConfig.stateTransformer(newState) | |
: newState; | |
scope.setContext('state', { type: 'zustand', value: transformedState as Record<string, unknown> }); | |
} else { | |
scope.setContext('state', null); | |
} | |
}); | |
}, | |
get, | |
api, | |
); | |
const typedSentryMiddleware = sentryMiddleware as unknown as SentryMiddleware; | |
export default typedSentryMiddleware; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A method to edit the state before sending to Sentry (remove sensitive data, functions, etc) | |
const stateTransformer = (state: BearState) => { | |
const cleanedState = { | |
...state, | |
bears: state.bears > 0 ? "There are some bears, but I won't tell you how many!" : "No bears here" | |
}; | |
// In zustand, actions are accessible from the store's state | |
// We might want to remove them before sending the state to Sentry | |
return Object.fromEntries( | |
Object.entries(cleanedState).filter( | |
([key]) => typeof cleanedState[key as keyof typeof cleanedState] !== 'function', | |
), | |
); | |
}; | |
export default stateTransformer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Example of how to use the middleware | |
import create from "zustand"; | |
import sentryMiddleware from './sentryMiddleware'; | |
import stateTransformer from './stateTransformer'; | |
interface BearState { | |
bears: number; | |
increase: (by: number) => void; | |
} | |
const useStore = create<BearState>()( | |
typedSentryMiddleware((set) => ({ | |
bears: 0, | |
increase: (by) => set((state) => ({ bears: state.bears + by })), | |
}), { stateTransformer: stateTransformer }) | |
); | |
export default useStore; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Improved following feedback on this issue getsentry/sentry-javascript#5430