I'm not sure what I'm missing but I have a simple login page that verifies a user.
This is my LoginPage.js function that handles the login.
import { connect } from "react-redux";
import { loginUser } from "../actions/auth";
...
<< Class declarations >>
...
handleLogin = e => {
e.preventDefault();
const creds = {
username: e.target.username.value,
password: e.target.password.value
};
console.log("Login data sent ", creds);
loginUser(creds);
};
...
<< Login Component rendered and form to handleLogin >>
...
export default connect(
undefined,
{ loginUser }
)(Login);
Which is sent to /actions/auth.js
export const loginUser = creds => {
console.log("login user creds ", creds);
return dispatch => {
console.log("inside dispatch ", creds);
try {
let response = API.post("api/login", {
username: "eve.holt#reqres.in",
password: "cityslicka"
});
console.log("Returned Data ", response);
dispatch(receiveLogin(creds));
return response;
} catch (e) {
console.log("Axios request failed ", e);
return false;
}
};
};
I have put console logs in to see where it goes but I only get:
Login data sent {username: "test", password: "test"}
login user creds {username: "test", password: "test"}
It doesn't seem to go any further than that so I don't see anything inside the dispatch.
Edit: I added that I am actually already using connect in the LoginPage.js
There are couple of updates we need to make in order for your login feature to work.
You're probably already somewhat familiar with the React-Redux flow.
A user interacts with your component, they trigger an event
(submitting a form/login).
We handle the event by calling our dispatcher function (the function we imported and plugged in connect()), taking the
user inputs to formulate a request to the back-end API. (redux-thunk action-creator)
We wait for the back-end API to verify the user credentials, if
successful they will give us back a token or user payload. (Promise-handler)
Use the returned object to dispatch an action, some info for our reducer to
handle. (Actual dispatch of action)
So let's try to create something to resemble that flow. To start, it looks like you've partially set-up an event-handler. The problem is that the loginUser() action-creator does not implicitly have access to the dispatch method. You need to call it from your props:
handleLogin = e => {
e.preventDefault();
const creds = {
username: e.target.username.value,
password: e.target.password.value
};
console.log("Login data sent ", creds);
this.props.loginUser(creds);
};
this.props.loginUser has dispatch binded to it thanks to the connect(), where as directly calling it from the import will not yield any redux functionality. This should be enough to complete 1 and 2.
Next we need to resolve number 3. At this point you should be able to execute the logic inside the dispatch function. However, the logic is not synchronous (ie, the flow of execution is not controlled), it does not wait for your API to give us something back before continuing.
You can introduce async/await for Promise-handling. In a nut-shell, it means to wait for something to complete before moving forward. Note that this will only work for promises. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
export const loginUser = creds => {
console.log("login user creds ", creds);
return async (dispatch) => {
console.log("inside dispatch ", creds);
try {
let response = await API.post("api/login", {
username: "eve.holt#reqres.in",
password: "cityslicka"
});
console.log("Returned Data ", response);
dispatch(receiveLogin(creds));
return response;
} catch (e) {
console.log("Axios request failed ", e);
return false;
}
};
};
For number 4, I will have to let you decide how to pass that data to your reducer :)
Your logic has a problem, I guess you want post request, if you get the correct response then dispatch. There is a function returned in your function. It doesn't make any sense to do this. Just execute it.
and use the dispatch function as a parameter is ok.
Related
Hello I'am completly new with React/Redux so there is a possibility that I violated some principles with the below code , so bare with me.
I'm building a React App which will consume my Express API. Everything is working perfectly but when I was building the Action Creators I couldnt think of a good way to handle any errors coming from the API without wrapping every single axios request with try/catch blocks.
Both in PHP world where I come from and Express you can create a global Error handler.
For any async requests in my Express APP I wrap them with the below function so I can catch them the same way as the synchronous.
module.exports = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch((err) => next(err));
};
};
From what I've learned through googling is that, there is an ErrorBoundary HOC for handling errors inside Components and for axios calls I should use axios interceptors. So I created this:
AxiosFactory Class
import axios from "axios";
import { setError } from "../actions/utilActions";
import store from "../store";
class AxiosFactory {
constructor(baseURL) {
this.instance = axios.create({
baseURL,
});
this.instance.interceptors.response.use(
function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
// Getting the errors from Express API
const {
response: {
data: { errors },
},
} = error;
store.dispatch(setError(errors));
return Promise.reject(error);
}
);
}
getInstance() {
return this.instance;
}
}
export default AxiosFactory;
User API Caller
import AxiosFactory from './AxiosFactory';
const axios = new AxiosFactory('/api/v1/users/').getInstance();
export default axios;
User ActionCreator
import { SUCCESS_LOGIN } from "./types/userTypes";
import userApi from "../apis/user";
// Tries to login the user
export const signInUser = () => {
return async (dispatch) => {
// Test
const {data:{data:{user} = await userApi.post("login", {
email: "test#test.com",
password: "test12345!",
});
dispatch({
type: SUCCESS_LOGIN,
payload: user,
});
}
Error ActionCreator
import { HAS_ERROR } from "./types/utilTypes";
export const setError = (errors) => {
return async (dispatch) => {
dispatch({
type: HAS_ERROR,
payload: errors,
});
};
};
The interceptor dispatches succesfuly the setError and the error state is getting updated like a charm, which means I dont need to manual dispatch on each call. Although I still need to catch the Promise rejection from Interceptor.
My 2 questions are:
Is there a way to lets say "stop the dispatch from executing" inside my User ActionCreator without try/catching the Promise ?
Does this whole thing I setup makes sense ? Or there is a better way to do it?
I have a Google Cloud Function which I am calling from my RN app but it is returning
[Error: Internal]
I have set the permission to Unauthenticated users so anyone can call it - for testing purposes only. When I set to Authenticated users permission, it throws another error [Error: Unauthenticated] eventhough I am authenticated and I can get the currentUser id in my app.
Tried searching for this error but it didnt send me to any possible solutions so decided to post here and hopefully recieve responses that will help me fix it.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.createUser = functions.region('europe-west1').https.onCall(async (data, context) => {
try {
//Checking that the user calling the Cloud Function is authenticated
if (!context.auth) {
throw new UnauthenticatedError('The user is not authenticated. Only authenticated Admin users can create new users.');
}
const newUser = {
email: data.email,
emailVerified: false,
password: data.password,
disabled: false
}
const role = data.role;
const userRecord = await admin
.auth()
.createUser(newUser);
const userId = userRecord.uid;
const claims = {};
claims[role] = true;
await admin.auth().setCustomUserClaims(userId, claims);
return { result: 'The new user has been successfully created.' };
} catch (error) {
if (error.type === 'UnauthenticatedError') {
throw new functions.https.HttpsError('unauthenticated', error.message);
} else if (error.type === 'NotAnAdminError' || error.type === 'InvalidRoleError') {
throw new functions.https.HttpsError('failed-precondition', error.message);
} else {
throw new functions.https.HttpsError('internal', error.message);
}
}
});
in my RN app I am calling it like this:
var user = {
role: role
}
const defaultApp = firebase.app();
const functionsForRegion = defaultApp.functions('europe-west1');
const createUser = await functionsForRegion.httpsCallable('createUser');
createUser(user)
.then((resp) => {
//Display success
});
console.log(resp.data.result);
})
.catch((error) => {
console.log("Error on register patient: ", error)
});
I think the way I am calling it in my RN app is correct because I have tested it with a testFunction and I returned a simple string. So, I believe the problem is somewhere in the function itself.
EDIT: I just tested by simply calling the function and returning the context and it always returns Internal error:
exports.registerNewPatient = functions.region('europe-west3').https.onCall((data, context) => {
return context; //this is returned as INTERNAL error.
}
I just cant get to understand whats going on here, why does it return Internal error when I am authenticated as a user and it should return the authenticated user data, isn't that right?
Try some console.log(context) ; console.log(data) statements in your registerNewPatient function and take a look at the logs. What do they say?
Some other things to consider might include that in your client code you use europe-west1 while your function code has europe-west3. Try to have those line up and see if it works? From my experience, if a specified function isn't found to exist, the client receives an INTERNAL error.
I'm working on an app using Next.js with redux by following this example and here is some part of store.js
// REDUCERS
const authReducer = (state = null, action) => {
switch (action.type){
case actionTypes.FETCH_USER:
return action.payload || false;
default:
return state;
}
}
export const rootReducer = combineReducers({
user: authReducer,
form: reduxForm,
});
// ACTIONS
export const fetchUser = () => {
return (dispatch) => {
axios.get('/api/current_user')
.then(res => dispatch({
type: actionTypes.FETCH_USER,
payload: res.data
}));
};
};
export const submitLogin = (values) => async dispacth => {
const res = await axios.post('/api/login', values);
// Router.push('/');
// console.log(res)
dispacth({ type: actionTypes.SUBMIT_LOGIN, payload: res.data });
};
and the client side such as header
function mapStateToProps (state) {
const { user } = state
return { user }
}
export default connect(mapStateToProps)(Header)
and when I console.log('############=>', this.props.user); the props & I'm not loggesd in then it's showing null but showing some extra data such as below screenshot
but when I logged in & console.log('############=>', this.props.user); it's showing proper data without extra data such as below image
what I'm doing wrong? please help me. Thanks
The problem is probably not on your React / Redux code but on your Next.js routes.
You’re trying to call an API with axios.get('/api/current_user') but Next.js is giving you an HTML response, that you indeed store in authReducer extracting it as action.payload.
You probably want to see this section about Custom Server and Routing.
dispacth({ type: actionTypes.SUBMIT_LOGIN, payload: res.data });
Should be:
dispatch({ type: actionTypes.SUBMIT_LOGIN, payload: res.data });
#MunimMunna is spot on. Your server is either redirecting you to an HTML login page, or returning an HTML error page for failed creds. In either case, Axios is seeing a 200 status code, so it thinks the response is valid. Your action creator blindly fires off the action with the HTML payload attached.
Consider making these changes:
Client:
Add a catch block to your axios promise that logs failed response.
Pass an Accept header of application/json to tell the server you don't want HTML responses. If you are lucky, this might be enough to get NextJS to behave the way you want.
Server: If needed, change the server to detect whether the request is an XHR request, or if application/json is the only response type the client wants. Don't redirect if those conditions are true. Return return a 401 status code instead. You can optionally return a JSON body with some extra error information.
Login.vue
methods: {
loginUser() {
this.$store.dispatch("auth/loginUser", { email: this.email, password: this.password })
.then(() => {
// Login ok, redirect user to dashboard
})
.catch(() => {
// Show error
})
}
}
Auth.js
actions: {
async loginUser ({dispatch}, {email, password}) {
return axios.post(url, {email, password})
.then(response => {
dispatch('otherAction');
})
.catch(response => {
})
},
async otherAction ({ dispatch, commit }) {
// Do other stuff
}
}
I use Vuex and actions to perform API calls. I need to wait otherAction dispatch before redirecting user to dashboard.
The problem is I can not use await in the then call. axios.post in the auth.js basically checks credentials of the user. After that I need to do a few more call to get all necessary data to show. Currently, user is redirected to dashboard, but I see errors on the dashboard because it doesn't wait until the 'get extra data of a user' call is completed to get the needed data. The thing is timing issue.
My way of thinking may be completely wrong. How can I make it work?
Make another action that dispatches all the other actions and stores the returned promises in an array then use Promise.all()
Motivation
I store user credentials in redux store. They are filled when user logs in. I would like to have reusable method to fetch data with user's username and password.
State / auth
const initState = {
isLoading: false,
user: undefined,
auth_err: false
};
My attempt
const fetchData = props => async (url, method, body) => {
try {
const response = await fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + Base64.btoa(props.user.username + ":" + props.user.password)
},
body: body
});
console.log(response);
return response;
} catch (err) {
console.log(err);
}
};
const mapStateToProps = state => {
return {
user: state.auth.user
}
};
export const SENDREQUEST = connect(mapStateToProps)(fetchData);
Call
const response = await SENDREQUEST("http://localhost:8080/users", "GET");
But once I call it I get:
TypeError: Cannot call a class as a function
Is there any way at all to create such one?
Any help would be appreciated ♥
I am assuming that you know about redux and its middleware.
First of all the error comes from passing fetchData to the return value of connect : connect returns a function which is a HOC : takes a component, returns a component which is a class here that cannot be called as a function as you do.
A solution for your problem is to use mapDispatchToProps and a middleware, roughly as follow :
class LoginFormPresenter {
render () {
// render your login
return <form onSubmit={this.props.logUser}></form>
}
}
// This makes the LoginFormPresenter always receive a props `logUser`
const LoginFormConnector = connect((state => { user: state.user }), {
logUser: (e) => (
// create a credentials object by reading the form
const credentials = ...;
// return a valid redux action
return {
type: "LOGIN",
credentials
};
)
});
const LoginForm = LoginFormConnector(LoginFormPresenter);
// Create an ad hoc middleware
//
const middleware = ({ dispatch, getState }) => next => action => {
if (action.type === "LOGIN") {
// log your user
fetch()
.then(user => next({ type: "LOGIN", user }));
return next({ type: "PROCESSING_LOGIN" }); // indicate to redux that you are processing the request
}
// let all others actions pass through
return next(action);
};
So the mechanism works like this:
The LoginFormConnector will inject a props logUser into any component it is applied to. This props is a function wich dispatches an action with the credentials of your user. It will also inject a user props taken from the redux state for you to show your user.
Inside a redux middleware you catch the action and use your generic fetchData to process the request. When the request is resolved you dispatch the action to the reducer and update it. No data fetching occurs in the component itself, everything is handled at the middleware level.