Quickstart

A quick dive into getting started with Lore

Step 8: Save User in Context

In this step we're going to save the current user to context, so that any component in the application can easily retrieve it.

You can view the finished code for this step by checking out the authentication.8 branch of the completed project.

The Master Component

Inside the src/components folder is a component named Master. This component is intended to serve as a wrapper around your application, and has three main functions:

  1. Subscribe to the Redux store, so the application will re-render when the store changes
  2. Fetch any data that needs to be retrieved before the application is rendered
  3. Remove the loading screen once the application is ready to display to the user

In this step, we'll be focusing on the second function, and fetching the the current user before we render the main application.

You can learn more about the Master component here.

Fetch the Current User in Master

Let's start by using the connect decorator to fetch the current user when the application loads, and rendering a loading experience until we have it. Update the Master component to look like this:

// src/components/Master.js
import React from 'react';
import createReactClass from 'create-react-class';
import { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import RemoveLoadingScreen from './RemoveLoadingScreen';
import '../../assets/css/main.css';

export default connect(function(getState, props) {
  return {
    user: getState('currentUser')
  };
}, { subscribe: true })(
  createReactClass({
    displayName: 'Master',

    render() {
      const { user } = this.props;

      if (user.state === PayloadStates.FETCHING) {
        return (
          <div className="loader" />
        );
      }

      return (
        <div>
          <RemoveLoadingScreen />
          {React.cloneElement(this.props.children)}
        </div>
      );
    }

  })
);
// src/components/Master.js
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import RemoveLoadingScreen from './RemoveLoadingScreen';
import '../../assets/css/main.css';

class Master extends React.Component {

  render() {
    const { user } = this.props;

    if (user.state === PayloadStates.FETCHING) {
      return (
        <div className="loader" />
      );
    }

    return (
      <div>
        <RemoveLoadingScreen />
        {React.cloneElement(this.props.children)}
      </div>
    );
  }

}

Master.propTypes = {
  user: PropTypes.object.isRequired
};

export default connect(function(getState, props) {
  return {
    user: getState('currentUser')
  };
}, { subscribe: true })(Master);
// src/components/Master.js
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import RemoveLoadingScreen from './RemoveLoadingScreen';
import '../../assets/css/main.css';

@connect(function(getState, props) {
  return {
    user: getState('currentUser')
  };
}, { subscribe: true })
class Master extends React.Component {

  static propTypes = {
    user: PropTypes.object.isRequired
  };

  render() {
    const { user } = this.props;

    if (user.state === PayloadStates.FETCHING) {
      return (
        <div className="loader" />
      );
    }

    return (
      <div>
        <RemoveLoadingScreen />
        {React.cloneElement(this.props.children)}
      </div>
    );
  }

}

export default Master;

Save the User in Context

Next we're going to save the current user to context, so that any component that needs it can access it. Update the Master component to include the necessary fields:

// src/components/Master.js
...
createReactClass({
  ...

  propTypes: {
    user: PropTypes.object.isRequired
  },

  childContextTypes: {
    user: PropTypes.object
  },

  getChildContext() {
    return {
      user: this.props.user
    };
  },

  render() {
    ...
  }

})
// src/components/Master.js
...

class Master extends React.Component {

  getChildContext() {
    return {
      user: this.props.user
    };
  }

  ...

}

Master.propTypes = {
  user: PropTypes.object.isRequired
};

Master.childContextTypes = {
  user: PropTypes.object
};

...
// src/components/Master.js
...
class Master extends React.Component {

  static propTypes = {
    user: PropTypes.object.isRequired
  };

  static childContextTypes = {
    user: PropTypes.object
  };

  getChildContext() {
    return {
      user: this.props.user
    };
  }

  ...

}
...

In the code above we've added childContextTypes, to declare that an object named user should be made available to all child components in the application, and added a getChildContext() method that provides the value of that object, which is our current user.

Update Profile to Use Context

Next, open the Profile component and modify it to retrieve the current user from context instead of props:

// src/components/Profile.js
...

export default createReactClass({
  ...

  contextTypes: {
    user: PropTypes.object.isRequired
  },

  ...

  render() {
    const { user } = this.context;
    ...
  }

});
// src/components/Profile.js
...
class Profile extends React.Component {
  ...
  render() {
    const { user } = this.context;
    ...
  }
}

Profile.contextTypes = {
  user: PropTypes.object.isRequired
};

export default Profile;
// src/components/Profile.js
...
class Profile extends React.Component {

  static contextTypes = {
    user: PropTypes.object.isRequired
  };

  ...

  render() {
    const { user } = this.context;
    ...
  }
}

export default Profile;

At this point, we no longer need the getDefaultProps() method, so feel free to delete it.

Visual Check-in

If everything went well, your application should now look like this.

Code Changes

Below is a list of files modified during this step.

src/components/Master.js

import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import RemoveLoadingScreen from './RemoveLoadingScreen';
import '../../assets/css/main.css';

export default connect(function(getState, props) {
  return {
    user: getState('currentUser')
  };
}, { subscribe: true })(
  createReactClass({
    displayName: 'Master',

    propTypes: {
      user: PropTypes.object.isRequired
    },

    childContextTypes: {
      user: PropTypes.object
    },

    getChildContext() {
      return {
        user: this.props.user
      };
    },

    render() {
      const { user } = this.props;

      if (user.state === PayloadStates.FETCHING) {
        return (
          <div className="loader" />
        );
      }

      return (
        <div>
          <RemoveLoadingScreen />
          {React.cloneElement(this.props.children)}
        </div>
      );
    }

  })
);
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import RemoveLoadingScreen from './RemoveLoadingScreen';
import '../../assets/css/main.css';

class Master extends React.Component {

  getChildContext() {
    return {
      user: this.props.user
    };
  }

  render() {
    const { user } = this.props;

    if (user.state === PayloadStates.FETCHING) {
      return (
        <div className="loader" />
      );
    }

    return (
      <div>
        <RemoveLoadingScreen />
        {React.cloneElement(this.props.children)}
      </div>
    );
  }

}

Master.propTypes = {
  user: PropTypes.object.isRequired
};

Master.childContextTypes = {
  user: PropTypes.object
};

export default connect(function(getState, props) {
  return {
    user: getState('currentUser')
  };
}, { subscribe: true })(Master);
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import RemoveLoadingScreen from './RemoveLoadingScreen';
import '../../assets/css/main.css';

@connect(function(getState, props) {
  return {
    user: getState('currentUser')
  };
}, { subscribe: true })
class Master extends React.Component {

  static propTypes = {
    user: PropTypes.object.isRequired
  };

  static childContextTypes = {
    user: PropTypes.object
  };

  getChildContext() {
    return {
      user: this.props.user
    };
  }

  render() {
    const { user } = this.props;

    if (user.state === PayloadStates.FETCHING) {
      return (
        <div className="loader" />
      );
    }

    return (
      <div>
        <RemoveLoadingScreen />
        {React.cloneElement(this.props.children)}
      </div>
    );
  }

}

export default Master;

src/components/Profile.js

import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { Link } from 'react-router';

export default createReactClass({
  displayName: 'Profile',

  contextTypes: {
    user: PropTypes.object.isRequired
  },

  render() {
    const { user } = this.context;

    return (
      <div className="card profile">
        <div className="card-block">
          <img
            className="img-circle avatar"
            src={user.data.avatar} />
          <h4 className="card-title">
            Hi {user.data.nickname}!
          </h4>
          <div className="card-text">
            <p>You have permission to perform the following:</p>
            <ul className="permissions">
              <li>Create Tweets</li>
              <li>Edit your own tweets</li>
              <li>Delete your own tweets</li>
            </ul>
          </div>
          <Link className="btn btn-primary" to="/logout">
            Log out
          </Link>
        </div>
      </div>
    );
  }

});
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router';

class Profile extends React.Component {

  render() {
    const { user } = this.context;

    return (
      <div className="card profile">
        <div className="card-block">
          <img
            className="img-circle avatar"
            src={user.data.avatar} />
          <h4 className="card-title">
            Hi {user.data.nickname}!
          </h4>
          <div className="card-text">
            <p>You have permission to perform the following:</p>
            <ul className="permissions">
              <li>Create Tweets</li>
              <li>Edit your own tweets</li>
              <li>Delete your own tweets</li>
            </ul>
          </div>
          <Link className="btn btn-primary" to="/logout">
            Log out
          </Link>
        </div>
      </div>
    );
  }

}

Profile.contextTypes = {
  user: PropTypes.object.isRequired
};

export default Profile;
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router';

class Profile extends React.Component {

  static contextTypes = {
    user: PropTypes.object.isRequired
  };

  render() {
    const { user } = this.context;

    return (
      <div className="card profile">
        <div className="card-block">
          <img
            className="img-circle avatar"
            src={user.data.avatar} />
          <h4 className="card-title">
            Hi {user.data.nickname}!
          </h4>
          <div className="card-text">
            <p>You have permission to perform the following:</p>
            <ul className="permissions">
              <li>Create Tweets</li>
              <li>Edit your own tweets</li>
              <li>Delete your own tweets</li>
            </ul>
          </div>
          <Link className="btn btn-primary" to="/logout">
            Log out
          </Link>
        </div>
      </div>
    );
  }

}

export default Profile;

Next Steps

In the next section we're going to replace the mock server with a real server.