pik-react-utils
v0.6.0
Published
Put a description here
Downloads
17
Keywords
Readme
Pik react utils
Todo
- ~~Entities should work with Proxies to get thier fields automaticly~~
- ~~Normalization proccess with _uid and _type~~
- ~~Typescript~~
- ~~Add API and store functions for delete/update/create entities~~
- ~~Entities typings~~
- ~~Css modules/Styled components~~
- ~~Tests~~
- ~~Sentry implementation~~
- ~~Oauth~~
- ~~Error handling~~
- Write doc about necessary settings to use that package
Dev guide
Quiсk start
npm i pik-react-utils
Settings
Table of contents
Utils
Basic
- pikUtilsConfig - function to add core utils configuration (sentry, api and authService configuration)
Regular
- factoryEntityGetter — function to create MobX computed entity getters
- factoryEntitiesGetter — function to create MobX computed entities getters
- bind, bindArgs - cache result of global.bind function
oAuth utils
- authService - implements specific
IAuthService
interface methodsauthorize(), handleAuthError(), prepareApiRequest()
, store@computed isAuthenticated: bool
Components
- PikContainer — it's necessary to use this container as container of the app
- Spinner — loading indicator component (deprecated)
- PikAuthorization -- wrapper to route containers, should be used when we need to enable oAuth onto specific route
Stuff
- BaseEntity, BaseValue, List — general types
- api — configurable api
- stores — stores
Tips
- All configs should be in entry point file of your application
API
Основные идеи
Стандартное API покрывает только стандартные кейсы. В случае кастомных запросов (например, для отправки данных сторонним сервисам, или нашим endpoint-ам с особой логикой, загрузки файлов и т.п.) нужно реализовывать это в проекте, а если это становится стандартным запросом, то выносить в новые методы для работы с API.
По договоренности с бэкэндом мы всегда знаем куда и как нужно посылать стандартные запросы. Например на GET, POST, PATCH какой-то сущности.
Не делать повторные запросы, если мы ожидаем ответа сервера по такому же запросу.
Работа с entitiesStore
Основные идеи
Разработчик может абстрагироваться от получения и хранения данных, апи-запросов, асинхронности, нормализации и прочих вспомогательных операций.
Хранилище само получает данные от сервера при необходимости.
Хранилище не делает лишних и повторных запросов.
Можно использовать методы получения данных в рендере.
Нужно кэшировать результаты запросов.
Данные полученные от сервера нормализуются и хранятся в единственном экземпляре.
Изменения в хранилище приведут к автоматической перерисове компонентов.
Параметры методов
getPromise: boolean
— получить промис как результат работы метода, на его reslove или reject можно завязаться, если нужно сделать какие-то операции после завершения запроса.sync: boolean
— отправить результат на серверapi: boolean
- отправить результат на сервер (нужно избавиться от параметра sync или api). Сейчас используется в createEntityafter: boolean
- все действия в сторе предшествуют отправке соответствующих апи-запросов, after меняет порядок на обратный — сначала запрос, и после reslove произойдут изменения в хранилище.log: boolean
(deprecated) - логировать изменения сущности, тогда только изменненные поля будут отправлены на сервер. Есть в старой версии утилит.
Принципы работы
Похож на принцип работы createFetcher из выступления Дэна Абрамова.
Когда используется метод getEntity/getList мы получаем либо то что хранится в хранилище, либо undefined.
Если мы получили undefined, то хранилище автоматически сделает запрос, чтобы получить необходимые данные от API.
Полученные данные нормализуются, чтобы храниться в единственном экземпляре.
В хранилище можно положить любые нормализуемые данные, если использовать функцию normalizeResult. Таким образом можно отделить логику запросов (особенно для нестандартных запросов) от хранения данных.
Когда данные в хранилище появятся/обновятся/удалятся, то использующие эти данные getEntity/getList в рендерах, заставят нужный компонент перерисоваться с актуальными данными.
Z. Бонус. Когда пользователь запрашивает свойство сущности, которого еще нет в хранилище, но которое там потенциально может быть, будет сделан запрос на получение полной сущности.
How not to use
Это не хелпер по созданию апи-запросов.
Если нужно сдедать запрос на удаление, создание или изменение того что не лежит в хранилище используйте другие утилиты.
Централизованная обработка ошибок не входит в текущую версию entitiesStore
How to use
- Приложение должно обрабатывать ситуации, когда данных нет в хранилише, и когда они есть. Приложение подписывается на обновления данных в хранилище и реагирует на изменения. Это достигает использованием двух методов entitiesStore.getEntity и entitiesStore.getList и декоратором observer из mobx-react.
const EmployeeList = inject(ENTIRIES_STORE)(observer(({ [ENTIRIES_STORE]: store }) => {
const employeeList = store.getList("employee")
if (!list) {
return <Spinner />
}
return (
<>
{employeeList.entities.map(employee => <Employee data={employee} key={employee._uid} />)}
</>
)
}))
const Employee = observer(({ data }) => (
<div>
{data.firstName} {data.lastName}
</div>
)
Здесь мы говорим, что хотим использовать данные списка сотрудников для отображения компонента. Если данных нет, выводим спиннер. Если они есть выводим данные. Если данных, сначала не было а потом они появились, то стриггерится ререндер и мы выведем список. Если изменятся данные о каком-то сотруднике, то мы это отобразим. Если вдруг данные о сотруднике или весь список инвалидируется компонент это обработает.
- Когда на основании данных нам нужно сделать вычисления для вывода, то мы делаем это в
@computed
и используем результат в рендере. В рендере вычисления мы не делаем, чтобы не повторять вычисления при перерисовках, и чтобы не страдала читабельность.
@inject(ENTIRIES_STORE)
@observer
class EmployeeList extends Component {
@computed get employeeList() {
const { [ENTIRIES_STORE]: store } = this.props
return store.getList("employee")
}
@computed get employers() {
if (!this.employeeList) {
return []
}
const companies = new Set()
this.employeeList.entities.map(({ employer }) => {
if (employer) {
companies.add(employer)
}
})
return companies
}
render() {
return (
<>
{this.employers.map(company => <Company data={company} key={company._uid} />)}
</>
)
}
}))
- Создать сущность на сервере и положить результат в хранилище, обработать результат валидации на сервере:
@inject(ENTIRIES_STORE)
@observer
class CreateEmployee extends Component {
@observable formData = { first_name: "", last_name: "" }
@observable errors = { first_name: [], last_name: [] }
@action.bound
clearValidationErrors() {
Object.keys(this.errors).forEach(key => this.errors[key] = "")
}
@action.bound
setValidationErrors(detail) {
Object.keys(detail)
.forEach(key => this.errors[key] = detail[key].message)
}
handleSubmit() {
const { [ENTIRIES_STORE]: store } = this.props
const promise = api.createEntity({ ...this.formData, _type: "employee" })
promise
.then(action((response) => {
store.normalizeResult(response)
this.clearValidationErrors()
))
.catch(({ detail }) => this.setValidationErrors(detail))
}
// ...
}))
Работа с oAuth
Основные идеи
- Быстрая настройка, легкое использование
- Абстракция от имплементации необходимого API для авторизации/аутентефикации пользователя
- authService имплементирует механизмы продления/получения bearer токена
- Собственная имплементация (при необходимости) IAuthService для изменения механизма авторизации
Принцип работы
configuration
- Разработчик передает
authUrl: string
иclientId: number
вapi.config({...})
api.config()
валидирует полученные аргументы, в случае еслиauthService: IAuthService === undefined
используется стандартный сервис авторизации
api Usage
- При каждом вызове
request()
получаем токен изsessionManager.sessionData
, используем полученный токен для добавления заголовка{ authorization: `Bearer pik ${token}` }
- Обрабатываем каждый ответ, в случае если
responseCode === 403
, вызываем методthis.authService.authorize()
application Usage
- Применяя механизм авторизации для уникального роута разработчику необходимо передать контейнер в функцию
PikAuthorization()
и вернуть результат вызова вRoute.component
соответствующий уникальному роуту - При переходе на уникальный роут
PikAuthorization()
валидируетapi.authService.isAuthenticated
, если возвращается true, то запускается механизм отрисовки компонента соответствующего уникальному роуту, в противном случае вызываетсяapi.authService.authorize()
How to use
- Import and configure api
import { api } from "pik-react-utils"
const appParams = {
authUrl: `http://yourathrerviceuri.com`,
clientId: 3939323,
}
const axiosConfig = {
withCredentials: false,
}
// with default authService
api.config({ appParams, axiosConfig })
// with custom authService
const myAwesomeAuthService: IAuthService = new myAwesomeAuthService()
api.config({ appParams, axiosConfig, authService: myAwesomeAuthService})
- Import PikAuthorization and enable oAuth token validation to specific route
import { PikAuthorization } from "pik-react-utils"
export default class AppRouter extends PureComponent {
render() {
return (
<Router>
<Switch>
{/*your code here*/}
<Route
exact={true}
path={`${BASE_PATH}${COMPANIES_LIST}`}
component={PikAuthorization(CompaniesList)}
/>
{/*your code here*/}
</Switch>
</Router>
)
}
}