How to Persist Mobx State Tree in React Native? - react-native

I need to persist a MST Store in React Native. The data is changed seldomly.
I'm confused between using AsyncStorage and AutoRun.

For persisting MST stores, you might be interested in using mst-persist, which, per the README, is currently a small wrapper around MST's onSnapshot and applySnapshot (disclaimer: I'm the creator).
To persist data in React Native with mst-persist backed by AsyncStorage, one would do:
import { types } from 'mobx-state-tree'
import { AsyncStorage } from 'react-native'
import { persist } from 'mst-persist'
const SomeStore = types.model('Store', {
name: 'John Doe',
age: 32
})
const someStore = SomeStore.create()
persist('some', someStore, {
storage: AsyncStorage, // default: localStorage
jsonify: true // if you use AsyncStorage, this should be true
// default: true
whitelist: ['name'] // only these keys will be persisted
}).then(() => console.log('someStore has been hydrated'))
My original use case for mst-persist was for React Native and the current README will actually point you to a commit in an OSS RN manga reader app I made as an example.
If you're interested in how to do it with MST without another library like mst-persist, the persistence source code is actually < 50 LoC currently. And minus some features, it's a brisk < 20 LoC:
import { onSnapshot, applySnapshot } from 'mobx-state-tree'
export const persist = (name, store, options = {}) => {
let {storage, jsonify} = options
onSnapshot(store, (_snapshot) => {
const snapshot = { ..._snapshot }
const data = !jsonify ? snapshot : JSON.stringify(snapshot)
storage.setItem(name, data)
})
return storage.getItem(name)
.then((data) => {
const snapshot = !jsonify ? data : JSON.parse(data)
applySnapshot(store, snapshot)
})
}
There are a handful of other examples out in the wild that show similar functionality as well, such as this gist that mst-persist is partly inspired by, this repo that uses HoCs and PersistGates similar to redux-persist, and this gist that takes multiple stores as an argument.

Related

Updates are slow with Redux

I'm building a React Native app, using Redux and AsyncStorage. When a user deletes an item, updates happen a bit slow (2-3 seconds).
I have an array of objects (rather small).
I delete an item in this array with such function:
let idFordeleteFav = categoryArrFav.map(function(el) {return el['name']}).indexOf(itemName)
let cleanedCategory = [...categoryArrFav.slice(0, idFordeleteFav), ...categoryArrFav.slice(idFordeleteFav+1)]
let completeNewList = {...allAffirmation, [category]: cleanedCategory}
props.dispatch(setAffArr(completeNewList))
My mapStateToProps looks like this:
const mapStateToProps = (state) => {
const { appR } = state
return {
allAffirmation: appR.allAffirmation,
affirmations: appR.affirmations}
}
I wonder, what I can do to update faster.
First of all, check how much middleware you have and the logic inside them. Also, I recommend you replace async storage with react native MMKV (https://github.com/mrousavy/react-native-mmkv) which is much faster:
You need to do 2 things. First, create a wrapper for MMKV because it is sync:
import { Storage } from 'redux-persist'
import { MMKV } from "react-native-mmkv"
const storage = new MMKV()
export const reduxStorage: Storage = {
setItem: (key, value) => {
storage.set(key, value)
return Promise.resolve(true)
},
getItem: (key) => {
const value = storage.getString(key)
return Promise.resolve(value)
},
removeItem: (key) => {
storage.delete(key)
return Promise.resolve()
},
}
The second step is migration from the async store for existing users if you have https://github.com/mrousavy/react-native-mmkv/blob/master/docs/MIGRATE_FROM_ASYNC_STORAGE.md

useEffect (with depencies from redux useSelector hooks) into custom hooks it's trigger on every import

I'm new in react native world and i'm on a new project with store (manage by redux).
I encounter an issue with custom hooks and useEffect
here my custom hooks
const useTheme = () => {
const [activeTheme, setActiveTheme] = useState();
const { id: universID, defaultTheme: universDefaultTheme } = useSelector(
(state) => state.univers
);
const { theme } = useSelector((state) => state);
const { themes: activeThemes } = useSelector((state) => state.settings);
const dispatch = useDispatch();
//set theme when univers change
useEffect(() => {
console.log('TODO TOO MANY CALLS!!!!!', universID);
if (universID) {
setTheme(
activeThemes.find((theme) => theme.univers === universID)?.theme
);
}
}, [universID]);
//get active theme of current univers
useEffect(() => {
setActiveTheme(
activeThemes.find((theme) => theme.univers === universID)?.theme
);
}, [activeThemes]);
... rest of code ...
return {
theme,
activeTheme,
setTheme,
};
}
on components i use
const {
theme: { colors },
} = useTheme();
My issue is that on every import the useEffect(()=>{},[universID]) is trigger. UniversID come from redux store.
If i understand clearly when i import useTheme() the reference of universID change because there are copy of universID from store created, and reference change.
if i pass universID as arguments to useTheme hooks there are no problem, cause reference is the same. But if i do this i need tu make a useSelector(universID) on every components who import useTheme hooks.
My understanding of mecanism is good ?
There are a way to get universID from store with the same reference on every import, for not trigger useEffect(,[universID]) on every import ? without pass universID as arguments of useTheme (i.e. useRef, useCallback) ?
Thanks for the time past to read (or better, to answer ;))

Can Redux store token sessions in React Native?

I am new to Redux and React Native and would like know if I can implement and store token sessions in Redux for keeping the user logged in after they close and reopen the app. I have found out some people recommend AsyncStorage but my app state is handled with Redux.
This is my Redux store which uses AsyncStorage too.
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import AsyncStorage from '#react-native-community/async-storage';
import reducer from './reducers/index'
const persistConfig = {
key: 'root',
version: 0,
storage: AsyncStorage
}
const persistedReducer = persistReducer(persistConfig, reducer)
const store = createStore(persistedReducer)
const persistor = persistStore(store)
export { store, persistor }
Would that be enough to keep a token session as I store other data in the same way?
redux store cannot restore data after closing and reopening the app.
import { AsyncStorage } from 'react-native'
you can store by
AsyncStorage.setItem(userSessionKey, userData)
and restore by
async function restoreSession() {
try {
const data = await AsyncStorage.getItem(userSessionKey)
const userData = JSON.parse(data)
if (userData !== null) {
return userData
} else {
throw new Error('User Data is empty')
}
} catch (error) {
//console.log(error)
return null
}
}
so, when the app starts, before navigating to main app,
restore the data, and add to redux store
Redux can't persist/keep the data of the store/reducer when you kill the application.
But with the help of redux-persist library, redux can persist the data of the reducer/store. Also if you user reudx-persist you don't have to manually create AsyncStorage calls for retrieving initially when app starts redux-persist will handle that for you. You can use different storage engines not just AsyncStorage more info here
In your case you can totally store user session token in redux with the help of redux-persist. Use Whitelist/blacklist in persist config to let redux-persist know which reducer to persist.
e.g.
// BLACKLIST
const persistConfig = {
key: 'root',
storage: storage,
blacklist: ['authReducer'] // navigation will not be persisted
};
// WHITELIST
const persistConfig = {
key: 'root',
storage: storage,
whitelist: ['authReducer'] // only navigation will be persisted
};

redux-persist/createPersistoid: error serializing state TypeError: Converting circular structure to JSON

I am trying to integrate redux-persist to my react-native project. The aim is to persist the redux store's data between application re-launch so that user does not need to log in each time they launch the app. Also, I want to store the previously viewed data on the local storage to avoid re-querying all the data each time app is re-launched.
I added the redux persist support as per the code below:
import { AsyncStorage } from 'react-native';
import { createStore, applyMiddleware } from 'redux';
//import { createLogger } from 'redux-logger';
import { persistStore, persistReducer } from 'redux-persist';
import rootReducer from '../reducers/index';
import ReduxThunk from 'redux-thunk';
import { createTransform } from 'redux-persist';
import JSOG from 'jsog';
export const JSOGTransform = createTransform(
(inboundState, key) => JSOG.encode(inboundState),
(outboundState, key) => JSOG.decode(outboundState),
)
const persistConfig = {
// Root
key: 'root',
// Storage Method (React Native)
storage: AsyncStorage,
//transforms: [JSOGTransform]
// Whitelist (Save Specific Reducers)
// whitelist: [
// 'authReducer',
// ],
// // Blacklist (Don't Save Specific Reducers)
// blacklist: [
// 'counterReducer',
// ],
};
// Middleware: Redux Persist Persisted Reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);
// Redux: Store
const store = createStore(
persistedReducer,
applyMiddleware(ReduxThunk),
);
// Middleware: Redux Persist Persister
let persistor = persistStore(store);
// Exports
export {
store,
persistor,
};
If I don't add jsog and use the transforms: [JSOGTransform] line in the persistConfig I get this error:
redux-persist/createPersistoid: error serializing state TypeError:
Converting circular structure to JSON
If I uncomment the 'transforms' line in the persistConfig (as per the suggestion here: https://github.com/rt2zz/redux-persist/issues/735), then I get this error:
Exception in HostObject::set: < unknown >
I am just persisting the returned "user" object from the firestore database in my redux-store. Without the redux-persist, there has been no problem, but with the persist added I am having this issue.
What type of circular problem would exist in the user object returned after successful login to firestore (using password/email auth.)?
Why would JSOG not work as suggested in the link above? Any alternatives to how I can solve this problem?
P.S. Not only the user returned from firestore is causing these errors, but any data returned from firestore seems to fail to persist.
I appreciate any helps!
Cheers...
I think I solved the problem. Instead of JSOG I installed flatted and used in for my redux-persist transform.
Working transform and persistConfig looks like this:
export const transformCircular = createTransform(
(inboundState, key) => Flatted.stringify(inboundState),
(outboundState, key) => Flatted.parse(outboundState),
)
const persistConfig = {
key: 'root',
storage: AsyncStorage,
stateReconciler: autoMergeLevel2,
transforms: [transformCircular]
};
Some developer have this problem because of the socket set in Redax, Otherwise, you can use this way as well
// SetTransform.js
import { createTransform } from 'redux-persist';
const SetTransform = createTransform(
// transform state on its way to being serialized and persisted.
(inboundState, key) => {
// convert mySet to an Array.
return { ...inboundState, mySet: [...inboundState.mySet] };
},
// transform state being rehydrated
(outboundState, key) => {
// convert mySet back to a Set.
return { ...outboundState, mySet: new Set(outboundState.mySet) };
},
// define which reducers this transform gets called for.
{ whitelist: ['someReducer'] }
);
export default SetTransform;
and
// PersistReducers
import storage from 'redux-persist/lib/storage';
import { SetTransform } from './transforms';
const persistConfig = {
key: 'root',
storage: storage,
transforms: [SetTransform]
};

Why is Ignite redux persist throwing immutable error when adding navigation reducer to whitelist?

I am using react native Ignite Andross Boilerplate and I am trying to persist the navigation state so that when the user close/open the app it will come back to the previous screen.
For exemple: The user is filling a sign up form then closes the app and cames back later to finish wherever he stoped.
Since redux persist is given out of the box with Ignite, I tought I could just add the navigation reducer to the persist whitelist and let redux persist to its job saving my nav state on local storage.
So I did this:
// App/Config/ReduxPersist.js
import immutablePersistenceTransform from '../Services/ImmutablePersistenceTransform'
import { AsyncStorage } from 'react-native'
// More info here: https://shift.infinite.red/shipping-persistant-reducers-7341691232b1
const REDUX_PERSIST = {
active: true,
reducerVersion: '1.1',
storeConfig: {
key: 'primary',
storage: AsyncStorage,
// Reducer keys that you do NOT want stored to persistence here.
blacklist: ['login', 'search'],
// Optionally, just specify the keys you DO want stored to persistence.
// An empty array means 'don't store any reducers' -> infinitered/ignite#409
whitelist: ['nav'],
transforms: [immutablePersistenceTransform]
}
}
export default REDUX_PERSIST
The thing is...I got this error as result:
And somehow I realized that the immutablePersistenceTransform was the one to blame. How did I know that ? Well...I didn't! I just saw that the only thing immutable related was that transform and I commented it.
So when my code is the following it works:
// App/Config/ReduxPersist.js
import immutablePersistenceTransform from '../Services/ImmutablePersistenceTransform'
import { AsyncStorage } from 'react-native'
// More info here: https://shift.infinite.red/shipping-persistant-reducers-7341691232b1
const REDUX_PERSIST = {
active: true,
reducerVersion: '1.1',
storeConfig: {
key: 'primary',
storage: AsyncStorage,
// Reducer keys that you do NOT want stored to persistence here.
blacklist: ['login', 'search'],
// Optionally, just specify the keys you DO want stored to persistence.
// An empty array means 'don't store any reducers' -> infinitered/ignite#409
whitelist: ['nav'],
//transforms: [immutablePersistenceTransform]
}
}
export default REDUX_PERSIST
// App/Services/ImmutablePersistenceTransform.js
Why is this error occuring ? Is commenting the transform the best workaround ? Any help will be welcome!!!
Also, the immutablePersistenceTransform is the following:
import R from 'ramda'
import Immutable from 'seamless-immutable'
// is this object already Immutable?
const isImmutable = R.has('asMutable')
// change this Immutable object into a JS object
const convertToJs = (state) => state.asMutable({deep: true})
// optionally convert this object into a JS object if it is Immutable
const fromImmutable = R.when(isImmutable, convertToJs)
// convert this JS object into an Immutable object
const toImmutable = (raw) => Immutable(raw)
// the transform interface that redux-persist is expecting
export default {
out: (state) => {
// console.log({ retrieving: state })
return toImmutable(state)
},
in: (raw) => {
// console.log({ storing: raw })
return fromImmutable(raw)
}
}
Any help ?
Man, i think that "nav" is not a Immutable data structure. That is why throw this error