Where should I subscribe firebase fetch items - react-native

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.

Related

Can I return value from action creator using redux-thunk?

I've seen a lot of examples of async action creators, but they all do some sort of fetching and pushing data to redux store and return nothing. I need another logic that looks something like:
const createUserAction = (user) => {
firestore().collection('users').add(user)
.then(result => {
dispatch({type: 'SET_USER', payload: {...user, id: result.id}})
})
}
I need to return result.id from createUserAction to navigate to page that displays user by his id. In my imagine it should work like
createUserAction({name: John}).then(id => navigation.navigate('UserDetailPage', {userId: id}))
I don't know how to implement that and I'll be glad if somebody can help
Returning values from action creators is a No-Go. The solution for this scenario that I've used and think is better is to do the redirect in the async action itself:
// afterCreation = callback function with one argument, the created user
const createUserAction = async (user, afterCreation) => {
const createdUser = await firestore().collection('users').add(user);
dispatch({type: 'SET_USER', payload: {...user, id: createdUser.id}});
afterCreation(createdUser);
};
createUserAction(
{name: John},
// Pass callback to action creator
(user) => navigation.navigate('UserDetailPage', {userId: user.id})
);

Confused about how should I work with Redux and Database

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?

How to properly initialize Vuex state to avoid additional API calls

Within a records module, there is an action, getRecords to retrieve all records from the API at /records, which commits the setRecords mutation which sets the state for records. A getter for records also exists.
Within the records vue, the created method calls the getRecords action, and the getter for records is passed to the datatable for display.
Until now everything works, however when navigating on and off the records vue, the API is called each time.
How is the properly handled? Does a best practice exist? I can move the action call to a higher level, but this generates an API that may not be required id the user never visits the records vue.
records module
const getters = {
allRecords: state => state.records
}
const state = {
records: []
}
const actions = {
async getRecords({commit}){
console.log('getting records...');
const response = await axios.get('/api/records')
commit('setRecords', response.data)
},
async addRecord({ commit }, user) {
console.log("user :" + JSON.stringify(user))
const response = await axios.post('/api/records', user)
.catch(err => console.log(err))
commit('addRecord', response.data)
}
}
const mutations = {
setRecords: (state, records) => (state.records = records),
addRecord: (state, user) => ([...state.records, user])
}
export default {
state,
getters,
actions,
mutations
}
I have handled this in various different ways in the past.
If you do not care if the data you are serving might be old, you can simply detect if you already have some items in your array:
const actions = {
async getRecords({ commit, getters }){
if (getters.allRecords.length > 0) {
// Don't bother retrieving them anymore
return;
}
console.log('getting records...');
const response = await axios.get('/api/records')
commit('setRecords', response.data)
},
async addRecord({ commit }, user) {
console.log("user :" + JSON.stringify(user))
const response = await axios.post('/api/records', user)
.catch(err => console.log(err))
commit('addRecord', response.data)
}
}
If you do want to have the updated values in your database, you can consider changing your api to only return changed records after a certain timestamp. For this we need the following:
We need to store the timestamp of our last update. The first time we would retrieve everything, and on subsequent requests we would send the timestamp.
A way to identify which records to update in the local state, and which records to delete or add. Having something like an id on your records might be helpful.
Let's assume that instead of returning a flat array of your records, the api returns a response in the format
{
records: [ ... ],
removedRecords: [ ... ],
timestamp: 123456789
}
You would change your state to
const state = {
records: [],
recordUpdateTimestamp: null
}
Your action would then look something like this:
async getRecords({ commit, state }){
const config = {};
if (state.recordUpdateTimestamp) {
config.params = {
timestamp: state.recordUpdateTimestamp
};
}
console.log('getting records...');
const { data }= await axios.get('/api/records', config)
commit('setRecords', data.records);
commit('removeRecords', data.removedRecords);
commit('setRecordUpdateTimestamp', data.timestamp);
},
I will leave writing the mutations to you.
This would obviously need work in the backend to determine which records to send back, but may have the advantage of cutting down both the amount of returned data and the time processing that data a lot.
FYI you don't need a shallow getter like the one you have.
Meaning that a getter that doesn't compute your state has no value might as well use the state itself.
About the practice, it really depends on how important it is to you that "records" has always the freshest data. If you don't need it to be always fetched, you can have a "initRecords" action to run on your "App.vue" on created hook that you can use to initialize your records. If you need always fresh data, what you have is good enough.

In Redux, how to get state in action? [duplicate]

This question already has answers here:
Accessing Redux state in an action creator?
(8 answers)
Closed 3 years ago.
In below example, after I get data from firebase, I want to add user object which is already present in redux store and append it in all the message objects. Request you to help.
Questions:
Is it a good approach to access state in action creator? If yes, how?
If not, what are the alternatives?
db.collection(`messages/${documentId}/chat`)
.get()
.then(snapshot => {
const messages = [];
snapshot.docs.forEach(doc => {
console.log("message", doc.id, doc.data());
messages.push({
id: doc.id,
from: doc.data().from,
to: doc.data().to,
text: doc.data().text,
timestamp: doc.data().timestamp.seconds * 1000
});
});
dispatch({ type: GET_CHAT, payload: messages });
});
if your using Redux Thunk middleware, you could simply do that:
const myAction = () => async (dispatch, getState) => {
const { field1, field2 } = getState().myReducer;
};
Simply.
import store from "..path/to/your/createStore/export".
then use it inside action or anywhere, where we cannot use connect method of redux to subscribe to our component.
store.getState() // will return the whole store object.
other useful method to dispatch action;.
store.dispatch(action name).
Lastly, soter.getState() should be used in case of emergency.
for your use case .
mostly we work with action arguments. And pass data as parameters of actions. From where we dispatch actions ex. from some component which is subscribed to redux store. there we will access to store object via the mapStateToProps method.
export const successBuy = data => {
let request = axios
.post(`/api/users/successBuy`, data)
.then(response => response.data);
return {
type: actionTypes.SET_CURRENT_USER,
payload: request
};
};

How do I update the Apollo data store/cache from a mutation query, the update option doesn't seem to trigger

I have a higher order component in my react native application that retrieves a Profile. When I call an "add follower" mutation, I want it to update the Profile to reflect the new follower in it's followers collection. How do I trigger the update to the store manually. I could refetch the entire profile object but would prefer to just do the insertion client-side without a network refetch. Currently, when I trigger the mutation, the Profile doesn't reflect the change in the screen.
It looks like I should be using the update option but it doesn't seem to work for me with my named mutations. http://dev.apollodata.com/react/api-mutations.html#graphql-mutation-options-update
const getUserQuery = gql`
query getUserQuery($userId:ID!) {
User(id:$userId) {
id
username
headline
photo
followers {
id
username
thumbnail
}
}
}
`;
...
const followUserMutation = gql`
mutation followUser($followingUserId: ID!, $followersUserId: ID!) {
addToUserFollowing(followingUserId: $followingUserId, followersUserId: $followersUserId) {
followersUser {
id
username
thumbnail
}
}
}`;
...
#graphql(getUserQuery)
#graphql(followUserMutation, { name: 'follow' })
#graphql(unfollowUserMutation, { name: 'unfollow' })
export default class MyProfileScreen extends Component Profile
...
this.props.follow({
variables,
update: (store, { data: { followersUser } }) => {
//this update never seems to get called
console.log('this never triggers here');
const newData = store.readQuery({ getUserQuery });
newData.followers.push(followersUser);
store.writeQuery({ getUserQuery, newData });
},
});
EDIT: Just realised that you need to add the update to the graphql definition of the mutation.
EDIT 2: #MonkeyBonkey found out that you have to add the variables in the read query function
#graphql(getUserQuery)
#graphql(followUserMutation, {
name: 'follow',
options: {
update: (store, { data: { followersUser } }) => {
console.log('this never triggers here');
const newData = store.readQuery({query:getUserQuery, variables});
newData.followers.push(followersUser);
store.writeQuery({ getUserQuery, newData });
}
},
});
#graphql(unfollowUserMutation, {
name: 'unfollow'
})
export default class MyProfileScreen extends Component Profile
...
this.props.follow({
variables: { .... },
);
I suggest you update the store using the updateQueries functionality link.
See for example this post
You could use compose to add the mutation to the component. Inside the mutation you do not need to call client.mutate. You just call the mutation on the follow user click.
It might also be possible to let apollo handle the update for you. If you change the mutation response a little bit that you add the following users to the followed user and add the dataIdFromObject functionality. link