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

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

Related

How to dispatch two actions in one epic, which could be in the same or in another reducer

I have two ducks (ui and backend) with epics in them.
I need to trigger two actions after finishing backend operations
One of these actions reside in the backend duck, the other in the ui duck.
I started with the background action and things worked as expected.
Adding the second action leads me to issues, as I can reach the action (console logs correctly), but not the reducer (no log)
The challenge I'm trying to solve is:
Kicking off two actions in one epic
dispatching an action in another reducer
My code looks similar to this:
the backendDuck's epic:
fetchFooEpic : (action$, store) =>
action$.pipe(
operators.filter(action => action.type === types.LOAD),
// start added section for second call
operators.switchMap(action => {
const response = operators.from(fetchSomeUrl(action))
.pipe(
operators.of(uiDuck.actions.fetchUserFulfilled(response.props)),
),
operators.catchError(err => {
console.error('Error happened!', err.message)
return rxjs.of({ type: types.ADD_CATEGORY_ERROR, payload: err })
})
return response
}),
// start added section for second call
// original first call
operators.map(a => ({ type: types.ENDACTION, payload: a.payload })),
operators.catchError(err => {
console.error('Error happened!', err.message)
return rxjs.of({ type: types.ADD_CATEGORY_ERROR, payload: err })
})
)
the uiDuck:
export actions={
...
fetchUserFulfilled: (value) => {
console.log('hello from action')
return ({ type: types.FETCHUSERFULFILLED, payload: value })
},
...
}
...
export default function reducer(state = initialState, action) {
switch (action.type) {
case types.FETCHUSERFULFILLED:
console.log('hello from reducer')
return {
...state,
user: action.payload,
}
...
Turns out I was combining the two calls in the wrong way.
For being able to pipe along, the piped observable needs to return an observable again.
When mapping to another redux-action, it seems to me that it doesn't return an observable (?) thus, the call needs to happen for all desired redux-actions at the same location (eg with concat)
For the sake of completeness I strive to explain all parts of the code in comments
import * as operators from 'rxjs'
fetchFooEpic : (action$, store) =>
action$.pipe(
operators.filter(action => action.type === types.LOAD), // Filter
operators.switchMap(action => // restart inner actions on each call
operators.from(fetchSomeUrl(action)) // creating observable from result
.pipe( // starting new flow on observable (self)
//operators.tap(a => console.log('Now running fetchfooepic 2', a)), // dedicated location for sideeffects
operators.switchMap( // restart inner actions on each call
(response) => operators.concat( // Kicking off several actions sequentially (merge() would do that in parallel)
operators.of(uiDuck.actions.fetchUserFulfilled(response)), // addressing the redux action in other reducer
operators.of(({ // addressing the redux action via the type in this duck (ENDACTION is controlled by epics only, no action exists for it)
type: types.ENDACTION,
payload: response
}})),
)),
operators.catchError(err =>{
console.error('Shit happens!', err.message) // errorhandling
return rxjs.of({ type: types.ADD_CATEGORY_ERROR, payload: err })
})
)
)
),
Generally the functions are documented with some (more or less understandable) examples in
https://rxjs.dev/api/index/function/

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.

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 to stop multiple Firebase events callback on(Value,snapshot)

I am new to firebase. I want to get realtime data when a user click on any pushKey. When i click on another node i get previous node data also.I dont know where to use ref.off('value',listener). please help me out. Anyhelp will be appreciated
export const fetchCurrentCircleLocation = pushKey => {
return async dispatch => {
let ref = firebase.database().ref(Circles/${pushKey}/members);
let listener = await ref.on("value", snapshot => {
// console.log("circleLocation", snapshot.val());
dispatch({
type: FETCH_CURRENT_GROUP_CIRCLE_LOCATION,
payload: snapshot.val()
});
});
};
};
Try using once. It will go through your database items only once. You don't need to remove listener.
ref.once("value", function (snapshot) {
snapshot.forEach((child) => {
console.log(child.val())
});
});

redux-observable, How to do an operator like promise.all()?

I have two async requests, want to write a epic do the job like promise.all()
const fetchData1 = () => (action$: ActionsObservable<any>, store: any) => (
ajax.getJSON('../../mockData/promiseAll/data1.json').map((data: any) => {
return requestData1Success(data);
})
);
const fetchData2 = () => (action$: ActionsObservable<any>, store: any) => (
ajax.getJSON('../../mockData/promiseAll/data2.json').map((data: any) => {
return requestData2Success(data);
})
)
const requestAllDataEpic = (action$: ActionsObservable<any>, store: any) => {
return action$.ofType(t.REQUEST_ALL_DATA)
.map((action) => action.payload)
.switchMap((names: string[]) => {
console.log(names);
return Observable.forkJoin([
fetchData1()(action$, store),
fetchData2()(action$, store)
])
.map((results: any[]) => {
const [action1, action2] = results;
requestData1Success(action1);
requestData2Success(action2);
});
});
};
But when I dispatch the action, the console give me an error:
Uncaught TypeError: Cannot read property 'type' of undefined
I think the reason is I do not give the middleware an action object, but undefined.
How can I do this correctly?
In the provided example, you are not actually returning your two actions, you're returning nothing:
.map((results: any[]) => {
const [action1, action2] = results;
// v----- not returning either of these
requestData1Success(action1);
requestData2Success(action2);
});
map can't used to emit two actions sequentially because it's 1:1 not 1:many (mergeMap, switchMap, concatMap, etc are 1:many). However, in your example you are already converting the responses to the actions inside your fetchData helpers--doing it again would wrap an action inside another action, not what you want. This looks like a bug when you were refactoring.
Other than that, it's actually not clear what you intended to do. If you have further questions you'll need to describe what you want you'd like to achieve.