J'utilise le hook useReducer pour enregistrer un état global. Parce que je souhaite enregistrer certains paramètres lorsque le navigateur est fermé, je sauvegarde ces paramètres dans le stockage local.
Pour le moment, j'utilise dispatch pour enregistrer le paramètre et une fonction séparée pour le sauvegarder dans le stockage local, mais ce serait bien si le paramètre est automatiquement enregistré après l'envoi. (parfois j'oublie d'enregistrer sur le stockage local et j'ai une différence entre le stockage local / local)
La lecture de l'état à partir du stockage local n'est pas un problème. J'utilise le paramètre initialState dans le hook useReducer pour cela.
Je pense que la réponse est de ne pas le faire, mais quelle est l'alternative? (sans utiliser redux)
3 Réponses :
Je pense qu'il est sage de confier à un service la responsabilité de stocker vos données depuis et vers localStorage. Vous pouvez donc créer un service avec une fonction get et une fonction set. Vous pouvez appeler le service à partir d'un effet dans redux.
À mon avis, le réducteur est là pour changer l'état.
Vous pouvez également utiliser rxjs pour attendre que le réducteur mette à jour votre état, puis le récupérer et l'enregistrer dans localStorage en utilisant votre service .
Au fait, en fonction de vos besoins, je ne vois vraiment aucune raison pour laquelle vous ne devriez pas stocker de données dans localStorage tant que (lors de l'utilisation de redux) vous gardez à l'esprit que l'état de redux vérité unique et unique. Stocker des données quelque part pour restaurer cet état lors de l'initialisation est une bonne approche à mon avis.
Voici un exemple utilisant ngrx:
@Effect()
myAction$: Observable<Action> = this.actions$.pipe(
ofType<MyAction>(actions.myAction),
debounceTime(300),
switchMap((action) =>
this.service.post(action.dto).pipe(
map((response: SomeDto) => {
// This updates the state using a reducer
return new CreateShortenedLinkSuccess(response);
}),
tap((value) => {
// Here, you can use the value param, to save
// changes to localStorage
this.router.navigate([`/mgmt/link/${value.dto.id}`]);
}),
catchError((response: HttpErrorResponse) => {
// Do scary stuff
})
)
)
);
J'ai un service (get / set), mais quand l'appeler sans redux? J'aime garder les états synchronisés pour éviter la confusion / les problèmes d'utilisateurs.
Vous pouvez ajouter une valeur dans votre état, par exemple initializedYn. Si c'est faux, vous utilisez les valeurs de localStorage pour produire un état initial, puis définissez initializedYn sur true. Une fois que cela est fait, votre boutique redux est TOUJOURS la vérité. Dans ce cas, vous stockez simplement les modifications dans le magasin dans localStorage, mais JAMAIS l'inverse.
L'état initial n'est pas un problème car useReducer a un paramètre supplémentaire pour cela. "Dans ce cas, vous stockez simplement les modifications dans le magasin dans localStorage mais JAMAIS l'inverse." -> mais comment?
Ensuite, utilisez simplement un effet, effectuez une action, changez l'état et utilisez l'effet pour enregistrer l'état modifié dans localStorage. Je mettrai à jour la réponse avec un exemple de ngrx
Désolé, mais la question concerne React. (voir tag)
Modifier : inspiré de cette belle approche concise de persistant localement un hook useState , c'est une meilleure approche pour persister localement un hook useReducer (non testé, mais devrait être à peu près correct):
// DO NOT USE THIS; see the updated implementation above instead
import { useRef, useReducer } from 'react'
function useLocallyPersistedReducer(reducer, defaultState, storageKey) {
const isLoading = useRef(true)
if (isLoading.current) {
try {
const persisted = JSON.parse(localStorage.getItem(storageKey))
defaultState = persisted
} catch (e) {
console.warn(`Failed to load state '${storageKey}' from localStorage; using default state instead`)
}
isLoading.current = false
}
const [ state, dispatch ] = useReducer(reducer, defaultState)
try {
localStorage.setItem(storageKey, JSON.stringify(state))
} catch (e) {
console.warn(`Failed to store updated state '${storageKey}'`)
}
return [ state, dispatch ]
}
Cela élimine le besoin de la référence supplémentaire et ajoute la prise en charge de la fonction init facultative . Plus important encore, il ne met à jour le stockage local que chaque fois que l'état est mis à jour, plutôt que sur chaque rendu.
Je jouais juste avec quelque chose de similaire et je voulais essayer de le réaliser en utilisant un crochet personnalisé qui pourrait être utilisé à la place de useReducer. Je suis venu avec le crochet approximatif suivant (j'ai ajouté un paramètre supplémentaire pour que je puisse spécifier la clé sous laquelle les données sont stockées):
import { useReducer, useEffect } from 'react'
function useLocallyPersistedReducer(reducer, defaultState, storageKey, init = null) {
const hookVars = useReducer(reducer, defaultState, (defaultState) => {
const persisted = JSON.parse(localStorage.getItem(storageKey))
return persisted !== null
? persisted
: init !== null ? init(defaultState) : defaultState
})
useEffect(() => {
localStorage.setItem(storageKey, JSON.stringify(hookVars[0]))
}, [storageKey, hookVars[0]])
return hookVars
}
Comme un travail difficile, cela fonctionne, mais il y a probablement quelques nuances que j'ai manquées. J'espère que cela vous donnera une idée de la façon dont vous pourriez l'aborder!
Intéressant. Aimerait que cela soit critiqué de manière constructive, pourrait être un modèle utile à adopter.
Question, pourquoi est-il nécessaire d'utiliser useRef dans ce cas? (juste curieux)
La raison pour laquelle j'ai inclus cela en premier lieu était parce que nous voulons un moyen de garantir que l'état est chargé la première fois que le hook est exécuté. Sans cela, il s'exécuterait à chaque fois que le composant serait ré-rendu.
@OamarKanji: en fait, il s'avère que vous n'avez pas besoin de la référence; voir la réponse mise à jour
J'ai trouvé le @Zaid Crouch excellent, mais il ne gère pas les mises à jour du stockage local provenant de différentes fenêtres (tabs / iframe / popup).
import { useReducer, useEffect, Dispatch } from 'react'
// Credits: https://stackoverflow.com/questions/54346866/save-to-localstorage-from-reducer-hook
export type ReducerType<T> = (state: T, action: ActionType) => (T)
export type InitType<T> = (state: T) => void
export interface ActionType {
type: string
}
var dispatcher:Dispatch<ActionType> | null = null
const LOCAL_STORAGE_UPDATED_IN_A_DIFFERENT_WINDOW = "LOCAL_STORAGE_UPDATED_IN_A_DIFFERENT_WINDOW"
export function useLocallyPersistedReducer<T>
(reducer: ReducerType<T>, defaultState: T, storageKey: string, init?:InitType<T>) : [T, Dispatch<ActionType>] {
const reducerWrapper = (status: T, action: ActionType) => {
switch (action.type){
case LOCAL_STORAGE_UPDATED_IN_A_DIFFERENT_WINDOW:
const statusReadFromLocalStorage = JSON.parse(localStorage.getItem(storageKey) || "{}")
return statusReadFromLocalStorage
default: return reducer(status, action)
}
}
const [state, dispatch] = useReducer(reducerWrapper, defaultState, (defaultState) => {
const persisted = JSON.parse(localStorage.getItem(storageKey) as string)
return persisted !== null
? persisted
: init ? init(defaultState) : defaultState
})
// NOTE Even if this codes runs multiple time we add only one event listener as long as the callback is singleton
dispatcher = dispatch
window.addEventListener('storage', listenForStorageChangesInOtherWindows, {once: true});
useEffect(() => {
localStorage.setItem(storageKey, JSON.stringify(state))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [localStorage, storageKey, state])
return [state, dispatch]
}
const listenForStorageChangesInOtherWindows = (key: any) => {
dispatcher && dispatcher({type: LOCAL_STORAGE_UPDATED_IN_A_DIFFERENT_WINDOW})
}
Cette question est similaire: stackoverflow.com/questions/50945152/...