So I'm trying to send the information from a login form to an action, then set the app's state's userLogged property to true if the login procedure is successful.
renderForm() {
return (
<Form
onSubmit={(values) => {
// call the login function.
// if called with this.props.login, I get the error
// 'cannot read `type` of undefined'
login(values.email, values.password);
}}
validate={({ email, password }) => {
// validation rules
}}>
{({submitForm}) => {
return (
<form onSubmit={submitForm}>
// form fields
</form>
);
}}
</Form>
);
}
Now, after the declaration of the component I have the following:
function mapDispatchToProps(dispatch) {
return bindActionCreators({ login }, dispatch);
}
function mapStateToProps(state) {
return { userLogged: state.userLogged.userLogged };
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginComponent);
The action receives the information, calls the API, but does not get to the reducer, and the app gives the 'dispatch not defined' error, even though the store, middleware and bindActionCreators have already been imported.
export function login(email, password) {
const request = axios({
headers: {
'content-type': 'application/json'
},
method: 'post',
url: `${ROOT_URL}login/login`,
params: {
email,
password
}
})
.then((data) => dispatch(dispatchLogin(data)));
}
function dispatchLogin(data) {
return {
type: 'USER_LOGIN',
payload: data
};
}
The reducer should retrieve the action's type and act edit the app's state.
export default function (state = {}, action) {
switch (action.type) {
case 'USER_LOGIN': {
console.log(action.payload.data);
state.userLogged = action.payload.data.status;
return { ...state }
}
default: return state;
}
}
Redux actions are synchronous by default, you have to use a middleware like redux-thunk for asynchronous actions: https://github.com/gaearon/redux-thunk
Example (untested):
export function login(email, password) {
return async (dispatch, getState) => {
let data = await axios({
headers: {
'content-type': 'application/json'
},
method: 'post',
url: `${ROOT_URL}login/login`,
params: {
email,
password
}
});
dispatch(dispatchLogin(data));
};
}
Related
I'm trying to call a function from React JSX button and I have a problem with that because when in react-redux actions in the defined function I don't put dispatch function, works as it supposed to.
const like_function = (id) => {
let post_id = id;
if (isAuthenticated) {
console.log(post_id, user_id);
like_post(post_id, user_id);
} else {
<Redirect to="/login" />;
console.log("Redirect to login");
}
};
Here in this button I invoke function with one parameter.
<button onClick={() => like_function(post.id)}>Like</button>
This is redux action. Here is the problem. When dispatch is deleted function works but with dispatch is not even called, it wont even log data to console before async request
export const like_post = (post_id, user_id) => async (dispatch) => {
const data = { post_id: post_id, user_id: user_id };
console.log(data);
dispatch({
type: POST_LIKE_LOADING,
});
try {
const res = await axios.put(`http://localhost:8000/api/like_list/`, data);
//console.log(res.data);
dispatch({
type: POST_LIKED,
payload: res.data,
});
} catch (err) {
console.log(err);
dispatch({
type: POST_LIKEING_FAIL,
});
}
};
Here are my redux reducers
case POST_LIKE_LOADING:
return {
...state,
isLoading: true,
};
case POST_LIKED:
return {
...state,
isLoading: true,
message: "OK"
};
Sorry about my English, hope you understood me, thanks in advance
You're not dispatching the action.
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
dispatch(like_post(post_id, user_id))
I'm following this tutorial: https://github.com/callicoder/spring-security-react-ant-design-polls-app
I already have a working backend that generates the JWT, and also the API returns the current user's details in http://localhost:8080/api/user/me with the GET method. All good on the back (tested with Postman).
But I have a problem when I try to load the current user from the API to the state of my App component. The idea is to create a route to the Login component and pass it a reference to the handleLogin method, which executes getCurrentUser() and redirects to the main page.
All of this is done via imports of a file called APIUtils that has methods to interact with the API. In particular, it has a generic request() method, that returns a promise using fetch, and receives the request's parameters. The problem is I can't get the response of the promise returned by the APIUtils/request() method. It says it's undefined.
App.js
//imports
class App extends Component {
state = {
currentUser: null
}
loadCurrentUser = () => {
// getCurrentUser is imported from APIUtils/index.js
getCurrentUser()
.then(response => {
this.setState({
currentUser: response
});
})
.catch(error => {
console.log(error)
});
}
handleLogin = () => {
this.loadCurrentUser();
this.props.history.push("/");
}
componentDidMount() {
this.loadCurrentUser();
}
render () {
return (
<Switch>
<Route exact path='/' component={Landing} />
<Route path="/login"
render={
(props) => <Login onLogin={this.handleLogin} {...props} />
}/>
</Route>
</Switch>
);
}
}
export default withRouter(App);
APIUtils/index.js
const request = (options) => {
const headers = new Headers({
'Content-Type': 'application/json',
})
if(localStorage.getItem(ACCESS_TOKEN)) {
headers.append('Authorization', 'Bearer ' + localStorage.getItem(ACCESS_TOKEN))
}
const defaults = { headers: headers };
options = Object.assign({}, defaults, options);
return fetch(options.url, options)
.then(response => {
response.json().then(json => {
if(!response.ok) {
return Promise.reject(json);
}
return json;
})}
);
}
// expects loginRequest = { email: 'something', password: 'something' }
export const login = (loginRequest) => {
return request({
url: API_BASE_URL + "/auth/signin",
method: 'POST',
body: JSON.stringify(loginRequest)
});
}
export const getCurrentUser = () => {
if(!localStorage.getItem(ACCESS_TOKEN)) {
return Promise.reject("No access token set.");
}
return request({
url: API_BASE_URL + "/user/me",
method: 'GET'
});
}
Login.js
class Login extends Component {
state = {
email: '',
password: ''
}
handleChange = (event) => {
this.setState({
[event.target.id]: event.target.value
});
}
handleSubmit = (event) => {
event.preventDefault();
const loginRequest = Object.assign({}, this.state);
login(loginRequest)
.then(response => {
localStorage.setItem(ACCESS_TOKEN, response.accessToken);
this.props.onLogin();
}).catch(error => {
if(error.status === 401) {
console.log('Your Username or Password is incorrect. Please try again!');
} else {
console.log('Sorry! Something went wrong. Please try again!');
}
});
}
render () {
return (
<React.Fragment>
/*
* form using onSubmit={this.handleSubmit}
* inputs using value={this.state.email} and onChange={this.handleChange}
* button of type="submit"
*/
</React.Fragment>
);
}
}
export default Login;
With this, after I log in and I load the landing page, via console I checked and I have the token in the local storage, also the request() method in APIUtils returns a response with URL: http://localhost:8080/api/user/me, and returns the json promise that's in the code, which is something like this:
{
"id": 23,
"name": "john",
"email": "new#mail.com"
}
But when I try to access the response from getCurrentUser() in App.js using then(), it response is undefined, so I can't set it to the state.
You are not returning the result of fetch():
return fetch(options.url, options)
.then(response => { // you forgot that this is also a function
return response.json().then(json => { // <--- missing return!!
if(!response.ok) {
return Promise.reject(json);
}
return json;
})}
);
I can understand why you may have missed it. It's fairly easy to miss. Which is why you should use the main feature of Promises - the reason they were invented - promise chaining:
let ok = null;
return fetch(options.url, options)
.then(response => {
ok = response.ok;
return response.json();
})
.then(json => {
if (!ok) {
return Promise.reject(json);
}
return json;
});
It's easier to spot a missing return this way because you can easily check that each then block has a return statment.
So I have recently stared to work with react, I am authenticating a user in my App component like this:
App
signIn(userData) {
console.log(userData)
//do a fetch call to get/users
axios.get('http://localhost:5000/api/users', {
auth: { //set auth headers so that userData will hold the email address and password for the authenticated user
username: userData. emailAddress,
password: userData.password
}
}).then(results => { console.log(results.data)
this.setState({
//set the authenticated user info into state
emailAddress: results.data,
password: results.data.user
});
})
}
and I also have another component called CreateCourse that allows a post request only if I provided the auth header from App,
CreateCourse
handleSubmit = event => {
event.preventDefault();
console.log(this.props)
const newCourse = {
title: this.state.title,
description: this.state.description,
estimatedTime: this.state.estimatedTime,
materialsNeeded: this.state.materialsNeeded
};
axios({
method: 'post',
url: 'http://localhost:5000/api/courses',
auth: {
username: this.props.emailAddress,
password: this.props.password
},
data: newCourse
}).then(
alert('The course has been successfully created!')
).then( () => {
const { history } = this.props;
history.push(`/`)
})
};
I was wondering if I could pass the auth header from App to the children components without using props or context api so that I don't have to manually put the auth headers on every axios request, for reference this is my repo : https://github.com/SpaceXar20/full_stack_app_with_react_and_a_rest_api_p10
I always create a singleton axios instance and set header for it after user signin successful.
let instance = null
class API {
constructor() {
if (!instance) {
instance = this
}
this.request = Axios.create({
baseURL: 'http://localhost:5000',
})
return instance
}
setToken = (accessToken) => {
this.request.defaults.headers.common.authorization = `Bearer ${accessToken}`
}
createCourses = () => this.request.post(...your post request...)
}
export default new API()
After your login successfull, you need call API.setToken(token). Then, when you call Api.createCourse(), the request will have token in headers.
singleton axios instance is the right approach . In the same pattern, use the below method .Import the file wherever required and use axiosapi.get .
const axiosConfig = {auth: {username: XXXX, password: YYYY}};
const axiosservice = axios.create(axiosConfig);
export const axiosapi = {
/**
* Describes the required parameters for the axiosapi.get request
* #param {string} url
* #param {Object} config - The configfor the get request (https://github.com/axios/axios#request-config)
*
* #returns {Promise}
*/
get: (url, config = {}, params) => {
return axiosservice.get(url, {
params,
responseType: 'json',
transformResponse: [
data => {
const parsedData = typeof data === 'string' ? JSON.parse(data) : data;
return get(parsedData);
},
],
...config,
})
.then()
.catch(error => {
return Promise.reject(error);
});
},
}
I have start my first unit test in react with jest this afternoon. The 5 firsts tests that i have to do are about testing the return functions. No so difficult.
But i have difficulty to understand how to unit test my function login that return something i dont understand yet. Is someone see what i have to put in my action.test.js, show me and explain me ?
How can i unit testing login and what represent the dispatch that return the login function ?
**In action.js**
<pre>
import { userConstants } from '../shared/constants';
import { userService } from '../shared/services';
import { history } from '../shared/helpers';
function request(user) {
return { type: userConstants.LOGIN_REQUEST, user };
}
function success(user) {
return { type: userConstants.LOGIN_SUCCESS, user };
}
function failure(error) {
return { type: userConstants.LOGIN_FAILURE, error };
}
function login(username, password) {
return (dispatch) => {
dispatch(request({ username }));
userService.login(username, password).then(
(user) => {
dispatch(success(user));
history.push('/');
},
(error) => {
dispatch(failure(error));
console.error(error); // eslint-disable-line no-console
},
);
};
}
function logout() {
userService.logout();
return { type: userConstants.LOGOUT };
}
function oldLogin() {
return { type: userConstants.OLD_LOGIN };
}
export const userActions = {
login,
logout,
oldLogin,
};
</pre>
**In service.js**
<pre>
function logout() {
// remove user from local storage to log user out
if (localStorage.getItem('user')) {
localStorage.removeItem('user');
}
}
function handleResponse(response) {
return response.text().then((text) => {
const data = text && JSON.parse(text);
if (!response.ok) {
if (response.status === 401) {
// auto logout if 401 response returned from api
logout();
window.location.reload(true);
}
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
}
function login(username, password) {
return fetch(
'https://mon-api',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
password,
context: {
deviceToken: '1cb1b51d19665cb45dc1caf254b02af',
},
}),
},
)
.then(handleResponse)
.then((user) => {
// login successful if there's a jwt token in the response
if (user.sessionToken) {
// store user details and jwt token in local storage to
// keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user));
}
return user;
});
}
export const userService = {
login,
logout,
};
</pre>
dispatch is a redux action. To be able to test you need to mock it. There are utilities like redux-mock-store that facilitate this task, refer to the following article for more details.
i am using redux in react-native to fetch data from an api, here is whhat i have done so far
api_type.js
export const USER_LOGIN = 'user_login_action';
export const USER_LOGINING = 'logining_users';
export const USER_LOGEDIN = 'user_logged_in';
index.js
import axios from 'axios';
import { USER_LOGIN, USER_WALLETS,USER_LOGINING } from './api_types';
const AUTH_API_URL = 'http:/api/v1';
const CORE_API_URL = 'http:/api/v1';
let username="";
let password="";
let auth_token ="";
let AuthStr = "";
export function UserWallets(){
return function(dispatch){
AuthStr ="Bearer "+auth_token;
console.log ("new auth : "+AuthStr);
axios.defaults.headers.common['Authorization'] = AuthStr
axios.get(`${CORE_API_URL}/wallet/allwallets`)
.then(response => {
dispatch({
type: USER_WALLETS,
payload: response['data']
});
}).catch((error) => {
console.log(error);
})
}
}
export function UserLogin() {
return function(dispatch) {
dispatch({
type:USER_LOGINING
});
axios.post(
`${AUTH_API_URL}/authenticate/users`,
{
email: username,
password: password
}
)
.then(response => {
dispatch({
type: USER_LOGIN,
payload: response['data']
});
auth_token=response['data']['token'];
}
)
.catch((error) => {
console.log(error);
})
}
}
export function username(term) {
username=term;
console.log("username " +username);
return{
type:"username",
username
};
}
export function password(term) {
password=term;
console.log("password " +password);
return{
type:"password",
password
};
}
export function authToken (term){
auth_token = term;
return{
type:"authtoken",
auth_token
}
}
auth_reducer.js
import { USER_LOGIN ,USER_LOGINING } from '../actions/api_types';
const INTIAL_STATE = {
message: '',
token:'',
logging: false,
loggedin: false,
loginerr: null,
};
export default function (state = INTIAL_STATE, action) {
console.log("present state"+action.type);
switch(action.type) {
case USER_LOGIN:{
return { ...state, message: action.payload.message, token:action.payload.token,loggedin:true};
}
case USER_LOGINING:{
return {...state,logging:true }
}
default:{
console.log("default "+action.type);
}
}
return state;
}
index.js // combine reducer
import { combineReducers } from 'redux';
import drawer from './drawer';
import AuthReducer from './auth_reducer';
import CoreReducer from './core_reducer';
export default combineReducers({
auth: AuthReducer,
});
i have created and configured the store and wrapped my app with the provider from react-redux, and i have passed the store to the provider, in a nutshell i can now access the store from my componets.
below is a function in my login_component, that triggers once i click on login
login(){
if(this.state.email==""){
alert("Email require");
return;
}else if(this.state.password==""){
alert("password require");
return;
}else{
//set the paramter for the reducer to use
this.props.username(this.state.email);
this.props.password(this.state.password);
//activate the user login action
this.props.UserLogin();
if(!this.props.auth.loggedin){
console.log("logging in");
//show loadging gif
}
//checking from response from the auth api
if(this.props.auth.message=="user successfully logged in"){
alert(this.props.auth.token);
Actions.home();
}else{
alert("invalid Username/Password");
}
}
}
Now this is problem, once i click on login, the block of code i commented (check response from api) will not wait for the store value to change before it perform it action, please i need a way around this.
i finally got a solution to the problem, the api call was async but the problem was that in the component, i tested for the response before the store changes so here is the solution, i added the following to my login component
componentWillReceiveProps(nextProps) {
console.log("component update");
if(nextProps.auth.loggedin==true){
if(nextProps.auth.message=="user successfully logged in"){
this.setState(previousState => {
return { spinnerv: false };
});
Actions.home();
}else{
alert("invalid Username/Password");
}
}
}
what happens here is that function componentWillReceiveProps, check if the states has changed and then text if the response is componentWillReceiveProps.
thanks jmargolisvt for your support.
i hope this help someone else.
You need to perform this API call asynchronously. Basically, you will have your login function dispatch an async action that will make the API call. Then from your success/fail methods of the API call, you'll dispatch another (synchronous) call that either logs the user in or not.
You'll want to incorporate Redux Thunks to make your async call.
https://github.com/gaearon/redux-thunk