diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c2f94770bf..42be1dcc2414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [react] feat: Add instrumentation for React Router v3 (#2759) - [apm/tracing] fix: Make sure fetch requests are being timed correctly (#2772) - [apm/tracing] fix: Make sure pageload transactions start timestamps are correctly generated (#2773) +- [react] ref: Use inline types to avoid redux dependency. (#2768) ## 5.20.0 diff --git a/packages/react/src/redux.ts b/packages/react/src/redux.ts index 8fa1547f84b6..195953c3fdc5 100644 --- a/packages/react/src/redux.ts +++ b/packages/react/src/redux.ts @@ -1,7 +1,52 @@ // @flow import { configureScope } from '@sentry/minimal'; import { Scope } from '@sentry/types'; -import { Action, AnyAction, PreloadedState, Reducer, StoreEnhancer, StoreEnhancerStoreCreator } from 'redux'; + +interface Action { + type: T; +} + +interface AnyAction extends Action { + [extraProps: string]: any; +} + +type Reducer = (state: S | undefined, action: A) => S; + +type Dispatch = (action: T, ...extraArgs: any[]) => T; + +type ExtendState = [Extension] extends [never] ? State : State & Extension; + +type Unsubscribe = () => void; + +interface Store { + dispatch: Dispatch; + getState(): S; + subscribe(listener: () => void): Unsubscribe; + replaceReducer( + nextReducer: Reducer, + ): Store, NewActions, StateExt, Ext> & Ext; +} + +declare const $CombinedState: unique symbol; + +type CombinedState = { readonly [$CombinedState]?: undefined } & S; + +type PreloadedState = Required extends { + [$CombinedState]: undefined; +} + ? S extends CombinedState + ? { [K in keyof S1]?: S1[K] extends object ? PreloadedState : S1[K] } + : never + : { [K in keyof S]: S[K] extends string | number | boolean | symbol ? S[K] : PreloadedState }; + +type StoreEnhancer = ( + next: StoreEnhancerStoreCreator, +) => StoreEnhancerStoreCreator; + +type StoreEnhancerStoreCreator = ( + reducer: Reducer, + preloadedState?: PreloadedState, +) => Store, A, StateExt, Ext> & Ext; export interface SentryEnhancerOptions { /** @@ -9,7 +54,7 @@ export interface SentryEnhancerOptions { * Use this to remove any private data before sending it to Sentry. * Return null to not attach the state. */ - stateTransformer(state: any | undefined): any | null; + stateTransformer(state: S | undefined): (S & any) | null; /** * Transforms the action before sending it as a breadcrumb. * Use this to remove any private data before sending it to Sentry. @@ -19,7 +64,7 @@ export interface SentryEnhancerOptions { /** * Called on every state update, configure the Sentry Scope with the redux state. */ - configureScopeWithState?(scope: Scope, state: any): void; + configureScopeWithState?(scope: Scope, state: S): void; } const ACTION_BREADCRUMB_CATEGORY = 'redux.action'; @@ -29,9 +74,14 @@ const STATE_CONTEXT_KEY = 'redux.state'; const defaultOptions: SentryEnhancerOptions = { actionTransformer: action => action, // tslint:disable-next-line: no-unsafe-any - stateTransformer: state => state, + stateTransformer: state => state || null, }; +/** + * Creates an enhancer that would be passed to Redux's createStore to log actions and the latest state to Sentry. + * + * @param enhancerOptions Options to pass to the enhancer + */ function createReduxEnhancer(enhancerOptions?: Partial): StoreEnhancer { const options = { ...defaultOptions, @@ -59,6 +109,7 @@ function createReduxEnhancer(enhancerOptions?: Partial): /* Set latest state to scope */ const transformedState = options.stateTransformer(newState); + // tslint:disable-next-line: strict-type-predicates if (typeof transformedState !== 'undefined' && transformedState !== null) { // tslint:disable-next-line: no-unsafe-any scope.setContext(STATE_CONTEXT_KEY, transformedState);