web-dev-qa-db-ja.com

Reduxストアの状態をリセットする方法

私は状態管理にReduxを使用しています。
どうやってストアを初期状態にリセットしますか?

たとえば、2つのユーザーアカウント(u1u2)があるとします。
次の一連のイベントを想像してください。

  1. ユーザーu1がアプリにログインして何かをするので、ストアにデータをキャッシュします。

  2. ユーザーu1がログアウトします。

  3. ユーザーu2は、ブラウザを更新せずにアプリにログインします。

この時点で、キャッシュされたデータはu1に関連付けられているので、クリーンアップしたいと思います。

最初のユーザーがログアウトしたときにReduxストアを初期状態にリセットする方法

331
xyz

そのための1つの方法は、アプリケーションにルートリデューサーを書くことです。

ルートリデューサーは通常、アクションの処理をcombineReducers()によって生成されたリデューサーに委任します。ただし、USER_LOGOUTアクションを受け取るたびに、初期状態に戻ります。

たとえば、ルートリデューサーが次のようになっているとします。

const rootReducer = combineReducers({
  /* your app’s top-level reducers */
})

名前をappReducerに変更して、それに委任する新しいrootReducerを書くことができます。

const appReducer = combineReducers({
  /* your app’s top-level reducers */
})

const rootReducer = (state, action) => {
  return appReducer(state, action)
}

USER_LOGOUTアクションの後に初期状態を返すように新しいrootReducerを教える必要があります。知ってのとおり、リデューサーは、アクションに関係なく、最初の引数としてundefinedを指定して呼び出されると初期状態を返すようになっています。この事実を使用して、累積したstateappReducerに渡すときに条件付きで削除します。

 const rootReducer = (state, action) => {
  if (action.type === 'USER_LOGOUT') {
    state = undefined
  }

  return appReducer(state, action)
}

USER_LOGOUTが起動するたびに、すべてのリデューサーは新たに初期化されます。また、action.typeもチェックできるため、必要に応じて、最初とは異なる結果を返すこともあります。

繰り返しますが、完全に新しいコードは次のようになります。

const appReducer = combineReducers({
  /* your app’s top-level reducers */
})

const rootReducer = (state, action) => {
  if (action.type === 'USER_LOGOUT') {
    state = undefined
  }

  return appReducer(state, action)
}

ここでは状態を変えていないことに注意してください。別の関数に渡す前に、 参照をstateというローカル変数に再割り当てしているだけです。状態オブジェクトを変更すると、Reduxの原則に違反します。

redux-persist を使用している場合は、ストレージをクリーニングする必要があるかもしれません。 Redux-persistはあなたの状態のコピーをストレージエンジンに保存し、状態のコピーは更新時にそこからロードされます。

まず、適切な storage engine をインポートしてから、状態をundefinedに設定する前に解析して各記憶状態キーを消去する必要があります。

const rootReducer = (state, action) => {
    if (action.type === SIGNOUT_REQUEST) {
        // for all keys defined in your persistConfig(s)
        storage.removeItem('persist:root')
        // storage.removeItem('persist:otherKey')

        state = undefined;
    }
    return appReducer(state, action);
};
780
Dan Abramov

Dan Abramovによる受け入れられたコメントは、このアプローチと共にreact-router-reduxパッケージを使用したときに私たちが奇妙な問題を経験したことを除いて正しいことを指摘したいと思います。私たちの修正は、stateをundefinedに設定せず、現在のルーティングリデューサーを使用することでした。このパッケージを使用しているのであれば、以下の解決策を実装することをお勧めします。

const rootReducer = (state, action) => {
  if (action.type === 'USER_LOGOUT') {
    const { routing } = state
    state = { routing } 
  }
  return appReducer(state, action)
}
72
Ryan Irilli

アクションを定義します。

const RESET_ACTION = {
  type: "RESET"
}

それから、あなたの各レデューサーで、あなたがそれぞれのレデューサーを通して複数のアクションを処理するためにswitchまたはif-elseを使っていると仮定します。私はswitchのケースを取ります。

const INITIAL_STATE = {
  loggedIn: true
}

const randomReducer = (state=INITIAL_STATE, action) {
  switch(action.type) {
    case 'SOME_ACTION_TYPE':

       //do something with it

    case "RESET":

      return INITIAL_STATE; //Always return the initial state

   default: 
      return state; 
  }
}

RESETアクションを呼び出すときはいつでもこの方法で、あなたはデフォルトの状態でストアを更新するでしょう。

今、ログアウトのためにあなたは以下のように扱うことができます:

const logoutHandler = () => {
    store.dispatch(RESET_ACTION)
    // Also the custom logic like for the rest of the logout handler
}

ユーザーがログインするたびに、ブラウザは更新されません。ストアは常にデフォルトになります。

store.dispatch(RESET_ACTION)はアイディアを詳しく述べているだけです。あなたはたぶんその目的のために行動創造者を持つでしょう。もっと良い方法はLOGOUT_ACTIONを持っていることです。

このLOGOUT_ACTIONを派遣したら。カスタムミドルウェアは、Redux-SagaまたはRedux-Thunkのいずれかを使用してこのアクションを傍受できます。どちらの方法でも、別のアクション「RESET」を送ることができます。このようにストアのログアウトとリセットは同期的に行われ、あなたのストアは別のユーザーログインの準備が整います。

19
nirbhaygp
 const reducer = (state = initialState, { type, payload }) => {

   switch (type) {
      case RESET_STORE: {
        state = initialState
      }
        break
   }

   return state
 }

初期ストアにリセットしたい、すべてまたは一部のリデューサーによって処理されるアクションを起動することもできます。 1つのアクションで、自分の状態全体、または自分に合っていると思われる部分だけをリセットできます。これが最も簡単で制御しやすい方法だと私は思います。

11
Daniel petrov

Reduxでは次の解決策を適用しています。これは私のすべてのリデューサーにinitialStateを設定していることを前提としています(例:{user:{name、email}})。私はこれらのネストされたプロパティをチェックする多くのコンポーネントで、この修正で私は私のレンダリングメソッドが結合されたプロパティ条件で壊れるのを防ぎます。

const appReducer = combineReducers({
  tabs,
  user
})

const initialState = appReducer({}, {})

const rootReducer = (state, action) => {
  if (action.type === 'LOG_OUT') {
    state = initialState
  }

  return appReducer(state, action)
}
8
Rob Moorman

最良の答えに対する簡単な答えです。

const rootReducer = combineReducers({
    auth: authReducer,
    ...formReducers,
    routing
});


export default (state, action) => (
    action.type === 'USER_LOGOUT'
        ? rootReducer(undefined, action)
        : rootReducer(state, action)
)
5
Matt Carlotta

更新NGRX4

NGRX 4に移行している場合は、 移行ガイド から、自分のリデューサーを組み合わせるためのrootreducerメソッドがActionReducerMapメソッドに置き換えられていることに気付いたかもしれません。最初は、この新しいやり方で状態の再設定が難しくなる可能性があります。それは実際には単純明快ですが、これを行う方法は変わりました。

この解決策は NGRX4 Githubドキュメントのmeta-reducers APIセクションに触発されています。

まず、NGRXの新しいActionReducerMapオプションを使って、あなたがこのようにあなたのリデューサーを組み合わせているとしましょう:

//index.reducer.ts
export const reducers: ActionReducerMap<State> = {
    auth: fromAuth.reducer,
    layout: fromLayout.reducer,
    users: fromUsers.reducer,
    networks: fromNetworks.reducer,
    routingDisplay: fromRoutingDisplay.reducer,
    routing: fromRouting.reducer,
    routes: fromRoutes.reducer,
    routesFilter: fromRoutesFilter.reducer,
    params: fromParams.reducer
}

では、app.module内から状態をリセットしたいとしましょう。

//app.module.ts
import { IndexReducer } from './index.reducer';
import { StoreModule, ActionReducer, MetaReducer } from '@ngrx/store';
...
export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
    return function(state, action) {

      switch (action.type) {
          case fromAuth.LOGOUT:
            console.log("logout action");
            state = undefined;
      }

      return reducer(state, action);
    }
  }

  export const metaReducers: MetaReducer<any>[] = [debug];

  @NgModule({
    imports: [
        ...
        StoreModule.forRoot(reducers, { metaReducers}),
        ...
    ]
})

export class AppModule { }

`

そしてそれは基本的にNGRX 4で同じ効果を達成するための一つの方法です。

4
Tyler Brown

Dan、Ryan、およびRobのアプローチを組み合わせて、router状態を維持し、状態ツリー内の他のすべてを初期化することを考慮して、私はこれで終わりました。

const rootReducer = (state, action) => appReducer(action.type === LOGOUT ? {
    ...appReducer({}, {}),
    router: state && state.router || {}
  } : state, action);
4
Andy_D

Reduxに状態をリセットする機能を提供するためのコンポーネントを作成しました。ストアを強化し、リセットをトリガーするために特定のaction.typeをディスパッチするためにこのコンポーネントを使用する必要があります。実装の考え方は@Dan Abramovが言ったことと同じです。

Github: https://github.com/wwayne/redux-reset

3
wwayne

受け入れられた答えは私が私の事件を解決するのを助けました。しかし、私は、州全体をクリアしなければならないケースに遭遇しました。だから - 私はこのようにしました:

const combinedReducer = combineReducers({
    // my reducers 
});

const rootReducer = (state, action) => {
    if (action.type === RESET_REDUX_STATE) {
        // clear everything but keep the stuff we want to be preserved ..
        delete state.something;
        delete state.anotherThing;
    }
    return combinedReducer(state, action);
}

export default rootReducer;

これが他の人に役立つことを願っています:)

2
pesho hristov

状態をクリアするためのアクションを作成しました。そのため、ログアウトアクション作成者を派遣すると、状態をクリアするためのアクションも派遣されます。

ユーザーレコードアクション

export const clearUserRecord = () => ({
  type: CLEAR_USER_RECORD
});

ログアウトアクション作成者

export const logoutUser = () => {
  return dispatch => {
    dispatch(requestLogout())
    dispatch(receiveLogout())
    localStorage.removeItem('auth_token')
    dispatch({ type: 'CLEAR_USER_RECORD' })
  }
};

減速機

const userRecords = (state = {isFetching: false,
  userRecord: [], message: ''}, action) => {
  switch (action.type) {
    case REQUEST_USER_RECORD:
    return { ...state,
      isFetching: true}
    case RECEIVE_USER_RECORD:
    return { ...state,
      isFetching: false,
      userRecord: action.user_record}
    case USER_RECORD_ERROR:
    return { ...state,
      isFetching: false,
      message: action.message}
    case CLEAR_USER_RECORD:
    return {...state,
      isFetching: false,
      message: '',
      userRecord: []}
    default:
      return state
  }
};

これが最適かどうかわかりませんか。

2
Navinesh Chand

次の解決策は私のために働いた。

私はメタリデューサーに状態をリセットする機能を追加しました。

return reducer(undefined, action);

すべての減力剤を初期状態に設定します。代わりにundefinedを返すことは、ストアの構造が破壊されたという事実のためにエラーを引き起こしていました。

/reducers/index.ts

export function resetState(reducer: ActionReducer<State>): ActionReducer<State> {
  return function (state: State, action: Action): State {

    switch (action.type) {
      case AuthActionTypes.Logout: {
        return reducer(undefined, action);
      }
      default: {
        return reducer(state, action);
      }
    }
  };
}

export const metaReducers: MetaReducer<State>[] = [ resetState ];

app.module.ts

import { StoreModule } from '@ngrx/store';
import { metaReducers, reducers } from './reducers';

@NgModule({
  imports: [
    StoreModule.forRoot(reducers, { metaReducers })
  ]
})
export class AppModule {}
1
Pan Piotr

redux-actions を使用している場合は、handleActionsにHOF(Higher Order Function)を使用した簡単な回避策があります。

import { handleActions } from 'redux-actions';

export function handleActionsEx(reducer, initialState) {
  const enhancedReducer = {
    ...reducer,
    RESET: () => initialState
  };
  return handleActions(enhancedReducer, initialState);
}

そして、元のhandleActionsExの代わりにhandleActionsを使ってリデューサーを処理します。

Danの答え この問題について素晴らしい考えを与えてくれますが、私はredux-persistを使っているので、うまくいきませんでした。
redux-persistと共に使用した場合、単にundefinedの状態を渡しても永続的な動作が引き起こされないため、手動でアイテムをストレージから削除する必要があることがわかりました(私の場合はReact Native、したがってAsyncStorage)。

await AsyncStorage.removeItem('persist:root');

または

await persistor.flush(); // or await persistor.purge();

私のためにも機能しませんでした - 彼らはただ私を怒鳴りました。 (例: "Unexpected key _persist ..." のように文句を言う

それから、RESETというアクションタイプに遭遇したときに、すべての個々のリデューサーがそれぞれ独自の初期状態を返すようにすることだけを思いついた。そのようにして、固執は自然に処理されます。明らかに上記の効用関数(handleActionsEx)がなければ、私のコードはDRYのようには見えません(ただし、それは単なる一行、つまりRESET: () => initialStateです)が、私はメタプログラミングが大好きです。

1
elquimista

@ dan-abramov answerを拡張したもので、特定のキーがリセットされないようにする必要がある場合があります。

const retainKeys = ['appConfig'];

const rootReducer = (state, action) => {
  if (action.type === 'LOGOUT_USER_SUCCESS' && state) {
    state = !isEmpty(retainKeys) ? pick(state, retainKeys) : undefined;
  }

  return appReducer(state, action);
};
1
gsaandy

私のために働いた速くて簡単なオプションは redux-reset を使うことでした。これは簡単で、大規模なアプリケーション向けの高度なオプションもあります。

ストア作成の設定

import reduxReset from 'redux-reset'
...
const enHanceCreateStore = compose(
applyMiddleware(...),
reduxReset()  // Will use 'RESET' as default action.type to trigger reset
)(createStore)
const store = enHanceCreateStore(reducers)

ログアウト機能であなたの「リセット」を送ってください

store.dispatch({
type: 'RESET'
})

お役に立てれば

1

なぜreturn module.exports.default();を使わないのですか?)

export default (state = {pending: false, error: null}, action = {}) => {
    switch (action.type) {
        case "RESET_POST":
            return module.exports.default();
        case "SEND_POST_PENDING":
            return {...state, pending: true, error: null};
        // ....
    }
    return state;
}

注: /アクションのデフォルト値を{}に設定していることを確認してください。switchステートメント内でaction.typeをチェックするときにエラーが発生したくないため、問題ありません。

0

Dan Abramovの答えに加えて、state = undefinedの代わりにaction = {type: '@@ INIT'}としてactionを明示的に設定しないでください。上記のアクションタイプでは、すべてのリデューサーは初期状態を返します。

0
rupav jain

私は、受け入れられた答えが私のためにうまくいったことを見つけました、しかしそれはESLint no-param-reassignエラーを引き起こしました - https://eslint.org/docs/rules/no-param-reassign

これが私が代わりにそれを処理する方法です、状態のコピーを作成することを確実にしてください(それは私の理解では、実行するためのReduxyの事です...):

import { combineReducers } from "redux"
import { routerReducer } from "react-router-redux"
import ws from "reducers/ws"
import session from "reducers/session"
import app from "reducers/app"

const appReducer = combineReducers({
    "routing": routerReducer,
    ws,
    session,
    app
})

export default (state, action) => {
    const stateCopy = action.type === "LOGOUT" ? undefined : { ...state }
    return appReducer(stateCopy, action)
}

しかし、そのコピーを作成する別のリデューサー関数に渡すために状態のコピーを作成するのは、少し複雑すぎるのでしょうか。これはそれほど見やすくはありませんが、もっと要点がわかります。

export default (state, action) => {
    return appReducer(action.type === "LOGOUT" ? undefined : state, action)
}
0
skwidbreth

サーバーでは、私は変数があります: global.isSsr = true そして各レデューサーで、私はconstがあります: initialState ストアでは、各Reducerで次のことを行います。 appReducer.jsの例

 const initialState = {
    auth: {},
    theme: {},
    sidebar: {},
    lsFanpage: {},
    lsChatApp: {},
    appSelected: {},
};

export default function (state = initialState, action) {
    if (typeof isSsr!=="undefined" && isSsr) { //<== using global.isSsr = true
        state = {...initialState};//<= important "will reset the data every time there is a request from the client to the server"
    }
    switch (action.type) {
        //...other code case here
        default: {
            return state;
        }
    }
}

最後にサーバーのルーター上で:

router.get('*', (req, res) => {
        store.dispatch({type:'reset-all-blabla'});//<= unlike any action.type // i use Math.random()
        // code ....render ssr here
});
0
Ngannv

このアプローチは非常に正しいです:他を無視して保つために任意の特定の状態「NAME」を破壊します。

const rootReducer = (state, action) => {
    if (action.type === 'USER_LOGOUT') {
        state.NAME = undefined
    }
    return appReducer(state, action)
}
0
khalil zaidoun

Danの答えを基に構築された、TypeScriptを使用したときの私の回避策(reduxタイピングは最初の引数としてundefinedをreducerに渡すことを不可能にするので、初期ルート状態を定数でキャッシュします):

// store

export const store: Store<IStoreState> = createStore(
  rootReducer,
  storeEnhacer,
)

export const initialRootState = {
  ...store.getState(),
}

// root reducer

const appReducer = combineReducers<IStoreState>(reducers)

export const rootReducer = (state: IStoreState, action: IAction<any>) => {
  if (action.type === "USER_LOGOUT") {
    return appReducer(initialRootState, action)
  }

  return appReducer(state, action)
}


// auth service

class Auth {
  ...

  logout() {
    store.dispatch({type: "USER_LOGOUT"})
  }
}
0
Jacka

状態を初期状態にリセットするために、次のコードを書きました。

const appReducers = (state, action) =>
   combineReducers({ reducer1, reducer2, user })(
     action.type === "LOGOUT" ? undefined : state,
     action
);
0
JDev

セキュリティの観点から、ユーザーをログアウトさせるときに行うべき最も安全なことは、すべての永続的な状態(たとえば、cookie、localStorageIndexedDBWeb SQLなど)をリセットし、window.location.reload()を使用してページをハードリフレッシュすることです。ずさんな開発者が誤ってまたは故意にDOMなどのwindowに機密データを保存している可能性があります。すべての持続的な状態を消してブラウザを更新することが、前のユーザからの情報が次のユーザに漏洩しないことを保証する唯一の方法です。

(もちろん、共有コンピュータのユーザとしては、「プライベートブラウジング」モードを使用するか、ブラウザウィンドウを自分で閉じ、「クリアブラウジングデータ」機能などを使用する必要があります。ただし、開発者として常に誰もが期待できるわけではありません。その勤勉さ)

0
tlrobinson

Reduxが初期状態の同じ変数を参照しないようにするための私の考え:

// write the default state as a function
const defaultOptionsState = () => ({
  option1: '',
  option2: 42,
});

const initialState = {
  options: defaultOptionsState() // invoke it in your initial state
};

export default (state = initialState, action) => {

  switch (action.type) {

    case RESET_OPTIONS:
    return {
      ...state,
      options: defaultOptionsState() // invoke the default function to reset this part of the state
    };

    default:
    return state;
  }
};
0
Hani

次の解決策は私のために働きます。

私たちのアプリケーションの最初の開始/レデューサー状態はフレッシュおよび新規で、デフォルトは初期状態

APP初期負荷を持続させるためにデフォルト状態を呼び出すアクションを追加する必要があります。

reAssigntheデフォルト状態とリデューサーはnewと同じように動作します。 。

メインAPPコンテナ

  componentDidMount() {   
    this.props.persistReducerState();
  }

メインAPPリデューサー

const appReducer = combineReducers({
  user: userStatusReducer,     
  analysis: analysisReducer,
  incentives: incentivesReducer
});

let defaultState = null;
export default (state, action) => {
  switch (action.type) {
    case appActions.ON_APP_LOAD:
      defaultState = defaultState || state;
      break;
    case userLoginActions.USER_LOGOUT:
      state = defaultState;
      return state;
    default:
      break;
  }
  return appReducer(state, action);
};

状態をリセットするためのログアウト呼び出しアクション /

function* logoutUser(action) {
  try {
    const response = yield call(UserLoginService.logout);
    yield put(LoginActions.logoutSuccess());
  } catch (error) {
    toast.error(error.message, {
      position: toast.POSITION.TOP_RIGHT
    });
  }
}

これがあなたの問題を解決することを願っています!

0
Mukundhan