User Profile Page - Part 1

This video is available to view for members only.

Click here to Join!

Already a member?

Login


In this video we are going to start adding in a Profile page area to our application. This profile page should have two sections:

  • A profile area displaying the user's user name, and email address
  • A 'change password' form

There are quite a lot of things to be covered in adding this page, with one of the most important being that we don't want the Profile page to be available to users who are not logged in.

Before we can go ahead and secure the page, we firstly need to create it. This involves creating the container, along with adding in the appropriate route:

// src/containers/ProfilePage.js

import React, {PropTypes} from 'react';

class ProfilePage extends React.Component {
  render() {
    return (
      <div>
        hello
      </div>
    );
  }

}

ProfilePage.propTypes = {
};

export default ProfilePage;

At this stage we only want to ensure our page actually loads. We will make it function as expected as we go further through this process.

To get to this page we want our user's to browse to /profile, so let's add in the route now and hook it up to this new ProfilePage component:

// /src/routes.js

import ProfilePage from './containers/ProfilePage';

export default (
  <Route path="/" component={App}>
    <IndexRoute component={HomePage}/>
    <Route path="login" component={LoginPage}/>
    <Route path="logout" component={LogoutPage}/>

    <Route path="profile" component={ProfilePage}/>

    <Route path="about" component={AboutPage}/>
    <Route path="*" component={NotFoundPage}/>
  </Route>
);

I have stripped out the extra parts of this file simply to highlight the important lines. Click here for the full file.

With these two simple changes we should now have a working Profile page in our front end. Well, 'working' in a very basic sense at any rate.

Securing Our Profile Page

Likely there are as many ways to secure this page as there are JavaScript frameworks (or, ahem, view libraries).

The way in which I am going to secure this page is to use Redux Auth Wrapper.

Redux Auth Wrapper is a higher order component. There's nothing particularly scary about the concept of a higher order component. It is simply a function that is given a component as its argument, and returns a new component. This may make more sense when seen in code.

Firstly, let's add Redux Auth Wrapper to our project:

yarn add redux-auth-wrapper

Largely we will now borrow from the Redux Auth Wrapper documentation. Firstly, we need to import UserAuthWrapper, and routerActions.

Then, we will create a new variable to hold the partially applied function UserAuthWrapper. The function will be fully applied when given the component we wish to decorate. Again, easier to see:

// /src/routes.js

import {routerActions} from 'react-router-redux';
import {UserAuthWrapper} from 'redux-auth-wrapper';

import ProfilePage from './containers/ProfilePage';

// Redirects to /login by default
const UserIsAuthenticated = UserAuthWrapper({
  authSelector: state => state.auth, // how to get the user state
  predicate: (auth) => auth.isAuthenticated, // function to run against the auth state to determine if authenticated
  redirectAction: routerActions.replace, // the redux action to dispatch for redirect
  wrapperDisplayName: 'UserIsAuthenticated' // a nice name for this auth check
});

export default (
  <Route path="/" component={App}>
    <Route path="profile" component={UserIsAuthenticated(ProfilePage)}/>
  </Route>
);

Click here to see the full file.

Ok, so a lot happening here. Let's start with UserIsAuthenticated.

UserAuthWrapper is the function provided by the Redux Auth Wrapper library we imported. This function comes largely good to go, but needs a little extra help from us to become specific enough to work with our application.

In particular, we need to tell this function where to find the interesting part of our state - that which holds the 'key' as to whether our user is, or is not yet authenticated.

We've already sorted this - we will need to look inside state.auth.

Redux Auth Wrapper has a function called authSelector which will be called with our application's state, and all we need to do is return the part of our state that contains the auth object.

Next, we define a predicate. This piece is slightly different to the example from the documentation. A predicate is a fancy name for a function which returns true or false. As part of our authReducer we are already saving a key / value pair that contains exactly what we need - auth.isAuthenticated. Therefore, all we need to do is return this value. The reason this function is given auth as its only argument is that it gets called with the value from our authSelector. No magic here.

If the value of isAuthenticated returns false, they will be redirected to /login. We could override this, but that's a sensible default - see failureRedirectPath.

Now as mentioned, UserAuthWrapper is only partially applied. We've given it the configuration and stored the result into UserIsAuthenticated.

To fully invoke this function we need to call it with a second property - our component. The proper name for this would be the Decorated Component, as the Component is now decorated / wrapped by UserAuthWrapper.

As our router is responsible for calling the given component when any particular route is requested, all we need to do here is wrap the component we would normally call - ProfilePage in this case - with the UserIsAuthenticated function. And there we have it, a higher order component.

<Route path="profile" component={UserIsAuthenticated(ProfilePage)}/>

Don't just take my word for any of this though, be sure to read the code as there's plenty of cool and interesting things to learn here.

You can now use this same function to wrap / secure any pages you wish.

Implementing The Profile Page

At this point we can be sure that any users who are not logged in can no longer see our Profile Page (/profile).

This is particularly helpful as we need a way to trigger the retrieval of our user's profile data, and we don't want this process to occur if the user isn't logged in.

Whenever I first create a new component I usually start with a hardcoded version to ensure the component itself actually loads. This doesn't stop us from defining a simple stateless functional component for our user's Profile Area:

// src/components/ProfileArea.js

import React, {PropTypes} from 'react';

const ProfileArea = (props) => {
  return (
    <div>
      <h1>Profile for {props.username}</h1>

      <ul>
        <li>Email address: {props.emailAddress}</li>
      </ul>
    </div>
  )
};

ProfileArea.propTypes = {
  username: PropTypes.string.isRequired,
  emailAddress: PropTypes.string.isRequired
};

export default ProfileArea;

Hopefully by now nothing here is new or alarming to you. Even though we have defined two props, neither of these values need to be dynamic at this stage. We will use the ProfileArea component and simply pass in some static values.

import React, {PropTypes} from 'react';
import ProfileArea from '../components/ProfileArea';

class ProfilePage extends React.Component {
  render() {
    return (
      <div>
        <ProfileArea
          username="peter"
          emailAddress="peter@whatever.com"
        />
      </div>
    );
  }
}

ProfilePage.propTypes = {
};

export default ProfilePage;

At which point when visiting the /profile route we should see a slightly more interesting, if albeit totally faked profile page.

Getting Real Data

Now we have the foundations in place, the next step is to trigger the process of requesting our real user's profile data from our Symfony 3 API.

In order to do this what I want to have happen is that whenever this ProfilePage component is mounted (think: loaded), I want to trigger an API request to retrieve the profile data for the logged in user.

From the perspective of the component, all I need to do here is dispatch an action saying hey, can you please find me the profile information relating to this user ID? Muchas gracias.

By virtue of the architecure that we have in place, our action will be 'seen' by a watching Redux Saga, which in turn will handle the calling and resolution of a real API request, which will then be passed to a reducer, which in turn updates our application's state, which in turn will trigger an update of our component's props, which in turn will allow us to display the retrieved values.

Actually when writing that out, it sounds like a lot. But this process is repeated again and again throughout your application, and it becomes second nature to follow. And it also brings a really nice structure to any, and every request that your application makes. Personally I love it.

To kick start this process we must go ahead and connect our ProfilePage to our Redux Store:

// /src/containers/ProfilePage.js

import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import ProfileArea from '../components/ProfileArea';

class ProfilePage extends React.Component {

  componentDidMount() {
    const userId = this.props.pageState.auth.id;
  }

  render() {
    return (
      <div>
        <ProfileArea username="peter" emailAddress="peter@whatever.com"/>
      </div>
    );
  }

}

ProfilePage.propTypes = {
  dispatch: PropTypes.func.isRequired,
  pageState: PropTypes.object.isRequired
};

function mapStateToProps(state) {
  return {
    pageState: state
  }
}

export default connect(mapStateToProps)(ProfilePage);

Again, we've covered connecting components already so I'm not going to cover that again.

The important change here is that in connecting our component we have mapped the application's state to the props of this component, thereby allowing the componentDidMount function to grab the currently logged in user's ID, which we can then use to trigger the process of calling our API.

This process will need a new Saga, and a new Reducer. We will get started on making those pieces in the very next video.


Code For This Course

Get the code for this course.

Code For This Episode

Get the code for this episode.

Share This Episode

If you have found this video helpful, please consider sharing. I really appreciate it.


Episodes in this series

# Title Duration
1 App Walkthrough - User Experience 03:15
2 App Walkthrough - Developer Experience 07:41
3 Development Environment Setup 06:34
4 Login - Part 1 09:15
5 Login - Part 2 07:55
6 Login - Part 3 12:37
7 Login - Part 4 10:22
8 Login - Part 5 08:00
9 Saving Redux State to Local Storage 08:50
10 Logout 10:57
11 Adding an Auth-aware NavBar 14:43
12 Cleanup, Linting, and Login Form Styling 09:58
13 Showing Spinning Icons, Because Why Not? 08:11
14 More Robust Request Tracking 09:07
15 Getting Started Testing With Jest 06:43
16 Testing Request Reducer - Part 1 11:35
17 Testing Request Reducer - Part 2 05:25
18 Testing AuthSaga - Happy Path 09:19
19 Testing AuthSaga - Unhappy Paths 04:38
21 Testing JavaScript's Fetch with Jest - Happy Path 05:15
21 Testing JavaScript's Fetch with Jest - Unhappy Paths 04:35
22 Getting Started with Jest Mocks 08:52
23 Using Webpack Environment Variables in Jest Tests 09:37
24 User Profile Page - Part 1 07:31
25 User Profile Page - Part 2 10:25
26 User Profile Page - Part 3 07:23
27 Change Password - Part 1 10:01
28 Change Password - Part 2 07:59
29 Change Password - Part 3 - Displaying Errors 06:28
30 Change Password - Part 4 - Converting Errors From Symfony to Redux Form 05:39
31 Change Password - Part 5 - Adding More Tests 05:06
32 Change Password - Part 6 - Avoid Blocking, and Wrap Up 06:23
33 Registration - Part 1 08:50
34 Registration - Part 2 06:25
35 Registration - Part 3 05:25