We now have two states that may be used across app. 1) User, specifically user sign in / out state; 2) User profile. It is time to introduce state management tool.
State management tool essentially is a pub-sub system with or without a mutable or immutable store. Redux is one of the most popular options. Let's integrate Amplify with Redux.
- 1. Install Redux
- 2. Redux Files
- 3. Amplify Bridge
- 4. Listen to Redux Store
- 5. Dispatch to Redux Store
- 6. Run App
npm install --save redux
Then create a store
module with Redux. We build these files:
└── store
├── actions.js
├── index.js
└── reducers.js
actions.js
defines Redux Actions. So far we have three actions,
const SWITCH_USER = 'SWITCH_USER';
const UPDATE_PROFILE = 'UPDATE_PROFILE';
const DELETE_PROFILE = 'DELETE_PROFILE';
// when user sign in / out
function switchUser(user) {
return {
type: SWITCH_USER,
user
}
}
// when user update profile
function updateProfile(profile) {
return {
type: UPDATE_PROFILE,
profile
}
}
// when user sign out
function deleteProfile() {
return { type: DELETE_PROFILE }
}
export { SWITCH_USER, UPDATE_PROFILE, DELETE_PROFILE }
export { switchUser, updateProfile, deleteProfile }
reducers.js
defines Redux Reducers. Let's build two reducers, then combine them into 'Journal'.
Notice we don't use Object.assign
in switchUser
reducer like normal Redux examples. That was intentional. Becuase Object.assign
does not copy methods, which we need later.
import { combineReducers } from 'redux';
import { SWITCH_USER, UPDATE_PROFILE, DELETE_PROFILE } from './actions';
function user(state={}, action) {
switch(action.type) {
case SWITCH_USER:
return action.user;
default:
return state;
}
}
function profile(state={}, action) {
switch(action.type) {
case UPDATE_PROFILE:
return Object.assign(
{},
state,
action.profile
);
case DELETE_PROFILE:
return null;
default:
return state;
}
}
const Journal = combineReducers({
user,
profile
});
export default Journal;
index.js
create and export a Redux Store.
import { createStore } from 'redux';
import Journal from './reducers';
const store = createStore(Journal);
export default store;
Then we create AmplifyBridge.js
to link Amplify Hub
with Redux.
└── store
└── AmplifyBridge.js
export default class AmplifyBridge {
constructor(store) {
this.store = store;
this.onHubCapsule = this.onHubCapsule.bind(this);
Hub.listen('auth', this, 'AmplifyBridge'); // Add this component as a listener of auth events.
this.checkUser(); // first check
}
onHubCapsule(capsule) {
this.checkUser(); // triggered every time user sign in / out
}
checkUser() {
Auth.currentAuthenticatedUser()
.then(user => {
this.store.dispatch(switchUser(user));
this.loadProfile(user);
})
.catch(err => {
this.store.dispatch(switchUser(null));
this.store.dispatch(deleteProfile());
});
}
loadProfile() {
Auth.userAttributes(user)
.then(data => {
const profile = this.translateAttributes(data);
this.store.dispatch(updateProfile(profile));
})
.catch(err => this.store.dispatch(deleteProfile(err)));
}
}
Then we just create the bridge at src/App.js
import store, { AmplifyBridge } from './store';
new AmplifyBridge(store);
Now everytime user sign in / out, Redux store is updated with user and profile. Across app we just need to subscribe
to the store.
In src/components/Navigator.jsx
we can do some clean up now.
constructor(props) {
super(props);
this.storeListener = this.storeListener.bind(this);
this.state = { user: null, profile: null }
}
componentDidMount() {
this.unsubscribeStore = store.subscribe(this.storeListener);
}
componentWillUnmount() {
this.unsubscribeStore();
}
storeListener() {
logger.info('redux notification');
const state = store.getState();
this.setState({ user: state.user, profile: state.profile });
}
Since we have profile too, small update to greetings in <Navigator>
{ user? 'Hi ' + (profile.given_name || user.username) : 'Please sign in' }
Things are getting easier and cleaner.
Then do the same to src/components/Main.jsx
We pipe sign in / out events from Amplify Hub
to Redux. User profile changes have to be dispatched to store manually.
saveSuccess(data) {
logger.info('saved user profile', data);
store.dispatch(updateProfile(this.state.profile));
}
Now run app, update first name and notice greeting changes right after you click 'Save'
npm start