Saving realtime listener in redux - reactjs

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

Related

When the Next js app refreshing, useEffect not dispatching redux saga action and updating the state

My question is, when the next js app refreshing/reloading, redux store state not updating. I have the below code inside the component
const Landing = () => {
const freeADS = useSelector((state) => state.ads.freeAds); //this states are working fine without page refresh
useEffect(() => {
dispatch(fetchFreeAds());
}, [])
return(
{freeADS.map((data, i) => {
//some codings.........
})}
)
}
export default Landing;
redux action call
export const fetchFreeAds = () => {
return {
type: ActionTypes.FETCH_FREE_ADS
}
}
after the rootsaga / watch saga get the request, I call the handler like below
export function* handleFreeAds() {
const { response, error } = yield call(fetchFreeAds);
if (response)
{
yield put({type:"SET_FREE_ADS", payload: response.data[0]});
}
else{
}
}
actual api call goes here
export function fetchFreeAds() {
return axios.get('http://xxxxxxxxxx')
.then(response => ({ response }))
.catch(error => ({ error }))
}
I'm getting this error at the moment. pls give some support. thanks
Thanks to #slideshowp2
Problem solved by doing this miner modification. Added freeAds:[ ] backet to the initial state.
export interface State{
freeAds: null
}
export const adReducers = (state = {freeAds:[]}, {type, payload}) => {
switch(type)
case ActionTypes.SET_FREE_ADS:
return {
...state,
freeAds: payload
};
}

Cannot dispatch actions initially when app loads

I have a mern app and i'm using redux to maintain state of my posts, I want to fetch all data from my api at first run of the app (when the app component loads initially) but I can't achieve it. It only works when I post something and it fetches the post, but it doesn't fetch all the posts from db initially.
After struggling for a day I decided ask here.
This is my component tree:
In my PostsBody, I want to fetch all the posts from the database whenever the app loads initially (this is not happening) and then whenever there is a change in state like create, delete it should fetch the updated posts (this is happening).
This is my PostsBody component:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from 'react-redux';
import Post from "./Post";
import { getPostsAction } from '../actions/posts';
const PostsBody = () => {
const dispatch = useDispatch();
// fetching posts
useEffect(() => {
dispatch(getPostsAction);
}, [dispatch]);
const posts = useSelector((globalState) => globalState.postsReducer);
console.log(posts); // intially empty when the app reloads/renders.
return (
// simply posts.map to display individual posts
);
}
export default PostsBody;
Action:
export const getPostsAction = () => async (dispatch) => {
try {
const { data } = await getAllPosts();
const action = {
type: 'GET_ALL',
payload: data,
}
dispatch(action);
} catch (error) {
console.log(error.message);
}
}
GET CALL:
import axios from 'axios';
const url = "http://localhost:5000/users";
export const getAllPosts = () => axios.get(url);
Reducer:
const postsReducer = (posts=[], action) => {
switch (action.type) {
case 'GET_ALL':
return action.payload;
case 'CREATE':
return [...posts, action.payload];
default: return posts;
}
}
export default postsReducer;
I repeat, the only problem is, it is not fetching all the posts from db initially when the app renders, after that when I create a new post it does fetch that post (not all from db).
Issues
It doesn't appear as though you are invoking the getPostsAction action creator correctly. Also, with only dispatch in the useEffect's dependency array the hook callback will only be invoked once when the component mounts.
Solution
Invoke the getPostsAction action.
useEffect(() => {
dispatch(getPostsAction()); // <-- invoke
}, [dispatch]);
Now this still only solves for fetching posts from the DB when the component mounts, but not when new posts are POST'd to your backend.
I've looked at your actions and state. Normally you would include another variable in the useEffect dependency array to trigger the effect callback to execute again, but I think a simpler way is possible. Instead of POST'ing the new post and dispatching the CREATE action you should POST the new "post" and immediately GET all posts and dispatch the GET_ALL action instead with that data.
export const createPostAction = (newPostData) => async (dispatch) => {
try {
await createPost(newPostData);
getAllPosts()(dispatch);
} catch (error) {
console.log(error.message);
}
}
I've basic familiarity with Thunks, but if the above doesn't work then you may need to duplicate some behavior, or factor it out into some common utility code used by both action creators.
export const createPostAction = (newPostData) => async (dispatch) => {
try {
await createPost(newPostData);
const { data } = await getAllPosts();
const action = {
type: 'GET_ALL',
payload: data,
}
dispatch(action);
} catch (error) {
console.log(error.message);
}
}

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

Synchronous action with redux-thunk

For my react web app, I want to check for authentication token when a protected link is accessed. Here's the action for checking auth:
export const checkAuthState = () => {
return (dispatch) => {
dispatch(loadingStart());
const eAuth = localStorage.getItem('eAuth');
if (!eAuth) {
dispatch(logout());
} else {
const employeeData = JSON.parse(localStorage.getItem('employeeData'));
dispatch(authSuccess(employeeData, eAuth));
}
};
};
But, as it runs asyncronously, the eAuth state in reducer is null. So, the user is redirected to login page.
Is there a way to wait till the eAuth is set before redirecting. I tried adding loading state to true until authSuccess sets it to false, but it didn't work either.
hope it will help you thanks
export const checkAuthState = () => {
return async (dispatch) => {
dispatch(loadingStart());
const eAuth = await localStorage.getItem('eAuth');
if (!eAuth) {
dispatch(logout());
} else {
const employeeData = await JSON.parse(localStorage.getItem('employeeData'));
dispatch(authSuccess(employeeData, eAuth));
}
};
};

Displaying notification after successful async redux action

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

Resources