I'm new to Redux (not React-Native) and The most straightforward way I found to use Redux is to use it with the createSlice function.
Here is the slice -
import AsyncStorage from "#react-native-async-storage/async-storage";
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
isLoggedIn: false,
cart: null
}
const Slice = createSlice({
name: 'userAuth',
initialState,
reducers: {
setLogin: (state, action) => {
state.isLoggedIn = action.payload.isLoggedIn;
AsyncStorage.setItem("isLoggedIn", JSON.stringify(state.isLoggedIn));
},
setLogout: (state) => {
state.isLoggedIn = false;
AsyncStorage.clear();
},
setCart: (state, action) => {
state.cart = action.payload.cart;
AsyncStorage.setItem("cart", JSON.stringify(action.payload.cart));
},
startUp: (state, action) => {
state.cart = action.payload.cart;
state.isLoggedIn = action.payload.isLoggedIn;
}
}
});
export const { setLogin, setLogout, setCart, startUp } = Slice.actions;
export const selectIsLoggedIn = (state) => state.userAuth.isLoggedIn;
export const selectCart = (state) => state.userAuth.cart;
export default Slice.reducer;
I'm using the useDispatch hook for updating the state.
const dispatch = useDispatch();
dispatch(setCart({cart: cartObj}));
But it leads me to an error-
WARN Possible Unhandled Promise Rejection (id: 0):
TypeError: Attempted to assign to readonly property.
onChange
http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:191470:48
generatorResume#[native code]
asyncGeneratorStep#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:25072:26
_next#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:25094:29
http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:25101:14
tryCallTwo#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:30613:9
doResolve#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:30777:25
Promise#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:30636:14
http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:25090:25
onChange#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:191487:33
http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:191758:24
generatorResume#[native code]
asyncGeneratorStep#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:25072:26
_next#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:25094:29
tryCallOne#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:30604:16
http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:30705:27
http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:31784:26
_callTimer#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:31684:17
_callReactNativeMicrotasksPass#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:31719:17
callReactNativeMicrotasks#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:31926:44
__callReactNativeMicrotasks#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:24002:46
http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:23781:45
__guard#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:23985:15
flushedQueue#http://192.168.0.100:19000/node_modules/expo/AppEntry.bundle?platform=android&dev=true&hot=false:23780:21
flushedQueue#[native code]
callFunctionReturnFlushedQueue#[native code]
I'm using Expo-CLI on Ubuntu 22.04.
Please tell me the right way to do this.
You will need to use Redux Persist to perform state management with Async Storage.
Check this out for further instructions.
https://blog.logrocket.com/use-redux-persist-react-native/
setting data into the asyncStorage is asynchronous.
Under no circonstances you should not run an asynchronous value into reducer(should 100% synchronus). instead move the asyncStorage code to your api request function,then set it into asyncStorage.
Related
When kept in the component body, the following code works fine. Inside useEffect, it checks the asyncstorage and dispatches an action (the function is longer but other checks/dispatches in the function are of the same kind - check asyncstorage and if value exists, dispatch an action)
useEffect(() => {
const getSettings = async () => {
const aSet = await AsyncStorage.getItem('aSet');
if (aSet) {
dispatch(setASet(true));
}
};
getSettings();
}, [dispatch]);
I'm trying to move it to a custom hook but am having problems. The custom hook is:
const useGetUserSettings = () => {
const dispatch = useDispatch();
useEffect(() => {
const getSettings = async () => {
const aSet = await AsyncStorage.getItem('aSet');
if (aSet) {
dispatch(setASet(true));
}
};
getSettings();
}, [dispatch]);
};
export default useGetUserSettings;
Then in the component where I want to call the above, I do:
import useGetUserSettings from './hooks/useGetUserSettings';
...
const getUserSettings = useGetUserSettings();
...
useEffect(() => {
getUserSettings();
}, [getUserSettings])
It returns an error:
getUserSettings is not a function. (In 'getUserSettings()', 'getUserSettings' is undefined
I've been reading rules of hooks and browsing examples on the internet but I can get it working. I've got ESlint set up so it'd show if there were an invalid path to the hook.
Try the following.
useEffect(() => {
if (!getUserSettings) return;
getUserSettings();
}, [getUserSettings]);
The hook doesn't return anything, so it's not surprising that the return value is undefined ;)
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')
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.
I am new to React native and I came across code for a reducer function but I am confused on why "token" is wrapped in brackets. Does it make token into a dynamic thing or something?
Can someone please explain why it is so? Thank you so much!
/** The reducer is in charge of updating the app state based on the dispatched action. **/
//Action Types
export const CREDENTIALED = 'auth/CREDENTIALED';
export const RESET_DATA = 'auth/RESET_DATA';
export const initialState = {
isLoading: true,
token: null,
};
//REDUCER
const authReducer = (state = initialState, action) => {
switch (action.type) {
case CREDENTIALED: {
let {token} = action;
return {...state, isLoading: true, token};
}
case RESET_DATA: {
return {...state, ...initialState};
}
default:
return state;
}
};
export default authReducer;
Not a react native expert by any stretch of the imagination, but it looks like token is just being destructured from action. Roughly the same as:
const dict = {
"alpha": 'a',
"beta": 'b',
}
const {alpha} = dict
console.log(alpha) // you should expect "a" to be printed out
I'm trying to get some data by nuxtServerInit and save it in state
store/index.js
import { fireDb } from '~/plugins/firebase'
export const state = () => ({
posts: []
})
export const mutations = {
addPosts (state, post) {
state.posts.push(post)
console.log('mutation =>', state.posts.length)
}
}
export const actions = {
nuxtServerInit (state, ctx) {
fireDb.collection('posts').orderBy('timestamp', 'desc').limit(3).get().then((snapshot) => {
snapshot.forEach((doc) => {
state.commit('addPosts', doc.data())
})
console.log('action => ', state.posts.length)
})
}
}
when I run this code console output is
mutation => 1
mutation => 2
mutation => 3
ERROR Cannot read property 'length' of undefined
And vue dev tools also doesn't show there's data inside posts[].
What am I missing here?
It looks like nuxtServerInit is dispatched as an action with the Nuxt context. Being an action, the first argument will be the Vuex context.
The Vuex context exposes several properties including state and commit.
The docs also say:
Note: Asynchronous nuxtServerInit actions must return a Promise or leverage async/await to allow the nuxt server to wait on them.
You can change your code to:
async nuxtServerInit({state, commit}, ctx) {
let snapshot = await fireDb.collection('posts').orderBy('timestamp', 'desc').limit(3).get();
snapshot.forEach((doc) => {
commit('addPosts', doc.data())
});
console.log('action => ', state.posts.length)
}