Создание компонента для обработки 500-x ошибок
Давайте создадим новую папку InternalServer
внутри папки ErrorPages
, а в ней создадим два новых файла и назовем их InternalServer.js
и InternalServer.css
:
Мы собираемся изменить файл InternalServer.js
:
import React from 'react';
import './InternalServer.css';
const internalServer = (props) => {
return (
<p className={'internalServer'}>{"500 SERVER ERROR, CONTACT ADMINISTRATOR!"}</p>
)
}
export default internalServer;
Затем изменим файл InternalServer.css
:
.internalServer{
font-weight: bold;
font-size: 50px;
text-align: center;
color: #c72d2d;
}
Далее, давайте изменим файл App.js
:
import InternalServer from '../components/ErrorPages/InternalServer/InternalServer';
<Route path="/" exact component={Home} />
<Route path="/owner-list" component={AsyncOwnerList} />
<Route path="/500" component={InternalServer} />
<Route path="*" component={NotFound} />
Превосходно.
Компонент для информации о 500-x ошибок готов, и мы можем продолжить.
Реализация Redux для обработки ошибок
Как и в случае с репозиторием в этом приложении, мы собираемся создать еще один экземпляр redux для обработки ошибок в одном месте нашего приложения. Мы уже немного знакомы с redux, поэтому эту часть будет довольно легко реализовать. Еще одна вещь заключается в том, что после создания нового файла-редьюсера у нас будет два файла-редьюсера, и, следовательно, нам нужно выполнить другой процесс регистрации внутри файла Index.js
.
Мы можем обрабатывать ошибки несколькими способами, и здесь мы собираемся сделать это, используя несколько редьюсеров в проекте. Более того, централизация действий по обработке ошибок в целом является хорошей практикой.
Давайте продолжим, изменив файл ActionTypes.js
, добавив три дополнительных типа действий:
export const HTTP_404_ERROR = 'HTTP_404_ERROR';
export const HTTP_500_ERROR = 'HTTP_500_ERROR';
export const HTTP_OTHER_ERROR = 'HTTP_OTHER_ERROR';
Внутри папки actions
находится файл repositoryActions.js
. Теперь внутри той же папки нам нужно добавить еще один файл действия errorHandlerActions.js
. Итак, давайте изменим этот файл:
import * as actionTypes from './actionTypes';
const execute404Handler = (props) => {
return {
type: actionTypes.HTTP_404_ERROR,
props: props
}
}
const execute500Handler = (props) => {
return {
type: actionTypes.HTTP_500_ERROR,
props: props
}
}
const executeOtherErrorHandler = (error) => {
return {
type: actionTypes.HTTP_OTHER_ERROR,
error: error
}
}
export const handleHTTPError = (error, props) => {
if (error.response.status === 404) {
return execute404Handler(props);
}
else if (error.response.status === 500) {
return execute500Handler(props);
}
else {
return executeOtherErrorHandler(error);
}
}
В приведенном выше коде мы экспортируем действие handleHTTPError
, в котором мы проверяем код состояния ошибки и выполняем соответствующую функцию. То же самое мы сделали с файлом repositoryActions.js
.
Внутри файла repositoryActions.js
нам нужно импортировать этот файл errorHandlerActions.js
:
import * as errorHandlerActions from './errorHandlerActions';
И чтобы заменить все комментарии в блоке catch каждой функции:
dispatch(errorHandlerActions.handleHTTPError(error, props));
Давайте продолжим, создав новый файл reducer errorHandlerReducer.js
внутри папки reducer.
Сейчас у нас есть такая структура папок:
Файл errorHandlerReducer.js
должен выглядеть так:
import * as actionTypes from '../actions/actionTypes';
const initialState = {
showErrorModal: false,
errorMessage: ''
}
const execute404 = (state, action) => {
action.props.history.push('/404');
return { ...state };
}
const execute500 = (state, action) => {
action.props.history.push('/500');
return { ...state };
}
const executeOtherError = (state, action) => {
return {
...state,
showErrorModal: true,
errorMessage: action.error.response.data
};
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.HTTP_404_ERROR:
return execute404(state, action);
case actionTypes.HTTP_500_ERROR:
return execute500(state, action);
case actionTypes.HTTP_OTHER_ERROR:
return executeOtherError(state, action);
default:
return state;
}
}
export default reducer;
Эта логика тоже знакома. Мы создаем объект состояния (initialState
), а затем функцию reducer
, которая принимает параметры state
и action
. Функция reducer
обновит state
на основе свойства type
, отправленного из файла errorHandlerActions
.
Регистрация нескольких редьюсеров
Чтобы завершить настройку Redux, давайте изменим файл Index.js
, чтобы также зарегистрировать этот редьюсер:
import repositoryReducer from './store/reducers/repositoryReducer';
import errorHandlerReducer from './store/reducers/errorHandlerReducer';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
const rootReducers = combineReducers({
repository: repositoryReducer,
errorHandler: errorHandlerReducer
})
const store = createStore(rootReducers, applyMiddleware(thunk));
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
registerServiceWorker();****
Мы импортируем файл errorHandlerReducer
и функцию combineReducers
. Затем с помощью функции combineReducers
мы создаем объект rootReducers
, который содержит все наши редьюсеры. Наконец, мы просто передаем этот объект в создание магазина.
Модификация компонента OwnerList
Нам нужно сделать еще одну вещь. В компоненте OwnerList нам нужно изменить способ настройки свойства data
из файла repositoryReducer
. Давайте изменим функцию mapStateToProps
:
const mapStateToProps = (state) => {
return {
data: state.repository.data
}
}
Мы должны это сделать, потому что у нас больше нет одного файла-редьюсера. Оба наших файла-редьюсера зарегистрированы внутри одного корневого объекта, и поэтому мы должны указать, какой редьюсер мы хотим использовать.
Мы можем попробовать нашу обработку ошибок, изменив код в методе сервера GetAllOwners
. В качестве первой строки кода мы можем добавить return NotFound()
или return StatusCode(500, “Some message”)
, и мы обязательно будем перенаправлены на правильную страницу ошибки.
Реализация компонента OwnerDetails
В этом компоненте мы собираемся показать отдельного владельца со всеми его учетными записями. Если вы думаете: «Ну, мы могли бы разделить это на две части», вы совершенно правы.
Мы и сделаем именно это.
Родительский компонент будет компонентом OwnerDetails, а дочерний компонент будет компонентом OwnersAccounts. Итак, давайте начнем с создания дочернего компонента.
Для компонента OwnersAccounts мы собираемся создать такую структуру:
Давайте изменим файл OwnersAccounts.js
:
import React from 'react';
import { Row, Col, Table } from 'react-bootstrap';
import Moment from 'react-moment';
const ownersAccounts = (props) => {
let accounts = null;
if (props.accounts) {
accounts = props.accounts.map(account => {
return (
<tr key={account.id}>
<td>{account.accountType}</td>
<td><Moment format="DD/MM/YYYY">{account.dateCreated}</Moment></td>
</tr>
);
})
}
return (
<Row>
<Col md={12}>
<Table responsive striped>
<thead>
<tr>
<th>Account type</th>
<th>Date created</th>
</tr>
</thead>
<tbody>
{accounts}
</tbody>
</Table>
</Col>
</Row>
)
}
export default ownersAccounts;
Давайте продолжим, создав структуру папок для компонента OwnerDetails:
Затем мы собираемся импортировать все необходимые файлы в компонент OwnerDetails:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Well, Row, Col } from 'react-bootstrap';
import * as repositoryActions from '../../../store/actions/repositoryActions';
import Moment from 'react-moment';
import OwnersAccounts from '../../../components/OwnerComponents/OwnersAccounts/OwnersAccounts';
import Aux from '../../../hoc/Auxiliary/Auxiliary';
После этого добавим реализацию компонента:
class OwnerDetails extends Component {
render() {
const owner = this.props.data;
return (
<Aux>
<Well>
<Row>
<Col md={3}>
<strong>Owner name:</strong>
</Col>
<Col md={3}>
{owner.name}
</Col>
</Row>
<Row>
<Col md={3}>
<strong>Date of birth:</strong>
</Col>
<Col md={3}>
<Moment format="DD/MM/YYYY">{owner.dateOfBirth}</Moment>
</Col>
</Row>
{this.renderTypeOfUserConditionally(owner)}
</Well>
<OwnersAccounts accounts={owner.accounts} />
</Aux>
)
}
}
export default OwnerDetails;
В приведенном выше коде мы используем оператор this.props.data
, но еще не реализовали редьюсер. Мы сделаем это через минуту. Обратите внимание на вызов функции renderTypeOfUserConditionally
. В этой функции мы условно обрабатываем данные владельца и возвращаем код JSX для отображения. Мы собираемся реализовать эту функцию через минуту. Ниже данных владельца мы отображаем все аккаунты, связанные с владельцем.
Условный рендеринг
Чтобы реализовать renderTypeOfUserConditionally
, нам нужно добавить следующий код над функцией render
, но все еще внутри класса:
renderTypeOfUserConditionally = (owner) => {
let typeOfUser = null;
if (owner.accounts && owner.accounts.length <= 2) {
typeOfUser = (
<Row>
<Col md={3}>
<strong>Type of user:</strong>
</Col>
<Col md={3}>
<span className={'text-success'}>Beginner user.</span>
</Col>
</Row>
);
}
else {
typeOfUser = (
<Row>
<Col md={3}>
<strong>Type of user:</strong>
</Col>
<Col md={3}>
<span className={'text-info'}>Advanced user.</span>
</Col>
</Row>
);
}
return typeOfUser;
}
Подключение к Redux
Наконец, давайте подключим этот компонент к файлу редьюсера, чтобы получить данные владельца под закрывающим тегом функции рендеринга:
const mapStateToProps = (state) => {
return {
data: state.repository.data
}
}
const mapDispatchToProps = (dispatch) => {
return {
onGetData: (url, props) => dispatch(repositoryActions.getData(url, props))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(OwnerDetails);
И над функцией renderTypeOfUserConditionally
добавьте этот хук жизненного цикла:
componentDidMount = () => {
let id = this.props.match.params.id;
let url = '/api/owner/' + id + '/account';
this.props.onGetData(url, { ...this.props })
}
Мы извлекаем id
из URL-адреса, вызывая оператор match.params.id
из объекта props
. Затем мы просто создаем наш URL-адрес и вызываем свойство onGetData
для получения данных с сервера.
Прежде чем проверять наш результат, мы должны добавить маршрут для этого нового компонента в файл App.js
, прямо под маршрутом owner-list
:
<Route path="/ownerDetails/:id" component={OwnerDetails} />
Наконец, мы можем проверить результат для начинающего пользователя:
Кроме того, давайте проверим результат для опытного пользователя:
Мы можем проверить эти результаты и убедиться, что все работает должным образом.
Заключение
На данный момент мы почти уверены, что вы поняли как работает Redux. Как мы уже говорили в предыдущих статьях, после некоторой практики вы сможете легко применять Redux, используя его собственный шаблон реализации. Мы использовали его для отправки HTTP-запросов и обработки HTTP-ошибок, а теперь вы можете использовать его для других задач, которые вы хотели бы решить.
Прочитав этот пост, вы узнали:
- Как реализовать логику обработки ошибок с помощью рабочего процесса Redux.
- Как объединить редьюсеры в один объект
- Как получить данные для подробного представления.
Спасибо, что прочитали статью, и я надеюсь, что вы нашли в ней что-то полезное.
В следующей части серии, мы узнаем, как использовать динамический ввод для создания форм. Кроме того, мы собираемся представить модальные компоненты и создать компонент для действий POST.