redux-devtools-extension icon indicating copy to clipboard operation
redux-devtools-extension copied to clipboard

actionSanitizer & stateSanitizer with TypeScript

Open fvilers opened this issue 4 years ago • 5 comments

Hi !

I'd like to use these EnhancerOptions with TypeScript and my typed state but can't find a way to set them correctly. I don't want to use any and to lose types.

Here's a small reproductible example (type StateSanitizer comes from redux-devtools) :

type AppState = {
    msg: string;
    secret: string;
}
type StateSanitizer = <S>(state: S) => S;

const state: AppState = { msg: "Hello", secret: "SECRET !" };
const sanitizer: StateSanitizer = (state: AppState) => {
    const result = { ...state };
    result.secret = '';
    return result; // Casting to AppState doesn't help
};

Which result in a compiler error :

Type '(state: AppState) => { msg: string; secret: string; }' is not assignable to type 'StateSanitizer'.
  Types of parameters 'state' and 'state' are incompatible.
    Type 'S' is not assignable to type 'AppState'.

I tried this too (same error) :

const sanitizer: StateSanitizer = <S extends AppState>(state: S) => {
    const result = { ...state };
    result.secret = '';
    return result as S;
};

I'm probably missing something but can't find what.

Here's my application store creation file :

import { applyMiddleware, createStore } from "redux";
import { composeWithDevTools, EnhancerOptions } from "redux-devtools-extension";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
import { AppState } from "./state";

const stateSanitizer = (state: AppState) => state;
const options: EnhancerOptions = { stateSanitizer };
const composeEnhancers = composeWithDevTools(options);

const store = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(thunk))
);

export default store;

Which fails with the same issue at compile-time.

Thanks for you help and suggestions ;-)

fvilers avatar Apr 14 '20 08:04 fvilers

Something like this should work:

const composeEnhancers = composeWithDevTools({
    stateSanitizer: <S extends Partial<AppState>>(state: S): S => {
        const { user, ...rest } = state

        return {
            ...rest
        } as S
    }
})

pkuczynski avatar Feb 16 '21 14:02 pkuczynski

That can likely be closed.

thepuzzlemaster avatar Oct 18 '21 09:10 thepuzzlemaster

Is there a similar example for actionSanitizer? This is the furthest I've gotten:

actionSanitizer: <S extends Action<string>>(action: S): S => {
  if (action.type === 'LOAD_DATA') {
    action.data; // TypeScript compiler throws an error on this line
  }
  return action;
}

I've tried using <S extends Action<string> & {data: any}> but then I get an error because {data: any} doesn't fit the type constraint on the function.

isaaclyman avatar May 11 '22 16:05 isaaclyman

Update: I've got it working with the following:

actionSanitizer: <S extends Action<string>>(action: S & {payload: any}): S => {
  if (action.type === 'LOAD_DATA') {
    action.payload; // This works
  }
  return action;
}

In my case I'm using flux-standard-action so actions have a payload attached.

isaaclyman avatar May 11 '22 17:05 isaaclyman

If your action(s) have a payload prop, you can also use the AnyAction type from redux:

const actionSanitizer = <A extends AnyAction>(action: A) => {
    ....
}

nathan-kansu avatar Jul 21 '22 15:07 nathan-kansu