Confused about how should I work with Redux and Database - react-native

I'm trying to figure out how to use Redux in my React Native application.
I use Realm as a local database, and I'm pretty confused about how to implement it.
In Realm, I save an array of custom objects, so when the app starts I want to fetch the array and set it in the state of the app.
As far as I understand, I need to have an action, which looks like this:
export const fetchItems = () => {
return (dispatch) => {
dispatch({
type: "FETCH_ITEMS",
})
}
}
And have a reducer that looks kinda like this:
const initialState = {
items: [],
}
const reducer = (state = initialState, action: AnyAction) => {
switch (action.type) {
case ActionType.fetchItems:
return {
...state,
items: action.payload
}
break;
default:
return state;
}
}
But I'm not really sure how should I use this in my Home Screen for example.
I guess it should be something like this:
const { items } = useSelector(state => store.getState().items)
But then, when I'm adding a new item to the database, I should of course update the database, so when I should update the state? I tried to read articles, and watch some tutorials, but everyone works a little different and it is even more confusing.
So far, is what I wrote about Redux is right? should be using it like this?

Related

Redux Toolkit useSelector state not updating even though action is called

I've been banging my head against this, hopefully getting another pair of eyes on it will help.
I'm using Redux + Redux Toolkit in a React Native App, in a pretty simple way. I can tell (through a log statement) that my action is being called and the state is getting set, but my useSelector on the state never updates. I've tried it with shallowEqual as well, but that shouldn't be needed, since Redux Toolkit uses Immer and the object shouldn't pass an equality check after updating (most of the other similar issues I researched were due to that)
Here's my main slice, followed by all the related code. Pardon the code dump, but I want to give a full picture:
export interface Metadata {
title: string
author: string
firstLines: string
id: string
}
type MetadataState = Record<string, Metadata>
export const metadataSlice = createSlice({
name: "metadata",
initialState: {} as MetadataState,
reducers: {
setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
state = action.payload
console.log("new metadata: ", state)
},
addMetadata: (state: MetadataState, action: PayloadAction<Metadata>) => {
state[action.payload.id] = action.payload
}
}
});
I have an async action to load the metadata from AsyncStorage (like LocalStorage on mobile), as follows:
export function loadMetadata() {
return async (dispatch: AppDispatch, getState: () => RootState) => {
const maybeMetadata = await AsyncStorage.getItem("metadata");
if(maybeMetadata) {
dispatch(metadataSlice.actions.setMetadata(JSON.parse(maybeMetadata)))
return true
} else {
return false
}
}
}
And I dispatch that in my main component as follows:
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(loadMetadata())
}, [])
In another component, I'm trying to access the state simply by doing:
const metadata = useAppSelector(state => state.metadata)
Any idea what's going on? The state just never seems to update, even though I see my action being called and update the state within it. Is it not being dispatched correctly? I tried directly accessing the state with store.getState() and the state seems empty, is it somehow just not being set?
I'm honestly pretty lost, any help is appreciated.
The issue had to do with how Immer (which Redux Toolkit leverages for allowing mutable operations) works.
setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
state = action.payload
console.log("new metadata: ", state)
}
Instead of mutating state, I reassigned it, which messed up the way Immer keep track of draft states. The console.log statement returned the new state, but it didn't work with Immer. Instead, I needed to do this:
setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
// simply return the new state, since I'm changing the whole state
return action.payload
}
And it works fine now. I'm kind of surprised I didn't see this documented (it may be somewhere) or get some sort of warning, but good to know for the future!
An addition to Nathan's answer, to avoid linters flooding your code and for proper readability, instead of:
setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
return action.payload
}
Do it like this:
setMetadata: (state: MetadataState, action: PayloadAction<MetadataState>) => {
return {...state, ...action.payload}
}
By so doing, first parameter of the action state, is put to use as it should

redux state not updating (React-Native)

I have 8 APIs when called save on the same redux state, and I am saving them like this in the reducer:
case types.EXPLORE_CHEF_BY_TYPE:
return {
...state,
[action.data.type + '_chefs']: action.data.data,
};
for example it will be trending_chefs, new_chefs ...etc.
and in my component I have this:
function ExploreScreen({
...,
...exploreData
}) {
...
const mapStateToProps = (state) => {
return state.explore;
};
and to access the data I am doing this
const handleCategoryClick = useCallback((categoryName) => {
const selected =
selectedQuery.name.toLowerCase() + '_' + categoryName.toLowerCase();
if (exploreData[selected]) {
setSelectedCategory(categoryName);
setData(exploreData[selected]);
} else {
selectedQuery[`dataFunc_${categoryName}`]();
setSelectedCategory(categoryName);
}
}, []);
the problem is the data is not persisting, in the above function I am checking If I already called that API and if I did then I just fetch the data without calling the API. Am I doing something wrong here ?
I can't figure out what the problem is, since sometimes it works and sometimes it doesn't. Also. I think the exploreData in the state is causing the problem.
Thank you for the Help.

can't get data from server to NuxtJS Store

this is my code :
export const state = () => ({
products: []
});
export const getters = {
getProducts: state => {
return state.products;
}
};
export const mutations = {
SET_IP: (state, payload) => {
state.products = payload;
}
};
export const actions = () => ({
async getIP({ commit }) {
const ip = await this.$axios.$get("http://localhost:8080/products");
commit("SET_IP", ip);
}
});
the server is working nicely but i just can't get the data into the store
First of all, I highly recommend you rename your action and mutation to something like getProducts and SET_PRODUCTS instead of ip. Also make sure you change the variable name inside the action. While this doesn't change any functionality, it makes your code easier to read.
Second, maybe add a console.log(ip) right after you define the const in the action and see if you're getting the data you want in there. In most cases you're going to want to assign ip.data to your variable.
Lastly, make sure you're calling the action somewhere in the code.
You should do it like this:
this.$store.dispatch('getIP'); // Using your current name
this.$store.dispatch('getProducts'); // Using my recommended name

Where should I subscribe firebase fetch items

im building an react native with redux and Firebase Realtime Database, and I'm concerned about where to subscribe to fetch my items on a screen.
Im using useEffect to dispatch the subscription to firebase db:
useEffect(() => {
dispatch(userActions.fetchPets());
}, []);
and inside the action
export const fetchPets = () => {
return async dispatch => {
const user = await firebase.auth().currentUser;
firebase
.database()
.ref(`pets/${user.uid}`)
.on("child_added", snapshot => {
const pet = snapshot.val() || null;
dispatch({ type: ADD_PET, payload: pet });
});
};
};
My problem is when my screen re-render this action executes again filling with repeated data.
This is my reducer:
case ADD_PET:
return {
...state,
pets: [...state.pets, action.payload]
};
My question
Should I filter my state with key to delete repeated?
Should I put my subscription in another place? like a middleware or something? there is a pattern for this?
PS: "Sorry by my English"
the pattern you are using is fine.
If inside payload you have an array with new elements you have, your approach works fine. But, im assuming you are getting the same elements, just with any updated property. So, for example, if you have your pets store like this:
pets: [{id: 1, name: 'whatever'}], and your payload is : [{id: 1, name: 'whatever2'}], now you have both concatenated in your store, what is bad, because is the same object, updated.
So, if you will have the full list updated in the request, i would just change your reducer to this:
const initialState = { pets: [] };
case ADD_PET:
return {
...state,
pets: action.payload
};
So everytime you make the api request, you will have the updated list of elements.
Another case is if you get in the request only the updated, and you will have to filter your object based on ids, and then just replace the updated ones. But i dont think it is your case.

Updating a reducer from another reducer in react-native

I want to update the state of one reducer from another reducer in react-native.My code is like this.
This is the action.
export const doLike = payload => {
// Recieving response from the server
let updatedPost = {
Id: 1,
userId: 1,
postId: payload.post._id,
__v: 1,
count: payload.type === 1 ? 10 : 1
};
return {
type: SM_ACTION_LIKE,
post: updatedPost
};
};
This is the smReducer which accepts the action.
const initialState = {
post: {}
};
const smReducer = (state = initialState, action) => {
switch (action.type) {
case SM_ACTION_LIKE:
return {
...state,
post: action.post
};
break;
default:
return state;
}
return state;
};
export default smReducer;
Now I want to change the posts array of mainFeedReducer from here. My mainFeedReducer is this. I want to access the posts array of mainFeedReducer from smReducer.
const initialState = Immutable({
posts: [],
featuredWorkouts: []
});
const mainfeedReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_MAIN_FEED:
return {
...state,
posts: action.mainFeedData.posts,
featuredWorkouts: action.mainFeedData.featuredWorkouts
};
break;
default:
return state;
}
};
How can I achieve this?
Redux architecture revolves around a strict unidirectional data flow.
The correct way would be to design your reducers in such a way that they handle more data.
As mentioned in the docs
If a reducer needs to know data from another slice of state, the state tree shape may need to be reorganized so that a single reducer is handling more of the data.
Alternatives
You may consider using redux-thunk, since the inner
function, that recieves two parameters return (dispatch, getState),
has an access to the entire state map.
If you have an instance of your store object, then you can directly access at the states by doing store.getState()