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'
});
});
});
});
Related
I tried using it with the useState hook. singlebook['title'] and singlebook['author'] show right data in the console but not show in form data as prefill data. I am new in the next js.i did it with react and useLocation hook before.but useLocation is not working in next js. if i use value=singlebook['title'] inside the input tag. i can not clicked it and erase that data and can not edit anything
[editBookId].tsx
import React, { useState} from "react";
import { useRouter } from "next/router";
import { useDispatch} from "react-redux";
import { useAppDispatch, useAppSelector } from "../../hooks";
import { getAllBooks, updateBook} from "../../slices/bookSlice";
import { useForm } from "react-hook-form";
import { ToastContainer } from "react-toastify";
const EditBook = () => {
const AllBooks = useAppSelector(getAllBooks);
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const dispatch = useDispatch();
const router = useRouter();
let editBookId = router.query.editBookId
console.log(editBookId);
//return;
let singlebook='';
AllBooks['books'].map((x)=>{
if(editBookId==x.id){
singlebook=x;
}
}
)
const [data, setData] = useState({
title: singlebook['title'],
author: singlebook['author'],
});
const handleChange = (e) => {
setData({ ...data, [e.target.name]: e.target.value });
};
//const single book = AllBooks['books'];
console.log(singlebook)
const onSubmit = (e) => {
let data={'id':singlebook['id'],...e};
console.log(data)
dispatch(updateBook(data));
router.push("/showBooks");
};
return (
<div>
<ToastContainer/>
<h1 className="text-center">Edit Book</h1>
<div className="d-flex justify-content-center">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3">
<label htmlFor="text" className="form-label">
Title
</label>
<input
{...register("title", { required: "Title is required" })}
type="text"
name="title"
// value={singlebook['title']}
onChange={handleChange}
className="form-control"
/>
{errors.title?.type === "required" && "Title is required" && (
<p style={{ color: "red" }}>Title is Required.</p>
)}
</div>
<div className="mb-3">
<label htmlFor="text" className="form-label">
Author
</label>
<input
{...register("author", { required: "Author is required" })}
type="text"
name="author"
// value={singlebook['author']}
onChange={handleChange}
className="form-control"
/>
{errors.author?.type === "required" && "Author is required" && (
<p style={{ color: "red" }}>Author is Required.</p>
)}
</div>
<button type="submit" className="btn btn-primary">
Update Book
</button>
</form>
</div>
</div>
);
};
export default EditBook;
every functionality of crud operation is implemented here.all create, read, delete, update is working.
bookSlice.ts
import { PayloadAction } from "#reduxjs/toolkit";
import { toast } from "react-toastify";
const { createSlice } = require("#reduxjs/toolkit");
import { v4 as uuidv4 } from "uuid";
import { RootState } from "../store";
type Book = {
id: string;
title: string;
author: string;
};
type initialState = {
books: Book[];
};
const initialState: initialState = {
books: [
{ id: uuidv4(), title: "new programmer", author: "john smith" },
{ id: uuidv4(), title: "Connecting the dot", author: "john lilly" },
],
};
export const booksSlice = createSlice({
name: "books",
initialState: initialState,
reducers: {
showBooks: (state) => state,
addBook: (state, action: PayloadAction<Book[]>) => {
state.books.push(action.payload);
toast.success("New Book Added");
},
updateBook: (state, action: PayloadAction<Book>) => {
const { id, title, author } = action.payload;
const isBookExist = state.books.filter((book) => book.id === id);
if (isBookExist) {
isBookExist[0].title = title;
isBookExist[0].author = author;
toast.info("Book Updated");
}
},
deleteBook: (state, action: PayloadAction<Book>) => {
const id = action.payload;
state.books = state.books.filter((book) => book.id !== id);
toast.warn("Book Deleted");
},
},
});
export const { showBooks, addBook, deleteBook, updateBook } =
booksSlice.actions;
export const getAllBooks = (state: RootState) => state.books;
export default booksSlice.reducer;
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.
I am getting this error and I don;t know what else to do.
I am using next.js and my code looks like this.
The _app.js:
import '../styles/globals.scss'
import React from 'react'
import Layout from '../components/Layout'
import Head from "next/head";
import Signin from "./signin";
import Register from "./register";
import { DataProvider } from "../store/GlobalState";
function MyApp ({
Component,
pageProps
}) {
if (typeof window !== 'undefined') {
if (window.location.pathname === '/signin') {
return (
<DataProvider>
<Signin/>
</DataProvider>
)
} else if (window.location.pathname === '/register') {
return (
<DataProvider>
<Register/>
</DataProvider>
)
}
}
return (
<DataProvider>
<Head>
<title>Above the Sky</title>
</Head>
<Layout>
<Component {...pageProps} />
</Layout>
</DataProvider>
)
}
export default MyApp
I am doing this because I want the register and the login pages to be separate from the layout, not having any header or footer whatsoever... If you have a hint on this , how I should do this better please tell me .... but this is not the main problem..
and now the Register.js:
import Head from 'next/head'
import { useContext, useEffect, useState } from "react";
import Link from 'next/link'
import valid from '../utils/valid'
import { DataContext } from "../store/GlobalState";
const Register = () => {
const [ mounted, setMounted ] = useState(false);
const initialState = {
email: '',
password: '',
cf_password: ''
};
const [ userData, setUserData ] = useState(initialState);
const {
email,
password,
cf_password
} = userData;
const {
state,
dispatch
} = useContext(DataContext)
const handleChangeInput = e => {
const {
name,
value
} = e.target
setUserData({
...userData,
[name]: value
})
dispatch({
type: 'NOTIFY',
payload: {}
})
}
const handleSubmit = async e => {
e.preventDefault()
const errorMessage = valid(email, password, cf_password)
if (errorMessage) {
return dispatch({
type: 'NOTIFY',
payload: { error: errorMessage }
})
}
dispatch({
type: 'NOTIFY',
payload: { success: 'Ok' }
})
}
useEffect(() => {
setMounted(true)
}, [])
return (
mounted
&&
<div style={{
backgroundColor: 'black',
height: '100vh'
}}>
<Head>
<title>Register Page</title>
</Head>
<div className="login-dark" style={{ height: "695px" }}>
<form className='container' onSubmit={handleSubmit}>
<div className="illustration"><i className="fas fa-thin fa-user-plus"/></div>
<div className="mb-3">
<label htmlFor="exampleInputEmail1" className="form-label">Email address</label>
<input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"
name="email" value={email} onChange={handleChangeInput}/>
<div id="emailHelp" className="form-text">We'll never share your email with anyone else.</div>
</div>
<div className="mb-3">
<label htmlFor="exampleInputPassword1" className="form-label">Password</label>
<input type="password" className="form-control" id="exampleInputPassword1"
name="password" value={password} onChange={handleChangeInput}/>
</div>
<div className="mb-3">
<label htmlFor="exampleInputPassword2" className="form-label">Confirm Password</label>
<input type="password" className="form-control" id="exampleInputPassword2"
name="cf_password" value={cf_password} onChange={handleChangeInput}/>
</div>
<div className='button-container'>
<button type="submit" className="btn btn-primary btn-block">Register</button>
</div>
<a className="forgot" href="#">Forgot your email or password?</a>
<p className="have-account">Already have an account ? <Link href="/signin"><a style={{ color: 'crimson' }}>Login here</a></Link></p>
</form>
</div>
</div>
)
}
export default Register
When I render the register page I get this error in the console ..
"next-dev.js?3515:32 Warning: Did not expect server HTML to contain a in ."
These are my store files aswell:
Actions.js
export const ACTIONS = {
NOTIFY: 'NOTIFY',
AUTH: 'AUTH'
}
Reducer.js
import { ACTIONS } from './Actions';
const reducers = (state, action) => {
switch (action.type) {
case ACTIONS.NOTIFY:
return {
...state,
notify: action.payload
};
case ACTIONS.AUTH:
return {
...state,
auth: action.payload
};
default:
return state;
}
}
export default reducers
and the GlobalState.js
import { createContext, useReducer } from "react";
import reducers from "./Reducers";
export const DataContext = createContext()
export const DataProvider = ({ children }) => {
const initialState = {
notify: {},
auth: {}
}
const [ state, dispatch ] = useReducer(reducers, initialState)
const { cart, auth } = state
return (
<DataContext.Provider value={{
state,
dispatch
}}>
{children}
</DataContext.Provider>
)
}
I am trying to write a unit test to the Login component in order to check if handleSubmit function has been called after all the input fields are filled with values.
Here is my Login.js component and my Login.test.js
//Login.js
import React, { useState } from 'react';
import axios from 'axios';
import useStyles from '../styles/LoginStyle';
import { useStatefulFields } from '../../hooks/useStatefulFields';
function Login({ success }) {
const [values, handleChange] = useStatefulFields();
const [error, setError] = useState();
const handleSubmit = async () => {
await axios.post('www.example.com', {values}, {key})
.then((res) => {
if (res.data.success) {
success();
} else {
setError(res.data.error);
}
})
};
return (
<div>
<p className={classes.p}>Personalnummer</p>
<input
type="number"
className={classes.input}
onChange={handleChange}
name="personal_number"
title="personal_number"
/>
<p className={classes.p}>Arbeitsplatz</p>
<input
type="number"
onChange={handleChange}
name="workplace"
title="workplace"
className={classes.input}
/>
<p className={classes.p}>Passwort</p>
<input
type="password"
className={classes.input}
onChange={handleChange}
name="password"
title="password"
/>
<ColorButton
id="login-button"
className={
(values.password && values.personal_number && values.workplace)
? classes.button
: classes.buttonGray
}
disabled={!values.password && !values.personal_number && !values.workplace}
size="large"
onClick={
values.password && values.personal_number && values.workplace
? handleSubmit
: () => {}
}
>
</ColorButton>
</div>
)
}
//Login.test.js
import React from 'react';
import { shallow } from 'enzyme';
import Login from '../components/Workers/Login';
let wrapper;
beforeEach(() => {
wrapper = shallow(<Login success={() => {}} />);
});
test('should call handleSubmit', () => {
const spy = jest.spyOn(wrapper.instance(), 'handleSubmit');
wrapper.find('input[name="workplace"]').simulate('change', { target: { name: 'workplace', value: 'test' } });
wrapper.find('input[name="password"]').simulate('change', { target: { name: 'password', value: 'test' } });
wrapper.find('input[name="personal_number"]').simulate('change', { target: { name: 'personal_number', value: 'test' } });
wrapper.find('#login-button').simulate('click');
expect(spy).toHaveBeenCalled();
});
The error that I am getting is:
TypeError: Cannot read property 'handleSubmit' of null
What am I doing wrong?
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: [ ]
}