Passing observer into redux-sagas - reactjs

I'm trying to leverage a firebase observable from within the redux-sagas framework but I'm having trouble doing this without a hack. I'm trying to use firebase's "onAuthStateChange" function as shown here
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
essentially the observer executes whenever a user signs in or out
In my firebase utility file my method looks like this:
authChanged: () =>{
return firebaseAuth.onAuthStateChanged(callback);
}
then in my saga, for the moment, I'm simply trying to log to the console whenever the observer observes something:
export function* loginState(){
Firebaseutils.authChanged(function(user){
if(user){
console.log('User logged in!')
}else{
console.log('User logged out')
}
});
}
This fails due to 'callback' not being defined. I'm essentially trying to curry the observer to pass to sagas but it's not working. My workaround is to pass the full firebase auth object to my login/logout saga and then create the observer in there. That works but seems like a hack. Any help would be hugely appreciated.

I don't think you're actually integrating the observer into the saga in your snippet. It'll work for console.log b/c that's a sync function, but you can't yield anything from that callback, as its context is separate from that of the generator. this deprives you of lots of the sagas utility, if it works # all.
I had to get this working on my project, and the best I could do was inspired by this project and the architecture in this starter-kit.
Basically,it's a few steps.
Wrap your observer in function that takes dispatch and returns a promise.
export function initAuth(dispatch) {
return new Promise((resolve, reject) => {
myFirebaseAuthObj.onAuthStateChanged(
authUser => {
if (authUser) {
dispatch(signInFulfilled(authUser))
} else if (authUser === null) {
dispatch(signOutFulfilled())
}
resolve()
},
error => reject(error)
)
})
}
Wrap your top-level container in a function
const initialState = window.___INITIAL_STATE__
const store = createStore(initialState)
let render = () => {
const routes = require('./routes/index').default(store)
ReactDOM.render(
<AppContainer store={store} routes={routes} />,
MOUNT_NODE
)
}
wrap render() in initAuth:
initAuth(store.dispatch)
.then(() => render())
.catch(error => console.error(error))
use your sagas for anything else. for example, you can conduct route-changes from your sagas like so:
function* signIn(authProvider) {
try {
const authData = yield call([firebaseAuth, firebaseAuth.signInWithPopup], authProvider)
yield take(SIGN_IN_FULFILLED)
browserHistory.push('/dash')
}
catch (error) {
yield put(signInFailed(error))
}
}

It seems like you meant to have callback as a parameter to authChanged but authChanged currently takes no parameters. Here's what you probably meant to do:
authChanged: (callback) => {
return firebaseAuth.onAuthStateChanged(callback);
}
I'm not sure I understand what you mean by currying the observer but you could also do something like this to curry the firebase method to use as your method:
{
authChanged: ::firebaseAuth.onAuthStateChanged,
}

Related

Display loading state and change route when API call is successfull

While working on a side project, I faced an issue with react-router-dom.
What I want to implement is: When I submit a Form, I need to save the data on my server. While the request is pending, I need to display a loading indicator. Once the server says everything is ok, I need to redirect the user on a new page
action.js
export const addNotification = value => async dispatch => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
} catch(e) {
dispatch(addNotificationFailure())
}
}
component.js
class CreateNotificationForm extends Component {
onSubmit = (values) => {
this.props.addNotification(parameters, history)
}
render() {
const { isCreating } = this.props
const submitBtnText = isCreating ? 'Creating...' : 'Submit'
return (
<Form>
// content omitted
<Submit value={submitBtnText} />
</Form>
)
}
}
const mapStateToProps = (state) => ({
isCreating: getIsFetching(state)
})
const mapDispatchToProps = (dispatch) => ({ // omitted })
connect(mapStateToProps, mapDispatchToProps)(CreateNotificationForm)
So far so good: When I submit my form, the form's submit button shows a Creating... text.
However, how do I tell react-router to load a new path once the request is successful?
Right now, I've done that by using withRouter and using this.props.history as a second argument for this.props.addNotification.
It works great, but it seems really wrong
I've seen solutions using react-router-redux, but I don't really want to add a new middleware to my store.
Should I make the API call inside my component and use a Promise?
Any help?
Update:
After working a little on my own React project, and thinking about similar situations where I handle route changes there, I decided I want to change my original answer. I think the callback solution is OK, but the solution that you already mentioned of making the API call inside your component and using a promise is better. I realized that I've actually been doing this in my own app for a while now.
I use redux-form in my app, and it provides onSubmitSuccess/onSubmitFail functions that you can use to handle the submit result, and each of those rely on you returning a promise (usually from your action creator).
I think the fact that one of the most popular packages for form submission in React/Redux supports this pattern is an indication that it's probably a good pattern to use. Also, since react-router passes history into your component, it seems logical that they expect most people to do a lot of their programmatic route changes inside the component.
Here's an example of what the promise solution would look like with your code:
action.js
export const addNotification = value => dispatch => {
return new Promise(async (resolve, reject) => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
resolve(response)
} catch(e) {
dispatch(addNotificationFailure())
reject(e)
}
})
}
component.js
onSubmit = async () => {
try {
await this.props.addNotification(parameters)
this.props.history.push('/new/route')
} catch(e) {
// could use a try/catch block here to display
// an error to the user here if addNotification fails,
// or go to a different route
}
}
Old Answer:
A simple solution would be to allow addNotification() to accept a callback function as an optional second argument.
export const addNotification = (value, callback=null) => async dispatch => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
(typeof callback === 'function') && callback()
} catch(e) {
dispatch(addNotificationFailure())
}
}
Then inside your component use the router to go to the new route.
onSubmit = (values) => {
this.props.addNotification(parameters, () => {
this.props.history.push('/new/route')
})
}
You should not write your asynchronous calls in reducers or actions as the documentation clearly suggests them to be pure functions. You will have to introduce a redux-middleware like redux-thunk or redux-saga (I personally prefer sagas)
All your async calls will happen inside the middleware, and when it succeeds, you can use react-routers history .replace() or .push() methods to update your route. Let me know if it makes sense
You can use one popular package axios
See Here https://www.npmjs.com/package/axios
and you can implement your login like
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
You can write your loader login while calling api
and then you can hide your loader in .then

How can i add dependencies between actions with redux

Consider the code below
App.js:
function user_getUser() {
return dispatch => {
dispatch({ type: "GET_USER" })
}
}
function user_getCustomer() {
return dispatch => {
dispatch({ type: "GET_CUSTOMER" })
}
}
class App extends React.Component {
render() {
return (
<div>Hello world !</div>
)
}
}
function mapStateToProps(state) {
return {}
}
function mapDispatchToProps(dispatch) {
return {
getUser: () => dispatch(user_getUser()),
getCustomer: () => dispatch(user_getCustomer()),
}
}
ReactRedux.connect(mapStateToProps, mapDispatchToProps)(App)
ReactDOM.render(<App />, document.getElementById('root'));
I simplified the code but normally getUser() and getCustomer() get JSON objects from an api. I would like to execute getCustomer() when the api responsed to getUser().
Is it possible?
PS: I'm a beginner with reactJS so be indulgent
You should use Redux Thunk. It will let you run async code and then you could chain your functions. Another nice thing that you could do with it is check your store's state in the actions in case you need for example something from the store AFTER the first call has been completed.
Side note - Maybe you could refactor the server side to bring you both pieces of data together and then you could do it in 1 API call without having to wait until 1 finishes and then start the next.
2nd Side Note - If the calls are not necessarily dependent on each other, maybe use something like Promise.all?
Promise.all([user_getUser(), user_getCustomer()]
.then(([user, customer]) => {
dispatch(...)
});

redirect from component level after specific dispatch - redux thunk

I have a fairly simple use case, but having a hard to find the appropriate answer. I'm using React,Redux,React Router & redux thunk middleware.
Lets say, I have two module food-tags & food. These modules have individual create,list,edit page/component. In practical use case, food-tags have no special value. Whenever a food object is created, separated tags are inserted into the food object's tags property.
General use case is that, after any item is created successfully, react router redirects it to the list page.
whenever i'm calling the createTag action from food-tag module, I can do it in a hacky way. like just after the success dispatch, i can call
browserHistory.push('/dashboard/tags')
this leads me to a problem where i can create food-tag inline from the food create component. Codes are given below
actions.js
export function createTag(tag) {
return function (dispatch) {
axios.post(API_URL + 'api/tags', tag)
.then((response) => {
// I CAN DO REDIRECT HERE,BUT THIS CAUSES THE PROBLEM
dispatch({type: 'TAG_CREATE_RESOLVED', payload:response});
toastr.success('Tag created Successfully.......!');
})
.catch((err) => {
dispatch({type: 'TAG_CREATE_REJECTED', payload: err});
toastr.warning(err.message);
})
}
}
component/container.js
createTag () {
//validatation & others....
this.props.createTag(tag)
}
react-redux connection
function mapDispatchToProps (dispatch) {
return bindActionCreators({
createTag: createTag
}, dispatch)
}
Almost same pattern in food/create.js
$('#food-tags').select2(select2settings).on('select2:selecting', function (event) {
let isNewTagCreated = event.params.args.data.newOption,
name = event.params.args.data.text;
if (isNewTagCreated && name !== '') {
reactDOM.props.createTag({name}); // reactDOM = this context here
}
});
What I want basically that, I want to get access in the component level which action type is dispatching so that i can redirect from component & show notifications as well instead of action thunk. May be i'm not thinking in the proper way. there could be a dead simple work around.
It's good to know that redux-thunk passed out return value from the function. So you can return the promise from the action creator and wait until it will be finished in you component code
export function createTag(tag) {
return function (dispatch) {
return axios.post(API_URL + 'api/tags', tag) // return value is important here
.then((response) => dispatch({type: 'TAG_CREATE_RESOLVED', payload:response}))
.catch((err) => {
dispatch({type: 'TAG_CREATE_REJECTED', payload: err})
throw err; // you need to throw again to make it possible add more error handlers in component
})
}
}
Then in your component code
createTag () {
this.props.createTag(tag)
.then(() => {
toastr.success('Tag created Successfully.......!');
this.props.router.push() // I assume that you have wrapped into `withRouter`
})
.catch(err => {
toastr.warning(err.message);
});
}
Now you have proper split up between action logic and user interface.

Is it a good idea to use browserhostory.push in action helpers?

In my React App I need to take decision based on data I receive from the server.
If data is expected ( Dispatch actions to update state)
If data has error tag ( browserhistory.push('/notfound'); )
If expected data is unable to parsed ( browserhistory.push('/error');)
In my app structure, I am using Redux, React-Router and React-redux-Router libraries but no middleware. I have made actionHelpers to making ajax calls and then dispatch appropriate actions using Action Creator. These actionHelper methods are exposed in Components to change state.
My Questions:
What's the best way to handle these scenarios ?
Is actionHelper the best place to take these decisions ?
I don't want to use any middleware for now but please let me know if its a good idea to use middleware to handle these scenarios.
Actions are not the place where you should do redirections. This behavior should be implemented in the component itself and actions should be left to update the store.
You may want to use the Redux-thunk middleware here which allows you to dispatch a function (which receives dispatch as an argument instead of the object actions. You can then wrap that function in a promise and use it in componentWillMount.
In your actions file:
updateReduxStore(data) {
return {
type: SOME_TYPE,
payload: data.something
};
}
fetchAndValidateData() {
...
}
checkData() {
return function(dispatch) {
return new Promise((resolve, reject) => {
fetchAndValidateData().then((data) => {
try {
if (JSON.parse(data).length > 0) {
dispatch(updateReduxStore(data));
resolve('valid data');
} else if (data.error) {
reject('error in data');
}
}
catch(err) {
reject('malformed data');
}
});
});
};
}
Then in your component:
componentWillMount() {
this.props.checkData()
.then((message) => {
console.log(message); //valid data
})
.catch((err) => {
if (err === 'error in data') {
browserHistory.push('/notfound');
} else if (err === 'malformed data') {
browserHistory.push('/error');
}
});
}
Redux-thunk middleware is made for such use cases.

how to write async redux actions in a non-fake app

tl;dr: I need an example of an asynchronous redux-thunk action shows how to make an async call (e.g. fetch), and trigger a state update. I also need to see how someone might chain multiple such actions together, like: (1) see if user exists in cloud, then (2) if no, register them, then (3) use the new user record to fetch more data.
All the examples I've found makes the assumption that the redux store can be imported directly into the module that defines the actions. It's my understanding that this is a bad practice: the calling component is responsible for providing access to the store, via this.props.dispatch (which comes from the store being injected via the <Provider>).
Instead, every action in the redux world should return a function that will receive the appropriate dispatch; that function should do the work, and return... something. Obv, it matters what the something is.
Here's the pattern I've tried, based on the documentation, that has proven to be a failure. Nothing in the docs makes it clear why this doesn't work, but it doesn't -- because this action doesn't return a promise.
/**
* pushes a new user into the cloud; once complete, updates the store with the new user row
* #param {hash} user - of .firstName, .lastName
* #return {promise} resolves with user { userId, firstName, lastName, dateCreated }, or rejects with error
*/
Actions.registerUser = function(user) {
return function reduxAction(dispatch) {
return API.createUser(user) // API.createUser just does return fetch(...)
.then(function onUserRegistered(newUser) {
return dispatch({
type: 'ADD_USERS',
users: [newUser]
});
});
};
};
I have a reducer that responds to the ADD_USERS event; it merges the incoming array of one or more users with the array of users already in memory. Reducers are easy to write. That's why I switched to redux: one store, pure functions. But this thunk business is an absolute nightmare.
The error I receive is that .then is undefined on Actions.registerUser -- i.e. that Actions.registerUser doesn't return a promise.
I think the problem is obviously that I'm returning a function -- the reduxAction function -- but that doesn't seem to be negotiable. The only way to shoot data at the store is to use the dispatch method that is provided, and that means I can't return a promise.
Changing the onUserRegistered to simply invoke dispatch and then return the desired value doesn't work either, nor does having it return an actual promise.
PLZ HALP. I really don't get it. I can't believe people put up with all this.
EDIT: To provide some context, here's the kind of action composition I think I'm supposed to be able to perform, and which these thunk actions are frustrating:
Actions.bootSetup = function() {
return dispatch => {
return Actions.loadUserId() // looks for userId in local storage, or generates a new value
.then(Actions.storeUserId) // pushes userId into local storage
.then((userId) => {
return Actions.fetchUsers(userId) // fetches the user, by id, from the cloud
.then((user) => {
// if necessary, pushes the user into the cloud, too
return user || Actions.postUser({ userId: userId, firstName: 'auto-registered', lastName: 'tbd'});
});
})
.then((user) => {
console.log(`boot sequence complete with user `, user);
return dispatch({ type: 'ADD_OWNER', user });
});
};
};
I would expect that Actions.storeUserId and Actions.fetchUsers would, in addition to returning promises that resolve with values of my choosing, dispatch data to the store as a side-effect. I think the dispatch is occurring, but the chain breaks because none of these actions return promises - they return plain functions.
Not only does this seem much worse than Flux, it seems incomprehensible. I can't believe that all this madness was necessary just to consolidate app state into a single reducing store.
And yes -- I have tried the new version of flux, with its ReducerStore, but it has some inappropriate dependencies on CSS libraries that are incompatible with react-native. The project maintainers have said they don't intend to resolve the issue. I guess their state container is dependent on CSS functionality.
EDIT: my store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import Reducers from './reducers';
const createStoreWithMiddleWare = applyMiddleware(thunk)(createStore);
export const initialState = {
users: [] // will hold array of user objects
};
const store = createStoreWithMiddleWare(Reducers);
export default store;
EDIT: Here's the calling code. This is the root-level react-native component.
// index.ios.js
import Store from './store';
class myApp extends Component {
componentDidMount() {
Store.dispatch(Actions.bootSetup())
.then(() => {
console.log('*** boot complete ***');
});
}
render() {
return (
<Provider store={Store}>
<ApplicationRoutes />
</Provider>
);
}
}
My assumption is that Store.dispatch expects a function, and provides it with a reference to the store's dispatch method.
I can see one mistake right off the bat
Actions.bootSetup = function() {
return dispatch => {
return Actions.loadUserId()
You aren't chaining thunk actions correctly. If your actions returns a function, you need to pass dispatch to that action.
Take a look at this action creator(this is a fully functional real-world app, feel free to poke around), look at the 9th line, where loginUser is called.
export function changePassword(credentials) {
return (dispatch, getState) => {
dispatch(changePasswordStart(credentials))
return Firebase.changePassword(credentials)
.then(() => {
return logout()
})
.then(() => {
return loginUser(credentials.email, credentials.newPassword)(dispatch)
})
.then(() => {
dispatch(changePasswordSuccess(credentials))
toast.success('Password successfully changed')
}).catch(error => {
dispatch(changePasswordError(error.code))
toast.error('An error occured changing your password: ' + error.code)
})
}
}
Because loginUser is also a thunk action, it needs to have dispatch passed to the result of calling it. It makes sense if you think about it: the thunk doesn't do anything, it just creates a function. You need to call the function it returns to get it to do the action. Since the function it returns takes dispatch as an argument, you need to pass that in as well.
Once that's done, returning a promise from a thunk action will work. In fact, the example I gave above does exactly that. loginUser returns a promise, as does changePassword. Both are thenables.
Your code probably needs to look like this (though I am not sure, I don't have the actions being called)
Actions.bootSetup = function() {
return dispatch => {
return Actions.loadUserId()(dispatch) // pass dispatch to the thunk
.then(() => Actions.storeUserId(dispatch)) // pass dispatch to the thunk
.then((userId) => {
return Actions.fetchUsers(userId)(dispatch) // pass dispatch to the thunk
.then((user) => {
// pass dispatch to the thunk
return user || Actions.postUser({ userId: userId, firstName: 'auto-registered', lastName: 'tbd'})(dispatch);
});
})
.then((user) => {
console.log(`boot sequence complete with user `, user);
return dispatch({ type: 'ADD_OWNER', user });
});
};
};

Resources