tienda - javascript calcular precio
¿Cómo restablecer el estado de una tienda Redux? (27)
¿por qué no usas
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;
}
Nota:
asegúrese de establecer el valor predeterminado de la acción en
{}
y de que está bien porque no desea encontrar un error cuando verifica
action.type
dentro de la instrucción switch.
Estoy usando Redux para la gestión del estado.
¿Cómo restablezco la tienda a su estado inicial?
Por ejemplo, supongamos que tengo dos cuentas de usuario (
u1
y
u2
).
Imagine la siguiente secuencia de eventos:
-
El usuario
u1
inicia sesión en la aplicación y hace algo, por lo que almacenamos algunos datos en la tienda. -
El usuario
u1
sesión. -
El usuario
u2
inicia sesión en la aplicación sin actualizar el navegador.
En este punto, los datos almacenados en caché se asociarán con
u1
, y me gustaría limpiarlos.
¿Cómo puedo restablecer la tienda Redux a su estado inicial cuando el primer usuario cierra sesión?
ACTUALIZACIÓN NGRX4
Si está migrando a NGRX 4, puede haber notado en la guía de migración que el método de reducción de raíz para combinar sus reductores ha sido reemplazado por el método ActionReducerMap. Al principio, esta nueva forma de hacer las cosas podría hacer que restablecer el estado sea un desafío. En realidad es sencillo, pero la forma de hacerlo ha cambiado.
Esta solución está inspirada en la sección de API de meta reductores de los documentos de NGRX4 Github.
Primero, digamos que está combinando sus reductores de esta manera usando la nueva opción ActionReducerMap de NGRX:
//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
}
Ahora, supongamos que desea restablecer el estado desde 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 { }
``
Y esa es básicamente una forma de lograr el mismo efecto con NGRX 4.
Además de la respuesta de Dan Abramov, ¿no deberíamos establecer explícitamente la acción como action = {type: ''@@ INIT''} junto con state = undefined. Con el tipo de acción anterior, cada reductor devuelve el estado inicial.
Combinando los enfoques de Dan, Ryan y Rob, para tener en cuenta el estado del
router
e inicializar todo lo demás en el árbol de estado, terminé con esto:
const rootReducer = (state, action) => appReducer(action.type === LOGOUT ? {
...appReducer({}, {}),
router: state && state.router || {}
} : state, action);
Con Redux si he aplicado la siguiente solución, que supone que he establecido un Estado inicial en todos mis reductores (por ejemplo, {usuario: {nombre, correo electrónico}}). En muchos componentes verifico estas propiedades anidadas, por lo que con esta solución evito que mis métodos de renderización se rompan en condiciones de propiedad acopladas (por ejemplo, si state.user.email, que arrojará un error, el usuario no está definido si las soluciones mencionadas anteriormente).
const appReducer = combineReducers({
tabs,
user
})
const initialState = appReducer({}, {})
const rootReducer = (state, action) => {
if (action.type === ''LOG_OUT'') {
state = initialState
}
return appReducer(state, action)
}
Creé un componente para darle a Redux la capacidad de restablecer el estado, solo necesita usar este componente para mejorar su tienda y enviar un tipo de acción específico para activar el restablecimiento. La idea de implementación es la misma que dijo @Dan Abramov.
Definir una acción:
const RESET_ACTION = {
type: "RESET"
}
Luego, en cada uno de sus reductores, suponiendo que esté usando el
switch
o,
if-else
para manejar múltiples acciones a través de cada reductor.
Voy a tomar el caso por un
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;
}
}
De esta forma, siempre que llame a la acción
RESET
, su reductor actualizará la tienda con el estado predeterminado.
Ahora, para cerrar sesión, puede manejar lo siguiente a continuación:
const logoutHandler = () => {
store.dispatch(RESET_ACTION)
// Also the custom logic like for the rest of the logout handler
}
Cada vez que un usuario inicia sesión, sin una actualización del navegador. La tienda siempre estará por defecto.
store.dispatch(RESET_ACTION)
simplemente elabora la idea.
Lo más probable es que tengas un creador de acción para ese propósito.
Una forma mucho mejor será que tengas un
LOGOUT_ACTION
.
Una vez que
LOGOUT_ACTION
este
LOGOUT_ACTION
.
Un middleware personalizado puede interceptar esta acción, ya sea con Redux-Saga o Redux-Thunk.
Sin embargo, en ambos sentidos, puede enviar otra acción ''RESTAURAR''.
De esta manera, el cierre de sesión y el restablecimiento de la tienda se realizarán de forma sincrónica y su tienda estará lista para el inicio de sesión de otro usuario.
Descubrí que la respuesta aceptada funcionó bien para mí, pero provocó el error ESLint
no-param-reassign
parámetros -
https://eslint.org/docs/rules/no-param-reassign
Así es como lo manejé en su lugar, asegurándome de crear una copia del estado (que, a mi entender, es lo que debe hacer 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)
}
¿Pero tal vez crear una copia del estado para simplemente pasarlo a otra función reductora que crea una copia de eso es un poco demasiado complicado? Esto no se lee tan bien, pero es más directo:
export default (state, action) => {
return appReducer(action.type === "LOGOUT" ? undefined : state, action)
}
Desde una perspectiva de seguridad, lo más seguro al cerrar la sesión de un usuario es restablecer todos los estados persistentes (por ejemplo, cookies,
localStorage
,
IndexedDB
,
Web SQL
, etc.) y hacer una actualización completa de la página usando
window.location.reload()
.
Es posible que un desarrollador descuidado haya almacenado accidental o intencionalmente algunos datos confidenciales en la
window
, en el DOM, etc. Eliminar todo estado persistente y actualizar el navegador es la única forma de garantizar que no se filtre información del usuario anterior al siguiente usuario.
(Por supuesto, como usuario en una computadora compartida, debe usar el modo "navegación privada", cerrar la ventana del navegador usted mismo, usar la función "borrar datos de navegación", etc., pero como desarrollador no podemos esperar que todos estén siempre que diligente)
Este enfoque es muy correcto: Destruya cualquier estado específico "NOMBRE" para ignorar y conservar a los demás.
const rootReducer = (state, action) => {
if (action.type === ''USER_LOGOUT'') {
state.NAME = undefined
}
return appReducer(state, action)
}
He creado acciones para borrar el estado. Entonces, cuando despacho un creador de acciones de cierre de sesión, también despacho acciones para borrar el estado.
Acción de registro de usuario
export const clearUserRecord = () => ({
type: CLEAR_USER_RECORD
});
Creador de acciones de cierre de sesión
export const logoutUser = () => {
return dispatch => {
dispatch(requestLogout())
dispatch(receiveLogout())
localStorage.removeItem(''auth_token'')
dispatch({ type: ''CLEAR_USER_RECORD'' })
}
};
Reductor
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
}
};
No estoy seguro si esto es óptimo?
La respuesta aceptada me ayudó a resolver mi caso. Sin embargo, encontré un caso en el que no todo el estado tuvo que ser borrado. Entonces, lo hice de esta manera:
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;
Espero que esto ayude a alguien más :)
La siguiente solución funcionó para mí.
Agregué restablecer la función de estado a los meta reductores. La clave era usar
return reducer(undefined, action);
para configurar todos los reductores al estado inicial.
En cambio, regresar
undefined
estaba causando errores debido al hecho de que la estructura de la tienda ha sido destruida.
/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 {}
La siguiente solución me funciona.
Primero, al iniciar nuestra aplicación, el estado del reductor es nuevo y nuevo con InitialState predeterminado.
Tenemos que agregar una acción que invoca la carga inicial de la aplicación para mantener el estado predeterminado .
Al cerrar sesión en la aplicación, podemos reasignar el estado predeterminado y el reductor funcionará como nuevo .
Contenedor de la aplicación principal
componentDidMount() {
this.props.persistReducerState();
}
Principal APP Reductor
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);
};
Acción de llamada al cerrar sesión para restablecer el estado
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
});
}
}
¡Espero que esto resuelva tu problema!
Me gustaría señalar que el comentario aceptado por Dan Abramov es correcto, excepto que experimentamos un problema extraño al usar el paquete react-router-redux junto con este enfoque.
Nuestra solución fue no establecer el estado en
undefined
sino utilizar el reductor de enrutamiento actual.
Por lo tanto, sugeriría implementar la solución a continuación si está utilizando este paquete
const rootReducer = (state, action) => {
if (action.type === ''USER_LOGOUT'') {
const { routing } = state
state = { routing }
}
return appReducer(state, action)
}
Mi opinión para evitar que Redux haga referencia a la misma variable del estado inicial:
// 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;
}
};
Mi solución alternativa cuando trabajo con mecanografiado, basado en la respuesta de Dan (los tipos redux hacen que sea imposible pasar
undefined
al reductor como primer argumento, por lo que guardo en caché el estado raíz inicial en una constante):
// 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"})
}
}
Otra opción es:
store.dispatch({type: ''@@redux/INIT''})
''@@redux/INIT''
es el tipo de acción que redux se distribuye automáticamente cuando
createStore
, por lo que suponiendo que todos sus reductores ya tengan un valor predeterminado, esto quedaría atrapado por ellos y comenzaría su estado de nuevo.
Sin embargo, podría considerarse un detalle de implementación privada de redux, por lo que el comprador tenga cuidado ...
Para restablecer el estado a su estado inicial, escribí el siguiente código:
const appReducers = (state, action) =>
combineReducers({ reducer1, reducer2, user })(
action.type === "LOGOUT" ? undefined : state,
action
);
Si está usando
redux-actions
, aquí hay una solución rápida usando HOF (
función de orden superior
) para
handleActions
.
import { handleActions } from ''redux-actions'';
export function handleActionsEx(reducer, initialState) {
const enhancedReducer = {
...reducer,
RESET: () => initialState
};
return handleActions(enhancedReducer, initialState);
}
Y luego use
handleActionsEx
lugar de
handleActions
original para manejar los reductores.
La respuesta de Dan
da una gran idea sobre este problema, pero no funcionó bien para mí, porque estoy usando
redux-persist
.
Cuando se usa con
redux-persist
, simplemente pasar el estado
undefined
no desencadenó un comportamiento persistente, por lo que sabía que tenía que eliminar manualmente el elemento del almacenamiento (React Native en mi caso, por lo tanto
AsyncStorage
).
await AsyncStorage.removeItem(''persist:root'');
o
await persistor.flush(); // or await persistor.purge();
tampoco funcionó para mí, solo me gritaron. (por ejemplo, quejándose como "Clave inesperada _persist ..." )
Luego, de repente, pensé que todo lo que quiero es hacer que cada reductor individual devuelva su propio estado inicial cuando se encuentra el tipo de acción
RESET
.
De esa manera, la persistencia se maneja naturalmente.
Obviamente, sin la función de utilidad anterior (
handleActionsEx
), mi código no se verá SECO (aunque es solo una línea, es decir,
RESET: () => initialState
), pero no podría soportarlo porque me encanta la metaprogramación.
Simplemente haga que su enlace de cierre de sesión borre la sesión y actualice la página. No se necesita código adicional para su tienda. Cada vez que desee restablecer completamente el estado, una actualización de la página es una forma simple y fácilmente repetible de manejarlo.
Solo una extensión de la respuesta , a veces es posible que se reinicien ciertas claves.
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);
};
Solo una respuesta simplificada a la mejor respuesta:
const rootReducer = combineReducers({
auth: authReducer,
...formReducers,
routing
});
export default (state, action) => (
action.type === ''USER_LOGOUT''
? rootReducer(undefined, action)
: rootReducer(state, action)
)
Una forma de hacerlo sería escribir un reductor de raíz en su aplicación.
El reductor raíz normalmente delegaría el manejo de la acción al reductor generado por
combineReducers()
.
Sin embargo, cada vez que recibe la acción
USER_LOGOUT
, devuelve el estado inicial nuevamente.
Por ejemplo, si su reductor de raíz se ve así:
const rootReducer = combineReducers({
/* your app’s top-level reducers */
})
Puede cambiarle el nombre a
appReducer
y escribir un nuevo
rootReducer
delegando en él:
const appReducer = combineReducers({
/* your app’s top-level reducers */
})
const rootReducer = (state, action) => {
return appReducer(state, action)
}
Ahora solo tenemos que enseñarle al nuevo
rootReducer
a devolver el estado inicial después de la acción
USER_LOGOUT
.
Como sabemos, se supone que los reductores devuelven el estado inicial cuando se llaman con
undefined
como primer argumento, sin importar la acción.
appReducer
este hecho para eliminar condicionalmente el
state
acumulado a medida que lo
appReducer
a
appReducer
:
const rootReducer = (state, action) => {
if (action.type === ''USER_LOGOUT'') {
state = undefined
}
return appReducer(state, action)
}
Ahora, cuando
USER_LOGOUT
active, todos los reductores se inicializarán de nuevo.
También pueden devolver algo diferente de lo que hicieron inicialmente si lo desean porque también pueden verificar
action.type
.
Para reiterar, el nuevo código completo se ve así:
const appReducer = combineReducers({
/* your app’s top-level reducers */
})
const rootReducer = (state, action) => {
if (action.type === ''USER_LOGOUT'') {
state = undefined
}
return appReducer(state, action)
}
Tenga en cuenta que no estoy mutando el estado aquí, simplemente estoy
reasignando la referencia
de una variable local llamada
state
antes de pasarla a otra función.
La mutación de un objeto de estado sería una violación de los principios de Redux.
En caso de que esté usando redux-persist , es posible que también necesite limpiar su almacenamiento. Redux-persist mantiene una copia de su estado en un motor de almacenamiento, y la copia de estado se cargará desde allí en la actualización.
Primero, debe importar el
motor de almacenamiento
apropiado y luego, analizar el estado antes de configurarlo como
undefined
y limpiar cada clave de estado de almacenamiento.
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);
};
Una opción rápida y fácil que funcionó para mí fue usar redux-reset . Lo cual era sencillo y también tiene algunas opciones avanzadas para aplicaciones más grandes.
Configuración en crear tienda
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)
Despacha tu ''reset'' en tu función de cierre de sesión
store.dispatch({
type: ''RESET''
})
Espero que esto ayude
en el servidor, tengo una variable es: global.isSsr = true y en cada reductor, tengo un const es: initialState Para restablecer los datos en la Tienda, hago lo siguiente con cada Reductor: ejemplo con 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;
}
}
}
finalmente en el enrutador del servidor:
router.get(''*'', (req, res) => {
store.dispatch({type:''reset-all-blabla''});//<= unlike any action.type // i use Math.random()
// code ....render ssr here
});
const reducer = (state = initialState, { type, payload }) => {
switch (type) {
case RESET_STORE: {
state = initialState
}
break
}
return state
}
También puede activar una acción manejada por todos o algunos reductores, que desea restablecer a la tienda inicial. Una acción puede desencadenar un restablecimiento de todo su estado, o solo una parte que le parezca adecuada. Creo que esta es la forma más simple y controlable de hacerlo.