Issue in React by using Redux, actions and dispatch - javascript

I have a login form which needs to re-direct a user to a landing page if the user's email exists in the database.
I have a class called "FormToLogin" with a method called login. In the login method, I dispatch data and this.props.history to an action called loginAct.
Container:
class FormToLogin extends Component {
login = fullForm => {
const { dispatch } = this.props;
const data = {[user]: {...fullForm}}
return dispatch(login(data, this.props.history)) <-- apparently, passing history to an action is not good)
}
}
As you can see, I call the action by passing Data (which will include the email address entered by the user) and the history because I want to make a .push('/new_url') if the email exists in the database.
Action:
export const login = (data, history, dispatch) => {
return api.post(url, data)
.then(({ status, h, data }) => {
// whatever if it returns 200
}
.catch(({ response }) => {
dispatch(loginFail());
const status = (response || {}).status;
if (status === 401 && hasError(error_user, response.data)) {
history.push('/new_url') // This is INCORRECT
?? -- what do I need here -- ??
}
})
}
I have been told that it's bad practice to pass Route history to an Action.
So, history.push() shouldn't happen here.
I've been suggested to add a catch to a container level ("FormToLogin").
So, I've tried to create a catch in the Container(FormToLogin) when I call the Action with dispatch(login(data)), but it doesn't work. Also, var status doesn't exist in the container.
BEFORE: return dispatch(login(data, this.props.history))
AFTER: .catch(e => {
if (status === 401 && hasError(
error_user,
e.response.data
)) {
history.push('/new_url);
} throw e; })
What do I need to add or change?

Two ways to solve this issue.
1) Accessing history object inside Action creator without explicitly passing.
// create history object
history.js
import createHistory from 'history/createHashHistory'
export default createHistory()
action.js
import history from './history'
history.push('/new_url') // use it wherever you want.
2) If you don't want it inside action then handle that inside formLogin.
When dispatching dispatch(loginFail());, inside loginFail function set state of email_address. You could get that state using connect function inside FormToLogin due to react-redux library using props.
Inside render function you could write.
if (this.props.isEmailAddress) { history.push('/new_url') }

Related

Vue router being loaded before Vuex and the app

When the vue app is being created, it will dispatch an action from the store that hits the backend endpoint to fetch the user data and update the user state.
I wanted to use navigation guards (either beforeEnter inside the relevant route or beforeRouteEnter inside the component) so it will check if the user data has the right permissions to route to the component. However, when I try to access either store.state or store.getters, it returns the empty data because the router is loaded before the action in the store is completed yet.
I tried different solutions I found online but not much luck yet..The following is a snippet of the code.
export default {
name: 'App',
created() {
this.setupAxios()
this.fetchInitial()
},
methods: {
setupAxios(){
axios.defaults.headers.common['X-CSRFToken'] = window.csrfToken
},
fetchInitial(){
this.$store.dispatch('FETCH_USER_PROFILE')
},
}
}
const actions = {
['FETCH_USER_PROFILE']: ({commit, dispatch}) => {
return axios.get(reverseJS['api2:users-list']())
.then(resp => {
commit('FETCH_USER_PROFILE', resp.data)
return resp.data
})
.catch(error => {
commit('USERS_ERROR', error)
throw error
})
},
}

Vue: Using Vuex store in beforeRouteEnter hook to cancel navigation

I am trying to get vue-router to check a list of permissions, before finishing the navigation to a new route. The permissions are stored in vuex and ideally I'd want to avoid passing them as props everywhere.
When I use the next(vm => {}) callback, regardless of the outcome, it routes to next page, even though it should receive false and cancel the navigation.
beforeRouteEnter(to, undefined, next) {
next(vm => {
const allowedRoles = ['Administrator', 'Contributor'];
if (vm.$store.state.authentication.userInfo.userRoles.some(value => allowedRoles.includes(value))) {
return true;
}
else {
return false;
}
});
}
What am I doing wrong here that is causing it to fail?
By the time you've accessed vm inside next, the navigation has already occurred. Test the value before calling next. You can import the store into the component to access it:
import store from '#/store/index.js'; // import the store
beforeRouteEnter(to, undefined, next) {
const allowedRoles = ['Administrator', 'Contributor'];
const roles = store.state.authentication.userInfo.userRoles;
const isAllowed = roles.some(value => allowedRoles.includes(value))
next(isAllowed); // passes `true` or `false` to `next`
},
This way you can access the store without needing the component instance.

How can I cancel a state change from a resolving promise after component is already unmounted?

The Context:
I want to know how to get rid of this error:
Warning: Can't perform a React state update on an unmounted component.
I think I know exactly why this error shows up:
I have a Signin Route. I have a litte piece of code in the useEffect that does this:
if (!auth.isEmpty && auth.isLoaded) {
history.push("/");
}
So when someone goes to mypage/signin and is already signed in, he gets redirected to Homepage. This works fine BUT:
The Problem:
When he is not signed in I have a little Signin Function:
const signin = async (e: React.MouseEvent) => {
setIsLoading(true);
e.preventDefault();
try {
const user = await firebase.login({ email, password });
setIsLoading(false);
if (user) {
history.push("/");
}
} catch (error) {
setIsLoading(false);
setError(error.message);
}
};
So when the users hits enter, he gets redirected to home when there is no error. It works fine but I get this error in the console, because I set the state and the the snippet in useEffect routes me to /Home, but the promise is not yet completed from firebase. And when it's finished it tries to set state, but component already unmounted.
What have I tried
I added a isMounted hook and changed my signin function to look like this:
const signin = async (e: React.MouseEvent) => {
e.preventDefault();
if (isMounted) {
setIsLoading(true);
try {
const user = await firebase.login({ email, password });
setIsLoading(false);
if (user) {
history.push("/");
}
} catch (error) {
setIsLoading(false);
setError(error.message);
}
}
};
But still the same error on route change.
Additional Info
Don't get confused about these 2 loading states auth.isLoaded (from react-redux-firebase) and isLoading (my own state). Basically why I did it this way is, because when someone is already logged in and then goes to /signin he sees the login form for a tiny moment, because firebase doesn't know yet if user is authenticated, so I handled it like this, so the user definetily sees a spinner and then gets redirected if already logged in.
How to solve this little problem?
You can use React hooks for this. The useEffect return method is called when compoment is unmonuted from screen. This is like compomentdidunmount in class based react.
declare global variable _isMounted to false. When useEffect is called, it changes to true and components are on screen.
If component are unmounted, then return method from useEffect is called and _isMounted is set to false;
while updating the state, you can check using _isMounted variable that is component is mounted or not.
var _isMounted = false;
const fetchuser = () => {
if(_isMounted)
{
// code
}
}
useEffect(() => {
_isMounted = true;
// your code;
return()
{
_isMounted = false;
console.log("Component Unmounted");
}
},[])
if you redirected after login, you dont have to change loading state. Just remove setIsLoading(false)
const user = await firebase.login({ email, password });
if (user) {
history.push("/");
}

Firebase, user registration and navigation issue

I'm using React Native, Firebase and react-navigator.
In the LoadingScreen Component I observe the state changes (onAuthStateChanged)
componentDidMount() {
this.timer = setInterval(
() => firebase.auth().onAuthStateChanged(user => {
user ? this.props.navigation.navigate("App") : this.props.navigation.navigate("Auth");
}),
30,
);
}
In my AuthStack in the ChooseRole Component I have a function in which I want to register a user along with the role to be performed.
if (this.state.role === 'child') {
                Firebase
                    .auth ()
                    .createUserWithEmailAndPassword (email, password)
                    .then (() => {
                        const addChildRole = fc.httpsCallable (('addChildRole'));
                        addChildRole ({email: this.state.email}). then ((result) =>
                            this.setState ({role: result}));
                    })
                    .catch (errorMessage => {
                        console.log (errorMessage)
                    });
            })
The problem is that before .then() calls in witch I add a role, the Auth state probably changes and navigates to the application. In the AppStack, the Direction Component, based on the role, I want to target the child or parent component, but by calling
firebase.auth (). CurrentUser.getIdTokenResult ()
.then ((idTokenResult) => {
if (idTokenResult.claims.parent || idTokenResult.claims.child) {
}
idTokenResult.claims.parent and idTokenResult.claims.child gives undefined.
I want to handle giving users the role of registering and logging in, then moving to the appropriate component using Direction.js.
Do you have any idea how to solve this problem?
Not necessarily the cause of your problem, but too long to put in a comment.
There is no need to repeatedly call onAuthStateChanged. You can call it just once, and it will listen to auth state changes from that moment on. Calling it in short intervals as you're doing it bound to give problems.
Do this in componentDidMount instead:
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
user ? this.props.navigation.navigate("App") : this.props.navigation.navigate("Auth");
});
}
What could well be the cause of the problem is indeed that the onAuthStateChanged is fired before your database write completes. You can easily verify this by adding some logging statements.
If it is, you have two main options to fix the problem:
Don't use an onAuthStateChanged listener in componentDidMount, but simply check the value of firebase.auth().currentUser. This does not use a listener, so does not interfere with the database write.
Remove the onAuthStateChanged just before creating the user, so that it doesn't fire. You can do this by keeping the return value from the call to onAuthStateChanged, which is an unsubscribe function. So something along the lines of:
componentDidMount() {
stopAuthStateListener = firebase.auth().onAuthStateChanged(user => {
...
And then:
if (this.state.role === 'child') {
stopAuthStateListener();
Firebase
.auth ()
.createUserWithEmailAndPassword (email, password)
...

What is the best way to fetch api in redux?

How to write best way to fetch api resource in react app while we use redux in application.
my actions file is actions.js
export const getData = (endpoint) => (dispatch, getState) => {
return fetch('http://localhost:8000/api/getdata').then(
response => response.json()).then(
json =>
dispatch({
type: actionType.SAVE_ORDER,
endpoint,
response:json
}))
}
is it best way to fetch api?
The above code is fine.But there are few points you should look to.
If you want to show a Loader to user for API call then you might need some changes.
You can use async/await the syntax is much cleaner.
Also on API success/failure you might want to show some notification to user. Alternatively, You can check in componentWillReceiveProps to show notification but the drawback will be it will check on every props changes.So I mostly avoid it.
To cover this problems you can do:
import { createAction } from 'redux-actions';
const getDataRequest = createAction('GET_DATA_REQUEST');
const getDataFailed = createAction('GET_DATA_FAILURE');
const getDataSuccess = createAction('GET_DATA_SUCCESS');
export function getData(endpoint) {
return async (dispatch) => {
dispatch(getDataRequest());
const { error, response } = await fetch('http://localhost:8000/api/getdata');
if (response) {
dispatch(getDataSuccess(response.data));
//This is required only if you want to do something at component level
return true;
} else if (error) {
dispatch(getDataFailure(error));
//This is required only if you want to do something at component level
return false;
}
};
}
In your component:
this.props.getData(endpoint)
.then((apiStatus) => {
if (!apiStatus) {
// Show some notification or toast here
}
});
Your reducer will be like:
case 'GET_DATA_REQUEST': {
return {...state, status: 'fetching'}
}
case 'GET_DATA_SUCCESS': {
return {...state, status: 'success'}
}
case 'GET_DATA_FAILURE': {
return {...state, status: 'failure'}
}
Using middleware is the most sustainable way to do API calls in React + Redux applications. If you are using Observables, aka, Rxjs then look no further than redux-observable.
Otherwise, you can use redux-thunk or redux-saga.
If you are doing a quick prototype, then making a simple API call from the component using fetch is good enough. For each API call you will need three actions like:
LOAD_USER - action used set loading state before API call.
LOAD_USER_SUCC - when API call succeeds. Dispatch on from then block.
LOAD_USER_FAIL - when API call fails and you might want to set the value in redux store. Dispatch from catch block.
Example:
function mounted() {
store.dispatch(loadUsers());
getUsers()
.then((users) => store.dispatch(loadUsersSucc(users)))
.catch((err) => store.dispatch(loadUsersFail(err));
}

Categories

Resources