Lost in testing Redux vs Sinon/React - reactjs

Im completely lost on how to test the React components itself which include XHR calls. I have below items
LoginForm
Redux with action + reducer
Sinon (for spy/xhr stuff)
Chai
Below is some basic setup code. Please note that its not the full code but i guess you get the idea:
Form
class Form extends React.Component {
constructor() {
super();
this.state = {
username: null,
password: null
};
this.handleForm = this.handleForm.bind(this);
}
handleForm(e) {
e.preventDefault();
this.props.dispatch(authenticateUser(this.state.username, this.state.password));
}
render() {
<Form>
}
}
Action
export function authenticateUser(username, password) {
return function(dispatch, getState) {
dispatch({type: 'AUTHENTICATION_USER'});
fetch(getState().config.login_endpoint, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: {
username: username,
password: password
}
}).then(function (data) {
if(data.status === 404) {
dispatch({type: 'AUTHENTICATION_USER_NOTFOUND', payload: data.statusText});
} else {
dispatch({type: 'AUTHENTICATION_USER_FULFILLED', payload: data.json()});
}
}).catch(function (error) {
dispatch({type: 'AUTHENTICATION_USER_REJECTED', payload: error.message});
});
};
}
Reducer
export default function (state = {
loading: false,
errorMessage: null,
passwordSent: false
}, action) {
switch (action.type) {
case 'AUTHENTICATION_USER':
return {...state, loading: true, errorMessage:null};
case 'AUTHENTICATION_USER_NOTFOUND':
case 'AUTHENTICATION_USER_REJECTED':
return {...state, loading: false, errorMessage: action.payload};
case 'AUTHENTICATION_USER_FULFILLED':
window.location = '/';
break;
}
return state;
}
To test all this i setup a few test cases:
describe('components/LoginForm', () => {
it('should push username and password in xhr call after form submit');
it('redirects in case of succesfull login to the configured endpoint');
it('should show an user error on a 404 response');
it('should show an user error on a 500 response');
});
Testing of all the internal component stuff is already finished but no idea how we can handle the XHR stuff and also the redux events. I checked this page as reference but i really dont get the idea and it is also focussed on Jest instead
http://redux.js.org/docs/recipes/WritingTests.html
Hope that someone could bump me into the correct direction on how to handle this.
Thanks

Related

Redux Actions must be plain objects error

I am developing a frontend application using ReactJS. I haven't used redux before and I am getting an error.
I have the following code:
import { connect } from 'react-redux';
import PharmacistPreregisterComponent from "../components/PharmacistPreregisterComponent";
import { postPreregisteredPharmacist } from "../actions";
const mapDispatchToProps = dispatch => ({
onClick: (email, drugstoreId, alert) => {
dispatch(
postPreregisteredPharmacist(email, drugstoreId, alert)
);
}
});
export default connect (
null,
mapDispatchToProps
)(PharmacistPreregisterComponent)
In PharmacistPreregisterComponent the method:
handleSubmit(event) {
event.preventDefault();
this.onClick(
this.state.email,
this.state.drugstoreId,
this.state.alertMessage);
this.setState({
email: '',
drugstoreId: '',
alertMessage: ''
});
}
And the following action:
const PREREGISTER_PHARMACIST_SAVE_URL = "http://localhost:3000/admin/preregister/add"
export function postPreregisteredPharmacist(email, drugstoreId, alert) {
return dispatch => {
console.log("in action");
return fetch(PREREGISTER_PHARMACIST_SAVE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ "email": email, "drugstoreId": drugstoreId})
}).then ( response => {
console.log(response);
}).catch( error => {
console.log(error);
})
}
}
When submitting the form I get Actions must be plain objects. Use custom middleware for async actions. and I can't seem to figure out what the problem is.
As suggested by you in the comments, since you do not wish to update redux state based on the API request you can simply convert you function into a normal function instead of a action
Also consider setting the state to empty only if the API request is successful
import PharmacistPreregisterComponent from "../components/PharmacistPreregisterComponent";
import { postPreregisteredPharmacist } from "../actions";
handleSubmit(event) {
event.preventDefault();
postPreregisteredPharmacist (
this.state.email,
this.state.drugstoreId,
this.state.alertMessage
).then((response) => {
console.log(response);
this.setState({
email: '',
drugstoreId: '',
alertMessage: ''
});
});
}
export default PharmacistPreregisterComponent)
const PREREGISTER_PHARMACIST_SAVE_URL = "http://localhost:3000/admin/preregister/add"
export function postPreregisteredPharmacist(email, drugstoreId, alert) {
return fetch(PREREGISTER_PHARMACIST_SAVE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ "email": email, "drugstoreId": drugstoreId})
})
}

showing success and error messages in react/redux app

I'm trying to add toast notifications to my app, one plugin I've been trying to use is react-toastify.
The issue I'm having is probably more a general react/redux issue more than with a plugin such as react-toastify.
I'm using a reducer to set the redux state for errors and success messages, from what I understand with the current code, each error or success message is persistent in the store until another action is called to clear them.
The issue I can't figure out is how do I trigger a toast only once. Eg. I enter the wrong credentials, it creates an error toast, but whenever the state changes and reloads (typing anything into the email or password fields) it creates another toast.
How do I get it to only show once?
userActions.js
function handleErrors(res) {
if (res.ok) {
return res.json();
} else {
return res.json().then(err => {throw err;});
}
}
export const login = (user) => dispatch => {
fetch(`${url}/login`,
{
credentials: 'include',
method: 'post',
body: user,
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
})
.then(handleErrors)
.then(res =>
dispatch({
type: LOGIN,
payload: res
})
)
.catch(error =>
dispatch({
type: ERROR,
payload: error
})
)
}
userReducer.js
const initialState = {
errors: '',
success: ''
};
export default function(state = initialState, action) {
switch (action.type) {
case LOGIN:
return {
...state,
errors: '',
success: action.payload.message
};
case ERROR:
return {
...state,
success: '',
errors: action.payload.message
}
default:
return state;
}
}
app.js
app.post('/login', function(req, res) {
... return res.status(500).send({ message: 'Wrong credentials' });
... return res.status(200).send({ message: 'good!' });
});
login.js
class Login extends React.Component {
constructor() {
super();
this.state = {
email: "",
password: ""
}
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
}
render() {
const { errors, login, success } = this.props;
if (success !== '') toast.success(success, {
position: toast.POSITION.TOP_CENTER
});
if (errors !== '') toast.error(errors, {
position: toast.POSITION.TOP_CENTER
});
return (
<div>
<input type="text" id="email" placeholder="Email Address" onChange={this.handleChange} />
<input type="password" id="password" placeholder="Password" onChange={this.handleChange} />
<button onClick={() => login(JSON.stringify({email: this.state.email, password: this.state.password}))}>Log In</button>
<ToastContainer />
</div>
)
}
}
const mapStateToProps = state => ({
errors: state.store.errors,
success: state.store.success
});
export default connect(mapStateToProps, {login})(Login);
You're calling toast.success or toast.error inside render which makes a new toast pop up every time you re-render the component.
The solution is simple. Move your toast calls outside render, where they will only be called once.
One way to achieve this is to return a value from your userAction.
export const login = (user) => dispatch => {
return new Promise((resolve, reject) => {
fetch(`${url}/login`,
{
credentials: 'include',
method: 'post',
body: user,
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
})
.then(handleErrors)
.then(res => {
dispatch({
type: LOGIN,
payload: res
})
resolve(res)
}
)
.catch(error => {
dispatch({
type: ERROR,
payload: error
})
reject(error)
}
)
}
}
Then use that value to toast in login.js.
class Login ... {
...
loginUser = () => {
this.props.login(JSON.stringify({email: this.state.email, password: this.state.password}))
.then(res => {
toast.success(res.message, { position: toast.POSITION.TOP_CENTER })
}
).catch(error => {
toast.error(error.message, { position: toast.POSITION.TOP_CENTER })
}
)
}
...
render() {
return (
...
<button onClick={this.loginUser}>Log In</button>
...
)
}
}
There are other ways to achieve the same functionality and depending on the structure of your project, you may want to toast in a more generalized way.

How do you delay an async thunk action until after authentication session token has been stored?

I've built an API and I'm trying to fetch data (using axios) and render it into a component in my react-native app (node/express server). I'm using JWT to authenticate my users and react-navigation for screen routing.
I'm successfully authenticating and storing the session token into AsyncStorage. After login, the app navigates to the first screen where I want to show the list of data from my API.
The problem is that the GET request is being executed before the token is saved to AsyncStorage, so I'm getting a 401 unauthorized error.
The execution should be -
1) Authenticate user
2) Render component with fetched data
AHH! I can't figure it out. Please help? :(
I tried calling the action in lifecycle method componentDidMount, but no success with that.
Here's the parent component:
class PlansScreen extends Component {
static navigationOptions = ({ navigation }) => ({
tabBarLabel: 'Plans',
tabBarIcon: ({ tintColor }) => (
<Icon name="schedule" size={30} color={tintColor} />
)
});
render() {
return (
<View>
<ActivityList/>
</View>
);
}
}
export default PlansScreen;
Here's the child component I want to render the list of fetched data:
ignore the fact that I haven't fleshed out this entire component yet, I'm currently trying to just make the API request execute after the session token has saved
class ActivityList extends Component {
componentDidMount() {
this.props.fetchActivities();
}
render() {
// console.log(this.props);
return (
<View>
<FlatList
//My data will render here
/>
</View>
);
}
}
export default connect(null, {
fetchActivities,
})(ActivityList);
Here is the fetchActivities thunk action creator -
export const fetchActivities = () => {
return async (dispatch) => {
try {
dispatch({ type: FETCH_ACTIVITIES_INITIATE });
let token = await AsyncStorage.getItem('token');
let { data } = await axios.get(`${ROOT_URL}/activities`);
dispatch({
type: FETCH_ACTIVITIES_SUCCESS,
payload: data
});
console.log(data);
} catch(error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log(error.config);
// signupUserFail(dispatch);
};
};
};
Here's my activity list reducer -
import {
FETCH_ACTIVITIES_INITIATE,
FETCH_ACTIVITIES_SUCCESS
} from '../actions/types';
const INITIAL_STATE = {
activities: null,
loading: false
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_ACTIVITIES_INITIATE:
return { ...state, loading: true };
case FETCH_ACTIVITIES_SUCCESS:
return {...state,
activities: action.payload,
loading: false
};
default:
return state;
}
};
Here's my authentication/login thunk action creator -
export const loginUser = ({ userEmail, userPassword, navigation }) => {
return async (dispatch) => {
try {
dispatch({ type: LOGIN_USER_INITIATE });
let { data } = await axios.post(`${ROOT_URL}/users/login`, {
userEmail, userPassword
});
AsyncStorage.setItem('token', data.token);
// AsyncStorage.getItem('token').then((res) => console.log(res));
dispatch({
type: LOGIN_USER_SUCCESS,
payload: data
});
navigation.navigate('Plans');
console.log(data);
// console.log(store.getState());
} catch(error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
// console.log(error.config);
loginUserFail(dispatch);
};
};
};
and here's my login/auth reducer -
const INITIAL_STATE = {
userName: '',
userEmail: '',
userPassword: '',
user: null,
loginError: '',
signupError: '',
loading: false
};
export default (state = INITIAL_STATE, action) => {
// console.log(action);
switch (action.type) {
case USERNAME_CHANGED:
return {...state, userName: action.payload};
case EMAIL_CHANGED:
return {...state, userEmail: action.payload};
case PASSWORD_CHANGED:
return {...state, userPassword: action.payload};
case LOGIN_USER_INITIATE:
return {...state, loading: true, loginError: '', signupError: ''};
case SIGNUP_USER_INITIATE:
return {...state, loading: true, loginError: '', signupError: ''};
case LOGIN_USER_SUCCESS:
return { ...state,
user: action.payload,
loginError: '',
signupError: '',
loading: false,
userPassword: '',
userEmail: ''
};
case LOGIN_USER_FAIL:
return { ...state, loginError: 'Authentication failed!', userPassword: '', loading: false };
case SIGNUP_USER_FAIL:
return { ...state, signupError: 'Signup failed!', userPassword: '', loading: false };
default:
return state;
}
};
this.props.fetchActivities is being called before the token is saved!
I've been trying to figure this out for hours!! Would really appreciate any help, even if it's just to lead me in the right direction.
Wow... I figured out what I did wrong and I feel so ridiculous for not realizing sooner x_x I forgot to add the auth header to the GET request and that's why it wasn't authenticating.
In the fetchActivities action creator, for me it should have been -
let { data } = await axios.get(`${ROOT_URL}/activities`, {
headers: { 'x-auth': `${token}` }
});
To anyone reading this in the future ----- make sure you added your auth header. Thanks to #Oblosys for his response, which helped me realize it had nothing to do with when the token was being saved.

Update state values from a different file in react native

I am keeping all my functions in one file, and calling those functions in activities where needed in my react native project. Now in one of my functions which has a fetch api, I am fetching data from my online server and printing the response after a successful query.
Now I want to be able to update state value with the response from the fetch method in the then.
App.js
...
import {registerUsers} from './src/utils/api.js'
export class App extends Component{
state = {
isLoggedIn:false,
isLoading:false,
isAppready:false
}
_Register = (email,password,fullName) =>{
this.setState({isLoading:true})
//calling the register user function here
registerUsers(email,password,fullName)
}
...
The api file
import React from 'react'
import { Alert } from 'react-native';
export function registerUsers(email, password, fullName) {
fetch('http://00.00.00.00/reg/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userEmail: email,
userPassword: password,
userFullName: fullName
})
}).then((response) => response.json())
.then((responseJson) => {
//setState({ isLoggedIn: true, isLoading: false })
// Showing response message coming from server after inserting records.
Alert.alert(responseJson);
}).catch((error) => {
// this.setState({ isLoggedIn: true, isLoading: false })
console.error(error);
});
}
I would now want to update the state values thus isLoggedIn: true and isLoading:false after the fetch method has been processed. The problem now is that I can't figure it out where to update the state values since I am calling the registerUsers function from a different file.
I would be grateful if someone could share an idea as to how to figure this out. Thanks
registerUsers should return the promise. That way, you can handle the response directly inside your component:
API:
export function registerUsers(email, password, fullName) {
return fetch('http://00.00.00.00/reg/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userEmail: email,
userPassword: password,
userFullName: fullName
})
}).then( response => response.json());
}
Component:
import {registerUsers} from './src/utils/api.js'
export class App extends Component{
state = {
isLoggedIn:false,
isLoading:false,
isAppready:false
}
_Register = (email,password,fullName) =>{
this.setState({isLoading:true})
//calling the register user function here
registerUsers(email, password, fullName)
.then( responseJson => {
this.setState({
isLoggedIn: true,
isLoading: false,
data: responseJson
});
}).catch( error => {
this.setState({ isLoggedIn: false, isLoading: false });
});
}

React-Redux: How do I set the initial state from the response of an asynchronous AJAX call?

How to setState() the response received from an AJAX Request so that I can display them in the page?
constructor(props)
{
super(props);
this.state = {
email: '',
first_name: '',
middle_name: '',
country: '',
country_code: '',
mobile_number: '',
gender: ''
}
}
componentDidMount()
{
store.dispatch(getUserProfile())
.then(() => {
const user = this.props.userProfile.userProfile && this.props.userProfile.userProfile.data.data;
this.setState({
email: user.email,
first_name: user.first_name
});
})
}
render()
{
return (
<div className="form-group col-sm-12">
<label htmlFor="email">Email*</label>
<input type="email" name="email" value={this.state.email || ''}/>
</div>
<div className="form-group col-sm-12">
<label htmlFor="email">First Name*</label>
<input type="email" name="email" value={this.state.first_name || ''}/>
</div>
)
}
Apparently, I can't use .then() with store.dispatch method.
Uncaught TypeError: _store2.default.dispatch(...).then is not a function
getUserProfile() action function
import axios from 'axios';
export function getUserProfile()
{
return function(dispatch)
{
dispatch(userProfileSuccess(false));
dispatch(userProfileError(null));
const request = axios
({
url: "http://testapi/auth/v1/user/details",
method: "get",
headers: {
'Content-Type': 'application/json',
'Authorization' : 'Bearer ' + localStorage.getItem('access_token')
}
})
.then(function(response) { dispatch(userProfileSuccess(response)); })
.catch(function(error) {
console.log(error)
});
return {
type: 'USER_PROFILE_SUCCESS',
payload: request
}
};
}
function userProfileSuccess(userProfile)
{
return {
type: 'USER_PROFILE_SUCCESS',
userProfile: userProfile
};
}
function userProfileError(userProfileError)
{
return {
type: 'USER_PROFILE_ERROR',
userProfileError: userProfileError
};
}
export default getUserProfile;
In the AJAX call, I tried:
.then(function(response) {
return new Promise((resolve) => {
dispatch(userProfileSuccess(response));
resolve();
});
})
but the console reports the same error.
Is there a callback that I can pass to store.dispatch? What is the correct approach to this?
You can add a callback in componentDidMount()
componentDidMount()
{
store.dispatch(getUserProfile(), () => {
const user = this.props.userProfile.userProfile && this.props.userProfile.userProfile.data.data;
this.setState({
email: user.email,
first_name: user.first_name
});
})
}
This may not run exactly same, I just want to give you an idea how to add callback using arrow function so that you don't need to use then.
As you are using redux then your redux store should keep track about when the api call is in progress or has completed or caught some error. So instead of passing any callback or promise, you should dispatch an action for each event like processing, success, error etc (which you are already doing in getprofile function). Though i would say you nicely distinguish between process, success, error. For example you getprofile method should roughly look like this
export function getUserProfile() {
return function (dispatch) {
dispatch(userProfileProcessing())
const request = axios({
url: "http://testapi/auth/v1/user/details",
method: "get",
headers: {
'Content-Type': 'application/json',
'Authorization' : 'Bearer ' + localStorage.getItem('access_token'),
},
})
.then(function (response) {
dispatch(userProfileSuccess(response))
})
.catch(function (error) {
dispatch(userProfileError(response))
console.log(error)
});
};
}
It is just what i prefer. If you want it your way, that is also fine.
Now everytime you dispatch any action, redux will update the reducer state. So thats the place where you can set/reset some flag to make the component aware of what is going on with api call. So your reducer might look like this:
// getUserProfileReducer.js
userProfileReducer = (state = {}, action) => {
switch (action.type) {
case 'USER_PROFILE_PROCESSING':
return {
...state,
processing: true,
success: false,
fail: false,
userProfile: null,
}
case 'USER_PROFILE_SUCCESS':
return {
...state,
processing: false,
success: true,
fail: false,
userProfile: action.userProfile,
}
case 'USER_PROFILE_Error':
return {
...state,
processing: false,
success: false,
fail: true,
userProfile: null,
}
}
}
Now all you need to do is to access this state from you component so that you can take necessary action according to that. For that you can user mapStateToProps function which convert the redux state to prop of the component.
constructor(props) {
super(props)
this.state = {
email: '',
first_name: '',
middle_name: '',
country: '',
country_code: '',
mobile_number: '',
gender: '',
}
}
componentWillReceiveProps(newProps) {
if (newProps.userProfileStatus.success) {
// The success flag is true so set the state
const user = newProps.userProfileStatus
this.setState({
email: user.email,
first_name: user.first_name,
})
}
else if (newProps.userProfileStatus.processing) {
// Api call is in progress so do action according to that like show loader etc.
}
}
componentDidMount() {
store.dispatch(getUserProfile())
}
render() {
return (
...
)
}
const mapStateToProps = (state) => {
return {
userProfileStatus: state.userProfileReducer,
}
}
Redux stores the state in the Redux store, separately from the React component state (think setState). You are almost there. What you need to do is guide the result data from the async dispatch to the redux store and then to your local component state. Steps 3 and 4 below.
Dispatch an async action to fetch the data.
Dispatch an action from within the promise to populate the redux state.
Write a reducer that intercepts the action and populates the redux state.
Connect your local component state with the redux state by using the connect function.

Resources