We are going to use the same endpoint to list users and search users. Searching users is similar to listing users with search queries. To list users, follow these steps:
- Define Saga. The Saga file is placed inside app/containers/User/saga.js. Here, we have only included the Saga code to create a user. The rest of the Saga code can be found inside CH06/app/containers/User/saga.js. It contains Saga for searching the user, removing a user, updating a user, creating a user, and checking a user's details:
import request from 'utils/request';
import qs from 'query-string';
import { notification } from 'antd';
import { call, put, takeLatest } from 'redux-saga/effects';
import {
USER_CREATE_REQUEST
} from './constants';
import {
onCreateSuccess,
onCreateFailure,
} from './actions';
export function* onCreateRequest(action) {
try {
const { success, user, message } = yield call(request, `/api/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ user: action.item }),
});
if (!success) {
throw message;
}
notification.success({
message: 'Create user ssucessfully',
description: `${user.name} was created`,
});
action.cb && action.cb(user);
yield put(onCreateSuccess(user));
} catch (err) {
notification.error({
message: 'Create user unssucessfully',
description: err.toString(),
});
yield put(onCreateFailure(err.toString()));
}
}
export default function* data() {
yield takeLatest(USER_CREATE_REQUEST, onCreateRequest);
}
- Define reducers. The reducer file to search, remove, update and search users can be found in CH06/app/containers/User/reducer.js. In the next chapter, we are going to check how to debug each of the requests.
- Inject the reducers and the Saga to the root user container:
import React from 'react';
import { compose } from 'redux';
import { Switch, Route } from 'react-router-dom';
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
import saga from './saga';
import reducer from './reducer';
import All from './All';
import AddUser from './Add';
import EditUser from './Edit';
const User = () => (
<Switch>
<Route exact path="/" component={All} />
<Route exact path="/users/add" component={AddUser} />
<Route path="/users/:id/edit" component={EditUser} />
</Switch>
);
const withSaga = injectSaga({ key: 'user', saga });
const withReducer = injectReducer({ key: 'user', reducer });
export default compose(
withSaga,
withReducer,
)(User);
- Connect the User container with Redux. The container file is updated to container code snippet, given as follows:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import qs from 'query-string';
import styled from 'styled-components';
import { Pagination } from 'antd';
import { createStructuredSelector } from 'reselect';
import {
getUsers,
getTotalPage,
getTotalItem,
getCurrentPage,
getDeleteItem,
} from './selectors';
import Form from './Form';
import List from './List';
import { onSearchRequest, onRemoveRequest } from './actions';
const PaginationView = styled.div`
margin: 40px 0;
`;
class User extends Component {
componentDidMount() {
this.props.onSubmit(qs.parse(this.props.location.search));
}
componentWillReceiveProps(nextProps) {
const newProps = qs.parse(nextProps.location.search);
const oldProps = qs.parse(this.props.location.search);
if (
oldProps.s !== newProps.s ||
oldProps.page !== newProps.page ||
nextProps.deleting !== this.props.deleting
) {
this.props.onSubmit(newProps);
}
}
onSubmit = e => {
this.props.history.push({
search: qs.stringify({
...qs.parse(this.props.location.search),
...e.toJS(),
page: 1,
}),
pathname: this.props.history.location.pathname,
});
};
onChange = page => {
this.props.history.push({
search: qs.stringify({
...qs.parse(this.props.location.search),
page,
}),
pathname: this.props.history.location.pathname,
});
};
render() {
const { results, totalPage, totalItem, currentPage, location } = this.props;
const newProps = qs.parse(location.search);
return (
<div className="all-user-containers">
<Form onSubmit={this.onSubmit} initialValues={newProps} />
<List
dataSource={results}
keyword={newProps.s}
onRemove={this.props.onRemove}
onReload={() => this.props.onSubmit(newProps)}
/>
<PaginationView>
{totalPage > 0 && (
<Pagination
style={{ marginTop: 10 }}
current={currentPage + 1}
total={totalItem}
pageSize={10}
onChange={this.onChange}
/>
)}
</PaginationView>
</div>
);
}
}
export const mapStateToProps = createStructuredSelector({
results: getUsers(),
totalPage: getTotalPage(),
totalItem: getTotalItem(),
deleting: getDeleteItem(),
currentPage: getCurrentPage(),
});
export const mapDispatchToProps = dispatch => ({
onSubmit: s => dispatch(onSearchRequest(s)),
onRemove: s => dispatch(onRemoveRequest(s)),
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(User);
- Connect the AddUser component to Redux:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import Form from './UserForm';
import { onCreateRequest } from './actions';
class AddUser extends Component {
onSubmit = e => {
this.props.onCreate(e.toJS(), () => this.props.history.push('/'));
};
render() {
return (
<div className="add-user-containers">
<Form
isNew
onSubmit={this.onSubmit}
initialValues={{ role: 'user', gender: 'male' }}
caption="Add New User"
/>
</div>
);
}
}
AddUser.propTypes = {
onCreate: PropTypes.func.isRequired,
};
export const mapDispatchToProps = dispatch => ({
onCreate: (item, cb) => dispatch(onCreateRequest(item, cb)),
});
export default withRouter(
connect(
null,
mapDispatchToProps,
)(AddUser),
);
- Connect the Edit User page to Redux:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { createStructuredSelector } from 'reselect';
import Form from './UserForm';
import { onUpdateRequest, onDetailRequest } from './actions';
import { getUser } from './selectors';
class EditUser extends Component {
componentDidMount() {
this.props.onFetch(this.props.match.params.id);
}
componentWillReceiveProps(nextProps) {
if (this.props.match.params.id !== nextProps.match.params.id) {
this.props.onFetch(nextProps.match.params.id);
}
}
onSubmit = e => {
this.props.onUpdate(this.props.match.params.id, e.toJS(), () =>
this.props.history.push('/'),
);
};
render() {
const { user } = this.props;
return (
<div className="add-user-containers">
<Form
initialValues={user}
onSubmit={this.onSubmit}
caption="Edit User"
/>
</div>
);
}
}
EditUser.propTypes = {
user: PropTypes.object,
onFetch: PropTypes.func.isRequired,
onUpdate: PropTypes.func.isRequired,
};
export const mapStateToProps = createStructuredSelector({
user: getUser(),
});
export const mapDispatchToProps = dispatch => ({
onFetch: id => dispatch(onDetailRequest(id)),
onUpdate: (id, item, cb) => dispatch(onUpdateRequest(id, item, cb)),
});
export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps,
)(EditUser),
);
This is should give us a working application in which we can add a new user, delete a user, edit a user, and view the details of a user.