Displaying notification after successful async redux action - reactjs

I'm trying to figure out the best way to display a sweetalert message after a successful async action. So I have an ExcursionDetail component that allows you to book the excursion. Here is the simplified component:
class ExcursionDetails extends Component {
bookExcursion() {
const userId = jwt_decode(localStorage.getItem('token')).sub;
this
.props
.actions
.bookExcursion(userId, this.props.match.params.id);
}
render() {
....
<RaisedButton label="Book Excursion" onClick={e => this.bookExcursion()}/>
....
)
}
}
const mapStateToProps = (state, ownProps) => {
return {excursion: state.excursion}
}
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(ExcursionActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ExcursionDetails);
The action creator:
export const bookExcursion = (userId, excursionId) => {
return (dispatch, state) => {
dispatch(requestBookExcursions())
return ExcursionApi
.bookExcursion(userId, excursionId)
.then(resp => {
if (resp.ok) {
return resp
.json()
.then(payload => {
dispatch(bookExcursionsSuccess(payload.data));
})
}
}).catch(err => {
dispatch(bookExcursionsFailed(err));
})
}
}
What would be the best practice to then display the sweet alert notification? The options I thought of were:
Add a bookSuccess property that I can view if true or false in my ExcursionDetails component and if true call the sweetalert function.
Create notification specific actions and reducers and listen for it in my components. Only issue with this is I would need to have some sort of setTimeout after every notification call to clear the notification reducer and this seems a bit hacky.
call the sweet alert function within my reducer
pass a callback to the action creator
redux-thunk returns a promise; however even if the http call fails it will return a successful promise so this option doesn't seem viable.

I would and is using the first option that you mentioned.
I have created a new component and pass the redux store using connect. I check for it if the value is true on componentWillReceiveProps and set the state according and then you can display your sweetalert.

Well you can call it in the action creator.
You can use something like toastr.
Simple and clean.
export const bookExcursion = (userId, excursionId) => {
return (dispatch, state) => {
dispatch(requestBookExcursions())
return ExcursionApi
.bookExcursion(userId, excursionId)
.then(resp => {
if (resp.ok) {
return resp
.json()
.then(payload => {
dispatch(bookExcursionsSuccess(payload.data));
//Here is your notification.
toastr.success('Have fun storming the castle!', 'Miracle Max Says')
})
}
}).catch(err => {
dispatch(bookExcursionsFailed(err));
})
}
}

Related

Redux Thunk Submit to Action

I been stuck to this quite a bit, I am trying to pass in my state to the redux but it seems like I am doing it wrong.
This are my code:
This is my submit function
popForm() {
let states = this.state.orders;
let d = states.filter((data) => {
return data !== null && data !== undefined
});
// console.log("d",d);
this.props.LogInClick(d);
// LogInClick(state);
}
This is my mapToDispatch
const mapDispatchToProps = (dispatch) => {
return {
LogInClick : (data) => dispatch(Actions.addDynamic(data)),
}
}
Action call
export const addDynamic = ({data}) => {
console.log("Manage to get to here");
console.log("dataInAction",data);
}
My reducer
case Actions.ADD_DYNAMIC: {
return {
...state,
data: action.payload
};
}
Your synchronous action should to return an object with type and payload.
When dealing with async actions, you need thunk(or saga etc) middleware. Your code seem to dispatch normal action (not async). So just make sure that your action returns type and payload.
Like this
export const addDynamic = ({data}) => {
console.log("Manage to get to here");
console.log("dataInAction",data);
return {
type: Action.ADD_DYNAMIC,
payload: data
}
}

Promise is not getting returned in the redux thunk

I am new to react-redux. Here what I am doing is that,
I have an action that is like
export const updateActivePage = (activePage) => {
return (dispatch) => {
return dispatch ({
type: UPDATE_ACTIVEPAGE,
payload: activePage
});
}
}
Now, in my container, I want to call an action which will get called after this so,
handlePageChange = (pageNumber) => {
this.props.updateActivePage(pageNumber).then(() => {
this.props.fetchUserJd(this.props.activePage);
})
}
case UPDATE_ACTIVEPAGE: {
console.log("action payload", action.payload);
return {
...state,
activePage: action.payload
}
}
So, Here I am trying to use then but I am getting an error that Uncaught TypeError: _this.props.updateActivePage(...).then is not a function
So what is it that I am doing wrong?
You are fundamentally misusing redux-thunk. The intended use for this library is to dispatch an action creator (in this case, updateActivePage), which returns a function that gets invoked with state params. You should be dispatching updateActivePage since this is your thunk. Also, this is the function which should contain your computation, which should then posit the results into state. It appears that you are calling this function directly, and for some reason expect it to return a promise, and then somehow doing some computation. You should re-visit the thunk docs.
export const updateActivePage = (activePage) => {
return (dispatch) => {
return dispatch ({
type: UPDATE_ACTIVEPAGE,
payload: activePage
});
}
}
dispatch is not a promise, It just a function.
export const updateActivePage = (activePage) => {
return (dispatch) => {
return new Promise((res, rej) => {
dispatch ({
type: UPDATE_ACTIVEPAGE,
payload: activePage
});
res(true);
})
}
}
Can you try returning promise like I mentioned above?

Saving realtime listener in redux

I need to trigger firestore realtime listener on login to listen to user profile data changes and cancel it before logout. To do that I need to save realtime listener in the store where I get stuck. I'm trying to do this in redux
export const cancelListener = (cancelListener) => {
return {
type: actionTypes.CANCEL_LISTENER,
cancelListener: cancelListener
}
}
export const uDataListener = (uid) => {
return dispatch => {
dispatch(uDataStart())
const dbRef = db.collection("user").doc(uid)
const cancelSubscription = dbRef
.onSnapshot(
(doc) => {
dispatch(uDataSuccess(doc.data()))
}
, ((error) => {
dispatch(uDataFail(error.message))})
);
dispatch(cancelListener(cancelSubscription))
}
}
and on logout simply call it from the redux store
export const logout = (cancelListener) => {
cancelListener()
fire.auth().signOut()
return {
type: actionTypes.AUTH_LOGOUT
}
}
However nothing is being saved in cancelListener therefore it can not be triggered. How do I accomplish this task? Please
Thanks
I have woken up in the middle of the night with other idea. I tried to add the method in the constant in action instead of saving the method in the redux state or reducer. I'm not sure if this is the best approach but it does the job. Now I just don't understand why I didn't try this approach in the first place. Here is the code which will need a bit of tweaks yet but it works
let cancelListener = null
export const logout = () => {
cancelListener()
fire.auth().signOut()
return {
type: actionTypes.AUTH_LOGOUT
}
}
export const auth = (email, password) => {
return dispatch => {
dispatch(authStart())
fire.auth().signInWithEmailAndPassword(email, password).then((u) => {
dispatch(authSuccess(u.user))
const dbRef = db.collection("user").doc(u.user.uid)
cancelListener = dbRef.onSnapshot((doc) => {
dispatch(saveUserData(doc.data()))
})
}).catch((error) => {
dispatch(authFailed(error.message))
});
}
}
Thank you very much for your help anyway. I really appreciate that
Just a quick thought, in uDataListener call an action e.g. START_LISTENER and in reducer you can have:
import { store } from './yourStore';
let cancelListener, dbRef;
function reducer(state, action) {
switch (action.type) {
case "START_LISTENER":
dbRef = db.collection("user").doc(action.uid)
cancelSubscription = dbRef.onSnapshot(function(doc) {
store.dispatch(
yourAction(doc.data()); //Dispatch new action using store
)
})
return state;
case "STOP_LISTENER":
cancelListener()
return state;
default:
return state;
}
STOP_LISTENER will be dispached when you are doing logout
Below you can see link how to dispatch from outside a component
Update React component by dispatching action from non-react component

React, Redux - Fetch data after updating via api call

I am using react with redux and typescript, Trying to add item from react via api call which returns back whether it is success or failed.
So I fetch data from componentDidMount
componentDidMount() {
this.props.dispatch(loadData());
}
And from the actions layer I add item like below
static addItem = (Item: IAddItemRequest): Promise<number> => {
return Promise.resolve(
AI.addItem(Item).then((ItemData) => {
return ItemData;
}).catch(error => {
return error;
}));
}
So how can I loadData after Adding an Item
Also AddItem doesn't change in the state
A possible soln can be opted using two actions, 1 that add item and second that load data.
export const loadData = (ItemData) => {
return {
type: LOAD_DATA,
payload: ItemData
};
};
export const addItemAction = (Item) => {
return dispatch => {
addItem(Item).then(ItemData => {
dispatch(loadData(ItemData));
});
};
};
Above both are inter linked redux actions and we have to call first action in react component's componentDidMount method as follow
componentDidMount() {
this.props.dispatch(addItemAction(Item));
}

Can't do async in redux action

i already use redux thunk to do async. it already work with this format :
export function registerUser({email,password}){
return function(dispatch){
axios.post(`${ROOT_URL}/api/auth/signup`,{email,password})
.then(response =>{
dispatch({type:AUTH_USER});
localStorage.setItem('laravel_user_token',response.data.token);
browserHistory.push('/register');
})
.catch(response => dispatch(authError(response.data.error)));
}
}
now i want to try do some async in logout action like this :
export function logoutUser() {
console.log("logout");
localStorage.removeItem('laravel_user_token');
return { type: LOGOUT_USER }
}
that's work, Now i intend to redirect the page after logout was performed with this code:
export function logoutUser() {
return dispatch => {
console.log("logout");
localStorage.removeItem('laravel_user_token');
return dispatch({ type: LOGOUT_USER })
.then(() =>
browserHistory.push("/")
);
}
}
My problem is no responses comeback, even my console.log("logout") is not work.
There are some variants how you can perform it.
1. Via promises
This, way as you tried, is performing using Promise, so your main error - that your function is not returning Promise.
Change your action
export function logOut() {
return (dispatch) => {
return new Promise((resolve) => {
dispatch({
type : LOGOUT_USER,
});
resolve();
}
}
Your action handler can be simple reducer function, that will change the state.
case 'LOGOUT_USER' : (state) => {
return Object.assign({}, state, {
autenticated : false
})
}
And then, call it from the react component.
hireLogoutClick = () => {
this.props.logOut().then(() => browserHistory.push('/');
}
logOut function return Promise, which will call your callback function on resolve() step. Firstly will be called your reducer function and then will be called your callback (function passed in .then)
2. Via middleware
There is another variant, how to perform this, via middleware, and call it from reducer, like an action, middleware will catch it and redirect, for my opinion better, then use promises.
Create a middleware
import { browserHistory } from 'react-router'
import { ROUTING } from '../constants/ActionTypes'
export const redirect = store => next => action => {
if(action){
if (action.type === ROUTING) {
browserHistory['push'](action.nextUrl)
}
}
return next(action)
}
Bind it
// Apply the middleware to the store
const middleware = routerMiddleware(browserHistory)
const store = createStore(
reducers,
applyMiddleware(middleware)
)
Use :)
export function logOut () {
return (dispatch) => {
dispatch({
type : types.LOGOUT_USER
})
dispatch({
type : types.ROUTING,
nextUrl: '/'
})
}
}

Resources