access redux persist outside components - react-native

I'm trying to access a redux store with redux persist in a service for my react native app.
I need a specific token to set a websocket connection.
My code so far:
./redux/Store.js:
const persistedReducer = combineReducers({
tokens: persistReducer(secureConfig, TokensReducer),
});
const store = createStore(persistedReducer);
const configureStore = () => {
const persistor = persistStore(store);
return { persistor, store };
};
export default configureStore;
./redux/reducers/TokenReducer
const initialState = {
accessToken: null,
refreshToken: null
}
const TokensReducer = (state = initialState, action) {
// reducer
};
export default TokensReducer;
./service/websocket.js
import configureStore from '../redux/Store';
const { store } = configureStore();
console.log(store.getState().tokens);
The problem is, I'm not getting the persisted content, but I'm getting the initial state (accessToken = null, refreshToken = null).
When I access the store from inside my app (inside components inside and ), I get the correct values.
Edit:
when I wrap the console.log in a setTimeout() of let's say 1 second, it works! So it asynchronous, but how can I create my code to wait for it and not using setTimeout?

Related

Why isn't react native application re-rendering when redux State is changed using useSelector?

I'm trying to build a react native application using expo, firebase, and redux toolkit. With redux toolkit, I have created a slice that has an asyncThunk to get the data, such as a username, from firestore and I have an extra reducer that sets that data to the store.
In my screen file I'm using useSelector() to get the store value, and when I log the value I can see the store is changing but the screen isn't re-rendering. Also, I have a authentication slice in my store as well that works correctly but I don't know if that would mess anything up for this.
Store code:
export const store = configureStore({
reducer: {
firestore: firestoreSlice,
userAuth: authSlice,
},
})
Slice Code is below:
const initialState = {
isLoaded: false,
username: null
}
export const getUsername = createAsyncThunk('firestore/getUsername', async (userId) => {
const firestore = getFirestore();
const docRef = doc(firestore, "users", userId);
const docSnap = await getDoc(docRef);
const data = {
username: docSnap.data().username,
isLoaded: true
}
return data;
})
const firestoreSlice = createSlice({
name: 'firestore',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(getUsername.fulfilled, (state,action) => {
//state.isLoaded = action.payload.isLoaded;
//state.username = action.payload.username;
return Object.assign({}, state, {username: action.payload.username, isLoaded: action.payload.isLoaded})
})
}
});
export const selectUsername = (state) => state.firestore.username;
export const selectIsLoaded = (state) => state.firestore.isLoaded;
export default firestoreSlice.reducer;
Code inside Home Screen component below:
const username = useSelector(selectUsername);
const isLoaded = useSelector(selectIsLoaded);
useEffect(() => {
dispatch(getUsername(auth.currentUser.uid));
console.log(username + "..." + isLoaded);
}, [username]);
return(
<View style={styles.container}>
<StatusBar></StatusBar>
<Text>Home</Text>
{isLoaded == false ? (
<Text>Welcome, set your username in profile page</Text>
) : (
<Text>Welcome, {username}</Text>
)}
</View>
);
Console Log:
null...false
testing...true
In the console log, I can see the updated username and isLoaded values since UseEffect runs on changes to the username variable. However, the screen isn't re-rendering and I don't see the username displayed. I know that useSelector() only re-renders on reference changes to state, which is why in the extra reducer I tried mutating the state and creating a new object but neither caused the re-render.
Could someone help? Been stuck on this for like a week now!
Thanks!

How to update API path dynamically in VUEX state

I am trying to dynamically update the API path in my Vuex state. Vuex must have a default path "example.com/api/datasetA.json" set when the page loaded and I want to update the path to "example.com/api/datasetB.json" by the user interaction and fetch the new API data immediately.
The relevant part of my code is as follows (updated code):
VUEX:
export const state = () => ({
apiData: [],
apiId: 'datasetA.json'
});
export const mutations = {
fillApiData: (state, data) => {state.apiData = data},
updateApi: (state, newApiId) => {state.apiId = newApiId;}
};
export const actions = {
async getApiData({commit, state}) {
const response = await this.$axios.$get('https://example/api/'+state.apiId);
commit('fillApiData', response);
then VUE method as follows:
methods: {
updateApi(apiId) {
this.$store.commit('updateApi', apiId)
}
Create a mutation that changes the vuex state. Then run this mutation(commit) in the getApiData function
export const state = () => ({
apiData: [],
apiId: 'datasetA.json'
});
export const mutations = {
updateAPI(state, newApiId ) {
state.apiId = newApiId;
}
};
export const actions = {
async getApiData({commit, state}) {
const response = await this.$axios.$get('https://example/api/'+state.apiId);
commit('updateValue', response);
commit('updateAPI', 'some.new.datasetB.json');
}
}
I can update the state directly by using this.$store.state.apiId = apiId in methods but I know this is bad practice
You are correct. However, if you would like that approach to update the state outside Vuex, you can use mutations to change the Vuex - This is good practice.
Then you can do
this.$store.commit('updateAPI', 'my new value')

How to replace redux reducers and thunks with React Hooks API

I have been handed a project and been told to use React Hooks instead of Redux as much as possible. Is it possible to replace the reducers and thunks below with React Hooks? Is it worth it to replace?
Reducers/index.js
import { combineReducers } from 'redux'
import {createActions, createReducer, Types as ReduxSauceTypes} from 'reduxsauce'
import { reducer as ProfileReducer } from '#Reducers/Profile'
const appReducer = combineReducers({
profile: ProfileReducer,
// other reducers
})
const { Types, Creators: Actions } = createActions({
resetApp: []
})
const rootReducer = createReducer([], {
[Types.RESET_APP]: (state, action) => {
return appReducer(undefined, action)
//Passing undefined as state will make all the reducers using their initial states.
},
[ReduxSauceTypes.DEFAULT]: (state, action) => {
return appReducer(state, action)
}
})
const resetReduxStore = () => {
return dispatch => {
dispatch(Actions.resetApp())
}
}
export { rootReducer, resetReduxStore }
Reducers/Profile/index.js
import { createActions, createReducer } from 'reduxsauce';
export { default as thunks } from './thunks.js';
/* ------------- Initial State ------------- */
export const INITIAL_STATE = {
user: {},
};
/* ------------- Types and Action Creators ------------- */
export const { Types, Creators } = createActions({
setUser : ['user'],
});
/* ------------- Hookup Reducers To Types ------------- */
export const reducer = createReducer(INITIAL_STATE, {
[Types.SET_USER]: (state, { user }) => {
return {
...state,
user
};
},
});
export default Creators;
"Is it worth it?" is a matter of opinion, but I'll give mine.
Re: useReducer
React now has a useReducer hook that allows you to update a state by dispatching actions. You can use the same reducers that you have already. By default, useReducer only manages the state for the component that it's in. It doesn't come with a context provider. You could create your own context but at that point you are re-creating Redux.
My opinion: Use the React useReducer hook if you have state that it localized to one part of your app. Keep the reducer in some component and pass down callbacks and values via props. If you have state that is global in nature then use Redux.
Re: useState
You asked if you can replace your reducers. As I said, the useReducer hook would use the same sort of reducer so you wouldn't be replacing it. Perhaps the question you need to be asking is do I need a reducer-like system to update this state?
My Opinion: The code that you've posted here is extremely simple and it would be a good candidate for a basic useState hook.
const [user, setUser] = useState();
You could combine this with a context provider and a useContext hook to have a global profile state. If you start having multiple contexts for multiple states -- that's when you want to use Redux instead.
const UserContext = React.createContext([
undefined, // user
() => {} // setUser
]);
// takes no `value` because the state is internal
export const UserProvider = ({children}) => {
const [user, setUser] = React.useState<MaybeUser>();
return (
<UserContext.Provider value={[user, setUser]}>
{children}
</UserContext.Provider>
)
}
export const useUser = () => useContext(UserContext);
Usage in some component:
const Test = () => {
const [user, setUser] = useUser();
...
}
Re: Thunks
Thunks are function of dispatch so they can easily be rewritten with the useDispatch hook, if using Redux. You can use the useSelector hook instead of the getState() argument of a thunk.
There are lots of ways to replace thunks using hooks. But the only thunk that you have here is your resetReduxStore which really doesn't need to be a thunk.

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
};

Cart Items are not showing in cart after app restart

Cart Items are not showing in cart after app restart. I think its due to empty array cartItems.
import {AsyncStorage} from 'react-native'
const cartItems = [] //
AsyncStorage.getItem("cartItems").then(res=>
res!=null?res:[])
const initState = { cart: { items: cartItems } };
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducers,
initState,
composeEnhancer(applyMiddleware(thunk))
);
export default store;
It seems like this.props.cartItems is undefined which is why when you attempt to use Array.prototype.slice on them, it fails.
The issue seems to origin in the mapStateToProps, you're referencing state.cart.items but in your reducer, the structure seems to be state.items.
This should therefore work:
const mapStateToProps = (state) => ({
products: state.products.filteredItems,
cartItems: state.items,
});