Hello, friends.
It’s been a while since I completed my boot camp with Thinkful, and my journey started with FCC before that. I was applying for jobs, but realized I was getting rusty with code, so I’ve gone back to my full-stack React app to implement some stretch goals of mine. One was to create a message or graphic when the page is loading. Typically, with React, this only happens when you’re retrieving data.
I can’t seem to figure out how to go about adding this feature, and I can’t find anything online using just React-Redux.
Let’s walk through my login
component, action, and reducer. Maybe this will give a good idea for where I need to do this?
// login-form component
import React from 'react';
import { Field, reduxForm, focus } from 'redux-form';
import Input from './input';
import { login } from '../actions/auth';
import { required, nonEmpty } from '../validators';
import RegisterButton from './register-button';
import '../styles/login-form.css';
export class LoginForm extends React.Component {
onSubmit(values) {
return this.props.dispatch(login(values.username, values.password));
}
render() {
let error;
if (this.props.error) {
error = (
<div>
<p className="form-error" aria-live="polite">
{this.props.error}
</p>
</div>
);
}
return (
<form
className="login-form"
onSubmit={this.props.handleSubmit(values => this.onSubmit(values))}
>
{error}
<label htmlFor="username">Username</label>
<span className="demouser">Demo Username: demouser</span>
<Field
component={Input}
type="text"
name="username"
id="username"
validate={[required, nonEmpty]}
/>
<label htmlFor="password">Password</label>
<span className="demouser">Demo Password: password10</span>
<Field
component={Input}
type="password"
name="password"
id="password"
validate={[required, nonEmpty]}
/>
<button className="main-btn" disabled={this.props.pristine || this.props.submitting}>
Login
</button>
<RegisterButton />
</form>
);
}
}
export default reduxForm({
form: 'login',
onSubmitFail: (errors, dispatch) => dispatch(focus('login', 'username'))
})(LoginForm);
// auth action
import jwtDecode from 'jwt-decode';
import { SubmissionError } from 'redux-form';
import { API_BASE_URL } from '../config';
import { normalizeResponseErrors } from './utils';
import { saveAuthToken, clearAuthToken } from '../local-storage';
export const SET_AUTH_TOKEN = 'SET_AUTH_TOKEN';
export const setAuthToken = authToken => ({
type: SET_AUTH_TOKEN,
authToken
});
export const CLEAR_AUTH = 'CLEAR_AUTH';
export const clearAuth = () => ({
type: CLEAR_AUTH
});
export const AUTH_REQUEST = 'AUTH_REQUEST';
export const authRequest = () => ({
type: AUTH_REQUEST
});
export const AUTH_SUCCESS = 'AUTH_SUCCESS';
export const authSuccess = currentUser => ({
type: AUTH_SUCCESS,
currentUser
});
export const AUTH_ERROR = 'AUTH_ERROR';
export const authError = error => ({
type: AUTH_ERROR,
error
});
// Stores the auth token in state and localStorage, and decodes and stores
// the user data stored in the token
const storeAuthInfo = (authToken, dispatch) => {
const decodedToken = jwtDecode(authToken);
dispatch(setAuthToken(authToken));
dispatch(authSuccess(decodedToken.user));
saveAuthToken(authToken);
};
export const login = (username, password) => dispatch => {
dispatch(authRequest());
return (
fetch(`${API_BASE_URL}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
password
})
})
// Reject requests not 200 status, creating
// errors which follow a consistent format
.then(res => normalizeResponseErrors(res))
.then(res => res.json())
.then(({ authToken }) => storeAuthInfo(authToken, dispatch))
.catch(err => {
console.log('err from auth.js action:', err);
const { code } = err;
const message =
code === 401 ? 'Incorrect username or password' : 'Unable to login, please try again';
dispatch(authError(err));
// Could not authenticate, so return a SubmissionError for Redux Form
return Promise.reject(
new SubmissionError({
_error: message
})
);
})
);
};
export const refreshAuthToken = () => (dispatch, getState) => {
// set loading to true and clear earlier errors
dispatch(authRequest());
const authToken = getState().auth.authToken;
return fetch(`${API_BASE_URL}/auth/refresh`, {
method: 'POST',
headers: {
// Provide our existing token as credentials to get a new one
Authorization: `Bearer ${authToken}`
}
})
.then(res => normalizeResponseErrors(res))
.then(res => res.json())
.then(({ authToken }) => storeAuthInfo(authToken, dispatch))
.catch(err => {
// We couldn't get a refresh token because our current credentials
// are invalid or expired, or something else went wrong, so clear
// them and sign us out
dispatch(authError(err));
dispatch(clearAuth());
clearAuthToken(authToken);
});
};
// auth reducer
import {
SET_AUTH_TOKEN,
CLEAR_AUTH,
AUTH_REQUEST,
AUTH_SUCCESS,
AUTH_ERROR
} from '../actions/auth';
const initialState = {
isAuthenticated: false,
authToken: null, // authToken !== null does not mean it has been validated
currentUser: null,
loading: false,
error: null
};
export default function reducer(state = initialState, action) {
if (action.type === SET_AUTH_TOKEN) {
return Object.assign({}, state, {
authToken: action.authToken
});
} else if (action.type === CLEAR_AUTH) {
return Object.assign({}, state, {
isAuthenticated: false,
authToken: null,
currentUser: null
});
} else if (action.type === AUTH_REQUEST) {
return Object.assign({}, state, {
loading: true,
error: null
});
} else if (action.type === AUTH_SUCCESS) {
return Object.assign({}, state, {
isAuthenticated: true,
loading: false,
currentUser: action.currentUser // set state.auth.currentUser to user payload delivered from storeAuthInfo action
});
} else if (action.type === AUTH_ERROR) {
return Object.assign({}, state, {
loading: false,
error: action.error
});
}
return state;
}
// users action
import { SubmissionError } from 'redux-form';
import { API_BASE_URL } from '../config';
import { normalizeResponseErrors } from './utils';
export const registerUser = user => dispatch => {
return fetch(`${API_BASE_URL}/users`, {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(user)
})
.then(res => normalizeResponseErrors(res))
.then(res => res.json())
.catch(err => {
const { reason, message, location } = err;
if (reason === 'ValidationError') {
// Convert ValidationErrors into SubmissionErrors for Redux Form
return Promise.reject(
new SubmissionError({
[location]: message
})
);
}
});
};
Thanks in advance!