React redux - update object state with new value - reactjs

I use Redux for the first time, and I don't success to update a state. Here is the workflow : User logged in the app (/login), a token is stored in sessionStorage, and I dispatch user info to access userData in the /dashboard/:id page.
Right now when I console.log props on the Dashboard components, here is the result :
userData : {}
So the object userData is still empty.
Here is the code :
LoginForm :
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as authActions from '../../actions/authActions';
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
errors: {},
isLoading: false,
};
}
onChange(e) {
this.setState({
[e.target.name]: e.target.value
})
}
onSubmit(e) {
e.preventDefault();
this.setState({ errors: {}, isLoading: true });
this.props.actions.logInUser( { data: { user: { email: this.state.email, password: this.state.password }}})
}
render() {
return(
<div>
<form onSubmit={this.onSubmit.bind(this)}>
<div className="field">
<label className="label"> Email </label>
<div className="control">
<input type="email"
name="email"
value={this.state.email}
onChange={this.onChange.bind(this)}
className="input" />
</div>
</div>
<div className="field">
<label className="label"> Mot de passe </label>
<div className="control">
<input type="password"
ref="password"
name="password"
value={this.state.password}
onChange={this.onChange.bind(this)}
className="input" />
</div>
</div>
<div className="form-group">
<input type="submit" value="Signup" className="button is-primary" />
</div>
<Link to={{ pathname: '/register' }}>Inscription</Link>
</form>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(authActions, dispatch)
};
}
export default connect(null, mapDispatchToProps)(LoginForm);
The authAction :
import { browserHistory } from 'react-router';
import * as types from './types';
import sessionApi from '../api/SessionApi';
export function loginSuccess(userData) {
return {
type: types.LOG_IN_SUCCESS,
payload: userData
}
}
export function loginFailed() {
return {
type: types.LOG_IN_FAILED
}
}
export function logInUser(credentials) {
return function(dispatch) {
return sessionApi.login(credentials)
.then(response => {
console.log(response);
if(response.data) {
sessionStorage.setItem('jwt', response.data.authentication_token);
dispatch(loginSuccess(response.data));
browserHistory.push('/dashboard/' + response.data.id);
} else {
dispatch(loginFailed());
}
})
.catch(error => {
throw(error);
})
}
}
The API :
import axios from 'axios';
class SessionApi {
static login(credentials) {
return axios.post('<link_hidden>', credentials)
.then(response => {
console.log(response);
return response;
})
.catch(error => {
return error;
});
}
}
export default SessionApi;
The session reducer (I have a rootReducer for combineReducer) :
import * as types from '../actions/types';
import initialState from './initialState';
export default function sessionReducer(state = initialState, action) {
switch(action.type) {
case types.LOG_IN_SUCCESS:
console.log(action);
return {
...state,
userData: action.payload
}
case types.LOG_IN_FAILED:
console.log('login failed');
break;
default:
return state;
}
}
and the initialState file :
export default {
session: !!sessionStorage.jwt,
userData: {}
}
Does someone know why the userData object is still empty ? In the session reducer I pass the userData so I don't understand ..
EDIT
dashboard.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Dashboard extends Component {
constructor(props) {
super(props);
console.log(props);
}
render() {
return(
<div>
<h1> Hello user </h1>
</div>
);
}
}
function mapStateToProps(state) {
const userData = state.sessionReducer.userData;
return { userData };
}
export default connect(mapStateToProps)(Dashboard);

Your code seems fine to me. Could you check your router if it's working correctly?

Try and change your userData to array, I think your payload may be an array of objects. That is:
export default {
session: !!sessionStorage.jwt,
userData: [ ]
}

Related

React-Redux: Unhandled Rejection (TypeError): dispatch is not a function

I am struggling with Login page.
This is the actions/login.js:
export const login = (username, password) => (dispatch) => {
return AuthService.login(username, password).then(
(data) => {
debugger;
dispatch({
type: LOGIN_SUCCESS,
payload: { user: data },
});
return Promise.resolve();
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: LOGIN_FAIL,
});
dispatch({
type: SET_MESSAGE,
payload: message,
});
return Promise.reject();
}
);
};
This is my AuthService.js :
import {BASE_URL} from "../constants/globalConstants";
import axios from "axios";
export const USER_INFO = 'USER_INFO';
const loginEndpoint = BASE_URL + "authenticate";
class AuthService {
login(username, password) {
debugger;
return axios
.post(BASE_URL + "authenticate", { username, password })
.then((response) => {
if (response.data.jwtToken) {
localStorage.setItem(USER_INFO, JSON.stringify(response.data));
}
return response.data;
});
}
logout() {
localStorage.removeItem(USER_INFO);
}
register(username, email, password) {
return axios.post(BASE_URL + "register", {
username,
email,
password,
});
}
}
export default new AuthService();
And finally the Login.js:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link } from "react-router-dom";
import { Container, Row, Col, Card, CardBody, FormGroup, Label, Input, Button } from "reactstrap";
import { AvForm, AvField } from "availity-reactstrap-validation";
import axios from 'axios'
import { bindActionCreators } from "redux";
import { selectedSidebarStyle } from "../../actions/sidebarStyleAction";
import { connect } from "react-redux";
import tokenIsValid from './authrorization/JwtAuthorization'
import './../../static/css/Auth.css'
import { BASE_URL } from "../../constants/globalConstants";
import AuthService from "../../services/AuthService";
import { login } from "../../actions/auth";
export const USER_NAME_SESSION_ATTRIBUTE_NAME = 'authenticatedUser';
export const JWT_AUTH_TOKEN = 'AUTH_TOKEN';
export const USER_INFO = 'USER_INFO';
const style = { border: '1px solid #FB3E3E' }
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
userAuth: false,
loading: false,
}
}
handleFieldChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}
// this.props.history.push(`/welcome/${this.state.username}`)
requestLogin = () => {
const loginEndpoint = BASE_URL + "authenticate";
axios({
method: 'post',
url: loginEndpoint,
data: {
username: this.state.username,
password: this.state.password
}
}).then((response) => {
if (response.data !== null) {
sessionStorage.setItem(USER_INFO, JSON.stringify(response.data));
}
}, (error) => {
console.log("Unsuccessful login request")
})
}
authHeader() {
const user = JSON.parse(localStorage.getItem(USER_INFO));
if (user && user.jwtToken) {
return { Authorization: 'Bearer ' + user.jwtToken };
} else {
return {};
}
}
isUserLoggedIn() {
let user = window.sessionStorage.getItem(USER_INFO)
if (user === null) {
return false
}
return true;
}
getLoggedInUserName() {
let user = window.sessionStorage.getItem(USER_INFO)
if (user === null) {
return ''
}
return user
}
/*
* TODO: See where to use the logout and how to redirect the user to the login page in case JWT token is expired
* */
logout() {
sessionStorage.removeItem(USER_INFO);
}
handleSubmit = (e) => {
e.preventDefault();
const {dispatch} = this.props;
dispatch(login(this.state.username, this.state.password))
.then(() => {
window.location.reload();
})
.catch(() => {
this.setState({
loading: false
});
});
}
render() {
return (
<React.Fragment>
<div className="account-home-btn d-none d-sm-block">
<Link to="/" className="text-white"><i className="mdi mdi-home h1"></i></Link>
</div>
<section className="bg-account-pages height-100vh">
<img className={"hive-logo1"} src={require('./hive-logo.png')} alt="Logo" width="70px" height="60px" />
<div className="display-table">
<div className="display-table-cell">
<Container>
<Row className="justify-content-center">
<Col lg={5}>
<Card className="account-card">
<CardBody>
<div className="text-center mt-3">
<h3 className="font-weight-bold"><a href=""
className="text-dark text-uppercase account-pages-logo">Sign In</a>
</h3>
<u><p className="text-muted">Enter your credentials to continue to the platform.</p></u>
</div>
<div className="p-3">
<AvForm onSubmit={this.handleSubmit}>
<FormGroup>
<Label htmlFor="username">Email</Label>
<AvField type="text" name="username" value={this.state.email}
onChange={this.handleFieldChange} required className="form-control"
id="username"
placeholder="Enter email" />
</FormGroup>
<FormGroup>
<Label htmlFor="userpassword">Password</Label>
<AvField type="password" name="password" value={this.state.password}
onChange={this.handleFieldChange} required className="form-control"
id="userpassword" placeholder="Enter password" />
</FormGroup>
<div className="custom-control custom-checkbox">
<Input type="checkbox" className="custom-control-input" id="customControlInline" />
<Label className="custom-control-label" htmlFor="customControlInline">Remember
me</Label>
</div>
<div className="mt-3">
<Button color="none" type="submit" className="sign-in-button" >Sign In</Button>
</div>
<div className="mt-4 mb-0 text-center">
<Link to="password_forget" className="text-dark"><i className="mdi mdi-lock"></i> Forgot
your password?</Link>
</div>
</AvForm>
</div>
</CardBody>
</Card>
</Col>
</Row>
</Container>
</div>
</div>
</section>
</React.Fragment>
);
}
}
Login.PropTypes = {
dispatch: PropTypes.func,
login: PropTypes.func
};
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
login
}, dispatch)
};
}
const mapStateToProps = (state) => {
const { isLoggedIn } = state.auth;
const { message } = state.message;
return {
isLoggedIn,
message
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
And I made so many changes and I can't fix this:
enter image description here
I am trying to push the login details, fetched from the bckend to the Session Storage and push it to the Redux so I can fetch the data later after loging and keep the token, id, password and email for the user
Somewhere in the documentation I have read that if we use mapDispatchToProps function in the connect method then the component will not get dispatch function as props .I tried finding the document link but could not get it.
try debugging component props to see dispatch function is there or not
You are already binding login with the dispatch.
So to call that, you need to do this;
this.props.login(...)
instead of this;
dispatch(login(...))
Functions in mapDispatchToProps are added to dispatch and if you call them like this this.props.function_name(), they are dispatched too.

TypeError: _this.props.addLead is not a function in react

i am having issues with prime-react, i tried to connect it to my django api which i had done successfully b4 and it work but with prime-react the same code doesn't seem to work. i don't know if it is running react dom or this is just a bug. so got this warning message in my console. Move code with side effects to componentDidMount, and set initial state in the constructor.
Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 17.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run npx react-codemod rename-unsafe-lifecycles in your project source folder.
'''
LeadsForm
import React, { Component } from 'react'
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { addLead } from '../actions/leads';
import {InputTextarea} from 'primereact/inputtextarea';
import {InputText} from 'primereact/inputtext';
import { Button } from 'primereact/button';
export class LeadsForm extends Component {
state = {
name: '',
email: '',
message: ''
};
static propTypes = {
addLead: PropTypes.func.isRequired
};
onChange = e => this.setState({ [e.target.name]: e.target.value});
onSubmit = e => {
e.preventDefault();
const { name, email, message } = this.state;
const lead = { name, email, message };
this.props.addLead(lead);
this.setState({
name: "",
email: "",
message: ""
});
};
render() {
const { name, email, message } = this.state;
return (
<div className="card card-w-title">
<div className="p-grid ">
<h2>Add Lead</h2>
<form onSubmit={this.onSubmit}>
<div className="p-col-12">
<span className="p-float-label">
<InputText id="in"
name="name"
value={name}
onChange={this.onChange} size={50} />
<label htmlFor="in">Name</label>
</span>
</div>
<div className="p-col-12">
<span className="p-float-label">
<InputText id="in"
name="email"
value={email}
onChange={this.onChange} size={50}/>
<label htmlFor="in">Email</label>
</span>
</div>
<div className="p-col-12">
<span className="p-float-label">
<InputTextarea id="in" size={50} rows={5} cols={30}
name="message"
value={message}
onChange={this.onChange} />
<label htmlFor="in">Message</label>
</span>
</div>
<Button type = "submit" value="Submit" label="Save" style={{marginBottom:'10px'}} className="p-button-raised" />
{/* <button type="submit" className="btn btn-primary">
Submit
</button> */}
</form>
</div>
</div>
)
}
}
export default connect(null, { addLead })(LeadsForm);
// here is the actions/leads file
import axios from 'axios';
import { GET_LEADS, ADD_LEAD } from './types'
export const getLeads = () => dispatch => {
axios
.get("http://127.0.0.1:8000/api/leads/")
.then(res => {
dispatch({
type: GET_LEADS,
payload: res.data
});
}).catch(err => console.log(err));
};
// ADD LEADS
export const addLead = lead => dispatch => {
axios
.post("http://127.0.0.1:8000/api/leads/", lead)
.then(res => {
dispatch({
type: ADD_LEAD,
payload: res.data
});
}).catch(err => console.log(err));
}
//here is the action/types file
export const GET_LEADS = "GET_LEADS";
export const ADD_LEAD = "ADD_LEAD";
//reducers/leads
import { GET_LEADS, ADD_LEAD } from '../actions/types.js';
const initialState = {
leads: []
}
export default function (state = initialState, action){
switch(action.type){
case GET_LEADS:
return {
...state,
leads: action.payload
};
case ADD_LEAD:
return {
...state,
leads: [...state.leads, action.payload]
}
default:
return state;
}
}
//reducers/index
import { combineReducers } from 'redux';
import leads from './leads';
export default combineReducers({
leads
});
'''
as said, run the command npx react-codemod rename-unsafe-lifecycles and if you get error like this:
npm ERR cb() never called
then run this:
npm config set registry https://registry.npmjs.org/
and then again run this: npx react-codemod rename-unsafe-lifecycles
This should solve the problem.

redux-saga always returns 'undefined'

I have been trying to create a React app using redux and redux-saga, but I haven't been able to make it work, and I alway get the value of undefined as the result.
This is my component catalogs/index.js:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import CatalogHeader from './CatalogHeader';
import CircularProgress from '#material-ui/core/CircularProgress';
import {getCatalogs} from 'actions/Catalogs';
import IntlMessages from 'util/IntlMessages';
import CustomScrollbars from 'util/CustomScrollbars';
class Catalogs extends Component {
constructor() {
super();
this.state = {
anchorEl: null
}
}
updateSearch = (evt) => {
this.props.updateSearch(evt.target.value);
this.onSearchTodo(evt.target.value)
};
render() {
const {catalogsList} = this.props;
return (
<div className="app-wrapper">
<div className="animated slideInUpTiny animation-duration-3">
<div className="app-module">
<div className="module-box">
<div className="module-box-header">
<CatalogHeader catalogsList={catalogsList}
placeholder="Buscar" user={this.props.user}
onChange={this.updateSearch.bind(this)}
value={this.props.searchTodo}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = ({catalogs, settings}) => {
const {width} = settings;
const {catalogsList} = catalogs;
return {
width,
catalogsList,
}
};
export default connect(mapStateToProps, {
getCatalogs,
})(Catalogs);
This is the other component catalogs/CatalogHeader.js
import React from 'react';
import IconButton from '#material-ui/core/IconButton';
import Button from '#material-ui/core/Button';
import {Dropdown, DropdownMenu, DropdownToggle, Popover} from 'reactstrap';
import SearchBox from 'components/SearchBox';
import Input from '#material-ui/core/Input';
import InputLabel from '#material-ui/core/InputLabel';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import FormHelperText from '#material-ui/core/FormHelperText';
import Select from '#material-ui/core/Select';
class CatalogHeader extends React.Component {
handleChange = name => event => {
this.setState({[name]: event.target.value});
};
onSearchBoxSelect = () => {
this.setState({
searchBox: !this.state.searchBox
})
};
constructor() {
super();
this.state = {
anchorEl: undefined,
searchBox: false,
popoverOpen: false
};
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState({
popoverOpen: !this.state.popoverOpen
});
}
printCatalogs(catalogs) {
console.log(catalogs);
}
render() {
const {catalogs} = this.props;
return (
<div className="module-box-header-inner catalogs-header">
<div className="col-5 catalogs-header">
<FormControl className="w-100 mb-2">
{this.printCatalogs(this.props.catalogs)}
<InputLabel htmlFor="age-simple">Seleccione Catálogo</InputLabel>
<Select
value={this.state.age}
onChange={this.handleChange('age')}
input={<Input id="ageSimple1"/>}>
{catalogs.map(catalog =>
<MenuItem key={catalog}>
{catalog}
</MenuItem>,
)}
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
</div>
<div className="module-box-header-right col-7 catalogs-header">
<div className="search-bar right-side-icon bg-transparent d-none d-sm-block col-6">
<div className="form-group">
<input className="form-control border-0" type="search" placeholder="Buscar"/>
<button className="search-icon"><i className="zmdi zmdi-search zmdi-hc-lg"/></button>
</div>
</div>
<div className="col-6">
<div className="catalogs-header catalogs-header-buttons">
<Button variant="contained" className="jr-btn bg-white">
<i className="zmdi zmdi-filter-list zmdi-hc-fw"/>
<span>Filtrar</span>
</Button>
<Button variant="contained" className="jr-btn bg-white">
<i className="zmdi zmdi-sort zmdi-hc-fw"/>
<span>Ordenar</span>
</Button>
</div>
</div>
</div>
</div>
)
}
}
export default CatalogHeader;
This is my actions file:
import {
GET_CATALOGS,
GET_CATALOGS_SUCCESS,
SHOW_MESSAGE
} from 'constants/ActionTypes';
export const getCatalogs = (group) => {
return {
type: GET_CATALOGS,
payload: group
};
};
export const getCatalogsSuccess = (catalogs) => {
return {
type: GET_CATALOGS_SUCCESS,
payload: catalogs
}
};
export const showCatalogsMessage = (message) => {
return {
type: SHOW_MESSAGE,
payload: message
};
};
This is my reducers file:
import {
GET_CATALOGS,
SHOW_MESSAGE,
HIDE_MESSAGE,
GET_CATALOGS_SUCCESS
} from 'constants/ActionTypes';
const INIT_STATE = {
loader: false,
alertMessage: '',
showMessage: false,
catalogsList: null
};
export default (state=INIT_STATE, action) => {
switch (action.type) {
case GET_CATALOGS_SUCCESS: {
return {
...state,
loader: false,
catalogsList: action.payload
}
}
case SHOW_MESSAGE: {
return {
...state,
alertMessage: action.payload,
showMessage: true,
loader: false
}
}
case HIDE_MESSAGE: {
return {
...state,
alertMessage: '',
showMessage: false,
loader: false
}
}
default:
return state;
}
}
This is my sagas file:
import {all, call, fork, put, takeEvery} from "redux-saga/effects";
import {catalogs} from 'backend/Catalogs';
import {GET_CATALOGS} from "constants/ActionTypes";
import {getCatalogs, getCatalogsSuccess, showCatalogsMessage} from 'actions/Catalogs';
const getCatalogsRequest = async (group) => {
await catalogs.getCatalogs(group)
.then(catalogsList => catalogsList)
.catch(error => error);
}
function* getCatalogsListFromRequest({payload}) {
const {group} = payload;
try {
const catalogsList = yield call(getCatalogsRequest, group);
if (catalogsList.message) {
yield put(showCatalogsMessage(catalogsList.Message));
} else {
yield put(getCatalogsSuccess(catalogsList.catalogsList))
}
} catch (error) {
yield put(showCatalogsMessage(error));
}
}
export function* getCatalogsList() {
yield takeEvery(GET_CATALOGS, getCatalogsListFromRequest);
}
export default function* rootSaga() {
yield all([
fork(getCatalogsList)
]);
}
And this is the file that performs the ajax request through Axios (I know for sure that this code is never reached, and a request to the backend server is never performed):
import axios from 'axios';
import {backendServer} from './constants';
const getCatalogsEndpoint = backendServer + 'api/util/catalogs/';
const getCatalogs = (group) => {
console.log('here inside getCatalogs in backend/Catalogs');
return axios.get(getCatalogsEndpoint+(group=null)?null:'?group='+group)
.then(Response => {
return {catalogsList: Response.data}
})
.catch(Error => Error);
};
export const catalogs = {
getCatalogs: getCatalogs
}
The problem arises in CatalogsHeader.js, because this.props.catalogsList always has the value of undefined:
I see problem in CatalogHeader. I think, you are referring a wrong pop.
In index.js you are passing the prop as catalogsList={catalogsList} to CatalogHeader.
But in CatalogHeader you are accessing prop as const {catalogs} = this.props; i.e. catalogs which is undefined. Please try changing this into catalogsList.

Jest - Mocking a function call within the handleSubmit of a form

I am trying to write a test that mocks the calling of a function within the handleSubmit of a form, however, I am unable to show that the function has been called.
The form is as follows:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import signUp from '../../actions/users/sign_up';
import PropTypes from 'prop-types';
class Signup extends Component {
constructor (props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.showError = this.showError.bind(this);
}
handleChange(event) {
const target = event.target;
this.setState({ [ target.name ]: target.value });
}
handleSubmit(event) {
event.preventDefault();
this.props.signUp(this.state);
}
showError(type) {
if (this.state && this.state.error && this.state.error.data.errors[ type ]) {
return this.state.error.data.errors[ type ][ 0 ];
}
}
componentDidUpdate (prevProps, prevState) {
const props = this.props;
if (prevProps === props) {
return;
}
this.setState({
...props,
});
}
render () {
return (
<div className='container-fluid'>
<div className='row'>
<div className='col col-md-6 offset-md-3 col-sm-12 col-12'>
<div className='card'>
<div className='card-header'>
<h4>Sign Up</h4>
</div>
<div className='card-body'>
<form onSubmit={ this.handleSubmit } >
<div className="form-row">
<div className="form-group col-md-12">
<label htmlFor="email">Email</label>
<input
type="email"
name="email"
className={ `form-control ${ this.showError('email') ? 'is-invalid' : '' }` }
id="email"
placeholder="Email"
onChange={ this.handleChange }
/>
<div className="invalid-feedback">
{ this.showError('email') }
</div>
</div>
</div>
<div className="form-row">
<div className="form-group col-md-12">
<label htmlFor="username">Username</label>
<input
type="text"
name="username"
className={ `form-control ${ this.showError('username') ? 'is-invalid' : '' }` }
id="username"
placeholder="Username"
onChange={ this.handleChange }
/>
<div className="invalid-feedback">
{ this.showError('username') }
</div>
</div>
</div>
<div className="form-row">
<div className="form-group col-md-12">
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
className={ `form-control ${ this.showError('password') ? 'is-invalid' : '' }` }
id="password"
placeholder="Password"
onChange={ this.handleChange }
/>
<div className="invalid-feedback">
{ this.showError('password') }
</div>
</div>
<button type="submit" className="btn btn-primary">Sign Up</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
)
}
}
function mapStateToProps (state) {
return {
email: state.UsersReducer.email,
username: state.UsersReducer.username,
password: state.UsersReducer.password,
error: state.UsersReducer.error,
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
signUp: signUp,
}, dispatch);
}
Signup.propTypes = {
email: PropTypes.string,
username: PropTypes.string,
password: PropTypes.string,
signUp: PropTypes.func.isRequired
}
export default connect(mapStateToProps, mapDispatchToProps)(Signup);
The signUp action looks like this:
import { SIGN_UP, SHOW_USER_ERRORS } from '../types';
import axios from 'axios';
import { API_ROOT, setLocalStorageHeader } from './../../api-config';
import { push } from 'react-router-redux';
export default function signUp (params) {
return dispatch => {
axios.post(`${ API_ROOT }/auth.json`, params).then(res => {
setLocalStorageHeader(res);
dispatch(push('/profile'));
dispatch(signUpAsync(res.data));
}).catch(error => {
dispatch({ type: SHOW_USER_ERRORS, payload: { error: error.response } });
});
}
}
function signUpAsync (data) {
return {
type: SIGN_UP,
payload: data
};
}
I am trying to simulate the fact that the form will be submitted with the values obtained from the form inputs, which are in the form's state (email, username and password).
The test I currently have is:
import React from 'react';
import { shallow, mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { bindActionCreators } from 'redux';
import thunk from 'redux-thunk';
import Signup from '../../../components/users/signup';
import UsersReducer from '../../../reducers/reducer_users';
describe('<Signup />', () => {
describe('render()', () => {
test('submits the form data', async () => {
const mockStore = configureStore([thunk]);
const initialState = {
UsersReducer: {
email: '',
username: '',
password: '',
},
};
const store = mockStore(initialState);
const dispatchMock = jest.spyOn(store, 'dispatch');
const signUp = jest.fn();
const wrapper = shallow(<Signup store={store} signUp={signUp} />);
const component = wrapper.dive();
component.find('#email').simulate(
'change', {
target: {
name: 'email', value: 'foo#gmail.com'
}
}
);
component.find('#email').simulate(
'change', {
target: {
name: 'username', value: 'foo'
}
}
);
component.find('#password').simulate(
'change', {
target: {
name: 'password',
value: '1234567',
}
}
)
component.find('form').simulate(
'submit', {
preventDefault() {}
}
)
expect(dispatchMock).toHaveBeenCalled();
expect(signUp).toHaveBeenCalledWith({
email: 'foo#gmail.com',
username: 'foo',
password: '12345678'
});
});
});
});
But I keep getting the following error no matter what I try.
Expected mock function to have been called with:
[{"email": "foo#gmail.com", "password": "12345678", "username": "foo"}]
But it was not called.
I think it's due to the fact that signUp isn't being mocked properly in shallow(<Signup store={store} signUp={signUp} />) because when I do console.log(wrapper.props()) I get:
{
...
signUp: [Function],
...
}
rather than an indication that it's a mocked function:
{ [Function: mockConstructor]
_isMockFunction: true,
...
}
I know that the signUp action is being called by the dispatch of the test is passing. I can also see the params in the signUp action when I add a console.log(params) into it.
Any assistance would be greatly appreciated.
Your add signUp in the mapDispatchToProps when adding redux to the view.
As you use redux-mock-store you can access all actions that were called by store.getActions() So in your case, instead of passing a signUp as spy which will be overwritten by mapDispatchToProps, it could look like this:
const signUpCall = store.getActions()[0]
expect(signUpCall).toHaveBeenCalledWith({
email: 'foo#gmail.com',
username: 'foo',
password: '12345678'
});
So, after a lot of trial and error, the solution was to mock the action call itself which was done by adding import * as signUp from '../../../actions/users/sign_up'; and mocking it with const signUpActionMock = jest.spyOn(signUp, 'default');
The test now looks like this:
import React from 'react';
import { shallow } from 'enzyme';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import Signup from '../../../components/users/signup';
import UsersReducer from '../../../reducers/reducer_users';
// Turns out this import allowed the signUp action to be mocked
import * as signUp from '../../../actions/users/sign_up';
describe('<Signup />', () => {
describe('render()', () => {
test('submits the form data', () => {
const middlewares = [thunk]
// Mock the signUp action call
const signUpActionMock = jest.spyOn(signUp, 'default');
const mockStore = configureStore(middlewares);
const initialState = {
UsersReducer: {
email: '',
username: '',
password: '',
},
};
const store = mockStore(initialState);
const wrapper = shallow(<Signup store={store} />);
const component = wrapper.dive();
component.find('#email').simulate(
'change', {
target: {
name: 'email', value: 'foo#gmail.com'
}
}
);
component.find('#email').simulate(
'change', {
target: {
name: 'username', value: 'foo'
}
}
);
component.find('#password').simulate(
'change', {
target: {
name: 'password',
value: '12345678',
}
}
);
component.find('form').simulate(
'submit', {
preventDefault() {}
}
);
expect(signUpActionMock).toHaveBeenCalledWith({
email: 'foo#gmail.com',
username: 'foo',
password: '12345678'
});
});
});
});

Redirect to user page after login in with react-redux

I'm trying to redirect to user page after login. This is my code:
import React, {PropTypes} from 'react';
import styles from '../styles/login.scss';
import { connect } from 'react-redux';
import { checkLogin } from './../actions/login.action';
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
};
this.handleChangeUsername = this.handleChangeUsername.bind(this);
this.handleChangePassword = this.handleChangePassword.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.wasSuccessful !== this.props.wasSuccessful) {
this.redirectToUserPage(nextProps.wasSuccessful);
}
}
redirectToUserPage = () => {
if (this.props.wasSuccessful) {
window.location.href = 'localhost:3000/filterableTable';
}
};
login =() => {
this.props.dispatch(checkLogin(this.state));
};
handleChangeUsername(event) {
this.setState({username: event.target.value});
}
handleChangePassword(event) {
this.setState({password: event.target.value});
}
render() {
console.log('----', this.props.wasSuccessful);
return (
<div className={styles.loginForm}>
<h3>Welcome</h3>
<div className={styles.container}>
<label><b>Username</b></label>
<input type="text" value={this.state.username} onChange={this.handleChangeUsername} name="username" placeholder="Username" required></input>
<label><b>Password</b></label>
<input type="password" value={this.state.password} onChange={this.handleChangePassword} name="password" placeholder="Password" required></input>
<input type="submit" value="Login" onClick={this.login}></input>
</div>
</div>
);
}
}
Login.propTypes = {
login: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
wasSuccessful: PropTypes.boolean,
};
export default connect((state) => ({
wasSuccessful: state.login.wasSuccessful
}))(Login);
On wasSuccessful the state changess to True, so when i press login, i should be able to redirect to user page.
componentWillReceiveProps(nextProps) {
if (nextProps.wasSuccessful !== this.props.wasSuccessful) {
this.redirectToUserPage(nextProps.wasSuccessful);
}
}
I understood that this will be called when the state is changed, so in here i call the function redirectToUserPage.
redirectToUserPage = () => {
if (this.props.wasSuccessful) {
window.location.href = 'localhost:3000/filterableTable';
}
};
I tryed like this, but it doesn't work. Please, help~
I suggest hashHistory from my experience,
import {hashHistory} from 'react-router'
redirectToUserPage = () => {
if (this.props.wasSuccessful) {
hashHistory.push('#/filterableTable');
}
};
I'm pretty sure you're getting a console error (something like "cannot access wasSuccessful of undefined") because of this.props.wasSuccessful inside redirectToUserPage. You're not binding this to redirectToUserPage function in constructor. The scope of this has changed from that of Login class to redirectToUserPage's. The this inside that function doesnt have props.

Resources