Thinkster
React
Creating Forms with React & Redux
  •  

Implementing Necessary Reducers, Middleware, and Higher-Level Components

Now that you're done with the registration form, let's implement a more complex form: the Settings form. The settings form lets you modify your profile picture, your username, your bio, your email address, and your password, as well as logout.

First off, let's add the ability to save the current user to agent.js. For ProductionReady, this takes the form of a put request.

Add a save function to src/agent.js
// ...

const Auth = {
  current: () =>
    requests.get('/user'),
  login: (email, password) =>
    requests.post('/users/login', { user: { email, password } }),
  register: (username, email, password) =>
    requests.post('/users', { user: { username, email, password } }),
  save: user =>
    requests.put('/user', { user })
};

// ...

Next, let's add a reducer to handle the settings view. In this case, you're not going to store the state of inputs in the redux store, so there's not going to be an 'UPDATE_FIELD' action like in the login and registration views.

Create the settings reducer in src/reducers/settings.js
export default (state = {}, action) => {
  switch (action.type) {
    case 'SETTINGS_SAVED':
      return {
        ...state,
        inProgress: false,
        errors: action.error ? action.payload.errors : null
      };
    case 'ASYNC_START':
      return {
        ...state,
        inProgress: true
      };
  }

  return state;
};

There's only 2 actions that this reducer will care about: 'ASYNC_START', which will fire when the user hits submit, and 'SETTINGS_SAVED', which will fire when the save request to the server completes. Everything else will be stored in the component itself. Let's tweak store.js to add this new reducer to our main reducer.

Update src/store.js
const reducer = combineReducers({
  auth,
  common,
  home,
  settings
});

Next up, let's add handlers for the logout button and the 'SETTINGS_SAVED' button to the common reducer.

Update src/reducers/common.js
// ...

export default (state = defaultState, action) => {
  switch (action.type) {
    // ...
    case 'REDIRECT':
      return { ...state, redirectTo: null };
    case 'LOGOUT':
      return { ...state, redirectTo: '/', token: null, currentUser: null };
    case 'SETTINGS_SAVED':
      return {
        ...state,
        redirectTo: action.error ? null : '/',
        currentUser: action.error ? null : action.payload.user
      };
    case 'LOGIN':
    // ...
  }
  return state;
};

Now, let's create a Settings component that will contain our form. First, let's write the high-level component.

Create the Settings component in src/components/Settings.js
import ListErrors from './ListErrors';
import React from 'react';
import { Link } from 'react-router';
import agent from '../agent';
import { connect } from 'react-redux';

const mapStateToProps = state => ({
  ...state.settings,
  currentUser: state.common.currentUser
});

const mapDispatchToProps = dispatch => ({
  onClickLogout: () => dispatch({ type: 'LOGOUT' }),
  onSubmitForm: user =>
    dispatch({ type: 'SETTINGS_SAVED', payload: agent.Auth.save(user) })
});

class Settings extends React.Component {
  render() {
    return (
      <div className="settings-page">
        <div className="container page">
          <div className="row">
            <div className="col-md-6 offset-md-3 col-xs-12">

              <h1 className="text-xs-center">Your Settings</h1>

              <ListErrors errors={this.props.errors}></ListErrors>

              <SettingsForm
                currentUser={this.props.currentUser}
                onSubmitForm={this.props.onSubmitForm} />

              <hr />

              <button
                className="btn btn-outline-danger"
                onClick={this.props.onClickLogout}>
                Or click here to logout.
              </button>

            </div>
          </div>
        </div>
      </div>
    );
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Settings);

This component can trigger the 2 events that we care about, 'SETTINGS_SAVED' and 'LOGIN'. However, this SettingsForm component is where the actual form and internal state will live.