docs: renamed file and added redux framework file
This commit is contained in:
committed by
Hugo Häggmark
parent
f251345b68
commit
ed1b001904
+10
-7
@@ -1,4 +1,4 @@
|
||||
# Frontend Review Checklist
|
||||
# Pull Request Review Checklist
|
||||
|
||||
## High level checks
|
||||
|
||||
@@ -15,19 +15,22 @@
|
||||
- [ ] The pull request contains necessary link(s) to issue(s).
|
||||
- [ ] The pull request contains commits with commit messages that are small and understandable.
|
||||
- [ ] The pull request does not contain magic strings or numbers that could be replaced with an `Enum` or `const` instead.
|
||||
- [ ] The pull request does not increase the number of `implicit any` errors.
|
||||
- [ ] The pull request does not contain uses of `any` or `{}` that are unexplainable.
|
||||
- [ ] The pull request does not contain large React component that could easily be split into several smaller components.
|
||||
- [ ] The pull request does not contain back end calls directly from components, use actions and Redux instead.
|
||||
|
||||
### Bug specific checks
|
||||
|
||||
- [ ] The pull request contains only one commit if possible.
|
||||
- [ ] The pull request contains `closes: #Issue` or `fixes: #Issue` in pull request description.
|
||||
|
||||
## Frontend specific checks
|
||||
|
||||
- [ ] The pull request does not increase the number of `implicit any` errors.
|
||||
- [ ] The pull request does not contain uses of `any` or `{}` without comments describing why.
|
||||
- [ ] The pull request does not contain large React component that could easily be split into several smaller components.
|
||||
- [ ] The pull request does not contain back end calls directly from components, use actions and Redux instead.
|
||||
|
||||
### Redux specific checks (skip if pull request does not contain Redux changes)
|
||||
|
||||
- [ ] The pull request does not contain code that mutate state in reducers or thunks.
|
||||
- [ ] The pull request uses helpers `actionCreatorFactory` and `reducerFactory` instead of traditional `switch statement` reducers in Redux.
|
||||
- [ ] The pull request uses `reducerTester` to test reducers.
|
||||
- [ ] The pull request uses helpers `actionCreatorFactory` and `reducerFactory` instead of traditional `switch statement` reducers in Redux. ([Redux framework](https://github.com/grafana/grafana/blob/master/style_guides/redux.md))
|
||||
- [ ] The pull request uses `reducerTester` to test reducers.([Redux framework](https://github.com/grafana/grafana/blob/master/style_guides/redux.md))
|
||||
- [ ] The pull request does not contain code that access reducers state slice directly, instead the code uses state selectors to access state.
|
||||
@@ -0,0 +1,158 @@
|
||||
# Redux framework
|
||||
|
||||
To reduce the amount of boilerplate code used to create a strongly typed redux solution with actions, action creators, reducers and tests we've introduced a small framework around Redux.
|
||||
|
||||
`+` Much less boilerplate code
|
||||
`-` Non Redux standard api
|
||||
|
||||
## New core functionality
|
||||
|
||||
### actionCreatorFactory
|
||||
|
||||
Used to create an action creator with the following signature
|
||||
|
||||
```typescript
|
||||
{ type: string , (payload: T): {type: string; payload: T;} }
|
||||
```
|
||||
|
||||
where the `type` string will be ensured to be unique and `T` is the type supplied to the factory.
|
||||
|
||||
#### Example
|
||||
|
||||
```typescript
|
||||
export const someAction = actionCreatorFactory<string>('SOME_ACTION').create();
|
||||
|
||||
// later when dispatched
|
||||
someAction('this rocks!');
|
||||
```
|
||||
|
||||
```typescript
|
||||
// best practices, always use an interface as type
|
||||
interface SomeAction {
|
||||
data: string;
|
||||
}
|
||||
export const someAction = actionCreatorFactory<SomeAction>('SOME_ACTION').create();
|
||||
|
||||
// later when dispatched
|
||||
someAction({ data: 'best practices' });
|
||||
```
|
||||
|
||||
```typescript
|
||||
// declaring an action creator with a type string that has already been defined will throw
|
||||
export const someAction = actionCreatorFactory<string>('SOME_ACTION').create();
|
||||
export const theAction = actionCreatorFactory<string>('SOME_ACTION').create(); // will throw
|
||||
```
|
||||
|
||||
### noPayloadActionCreatorFactory
|
||||
|
||||
Used when you don't need to supply a payload for your action. Will create an action creator with the following signature
|
||||
|
||||
```typescript
|
||||
{ type: string , (): {type: string; payload: undefined;} }
|
||||
```
|
||||
|
||||
where the `type` string will be ensured to be unique.
|
||||
|
||||
#### Example
|
||||
|
||||
```typescript
|
||||
export const noPayloadAction = noPayloadActionCreatorFactory('NO_PAYLOAD').create();
|
||||
|
||||
// later when dispatched
|
||||
noPayloadAction();
|
||||
```
|
||||
|
||||
```typescript
|
||||
// declaring an action creator with a type string that has already been defined will throw
|
||||
export const noPayloadAction = noPayloadActionCreatorFactory('NO_PAYLOAD').create();
|
||||
export const noAction = noPayloadActionCreatorFactory('NO_PAYLOAD').create(); // will throw
|
||||
```
|
||||
|
||||
### reducerFactory
|
||||
|
||||
Fluent API used to create a reducer. (same as implementing the standard switch statement in Redux)
|
||||
|
||||
#### Example
|
||||
|
||||
```typescript
|
||||
interface ExampleReducerState {
|
||||
data: string[];
|
||||
}
|
||||
|
||||
const intialState: ExampleReducerState = { data: [] };
|
||||
|
||||
export const someAction = actionCreatorFactory<string>('SOME_ACTION').create();
|
||||
export const otherAction = actionCreatorFactory<string[]>('Other_ACTION').create();
|
||||
|
||||
export const exampleReducer = reducerFactory<ExampleReducerState>(intialState)
|
||||
// addMapper is the function that ties an action creator to a state change
|
||||
.addMapper({
|
||||
// action creator to filter out which mapper to use
|
||||
filter: someAction,
|
||||
// mapper function where the state change occurs
|
||||
mapper: (state, action) => ({ ...state, data: state.data.concat(action.payload) }),
|
||||
})
|
||||
// a developer can just chain addMapper functions until reducer is done
|
||||
.addMapper({
|
||||
filter: otherAction,
|
||||
mapper: (state, action) => ({ ...state, data: action.payload }),
|
||||
})
|
||||
.create(); // this will return the reducer
|
||||
```
|
||||
|
||||
#### Typing limitations
|
||||
|
||||
There is a challenge left with the mapper function that I can not solve with TypeScript. The signature of a mapper is
|
||||
|
||||
```typescript
|
||||
<State, Payload>(state: State, action: ActionOf<Payload>) => State;
|
||||
```
|
||||
|
||||
If you would to return an object that is not of the state type like the following mapper
|
||||
|
||||
```typescript
|
||||
mapper: (state, action) => ({ nonExistingProperty: ''}),
|
||||
```
|
||||
|
||||
Then you would receive the following compile error
|
||||
|
||||
```shell
|
||||
[ts] Property 'data' is missing in type '{ nonExistingProperty: string; }' but required in type 'ExampleReducerState'. [2741]
|
||||
```
|
||||
|
||||
But if you return an object that is spreading state and add a non existing property type like the following mapper
|
||||
|
||||
```typescript
|
||||
mapper: (state, action) => ({ ...state, nonExistingProperty: ''}),
|
||||
```
|
||||
|
||||
Then you would not receive any compile error.
|
||||
|
||||
If you want to make sure that never happens you can just supply the State type to the mapper callback like the following mapper:
|
||||
|
||||
```typescript
|
||||
mapper: (state, action): ExampleReducerState => ({ ...state, nonExistingProperty: 'kalle' }),
|
||||
```
|
||||
|
||||
Then you would receive the following compile error
|
||||
|
||||
```shell
|
||||
[ts]
|
||||
Type '{ nonExistingProperty: string; data: string[]; }' is not assignable to type 'ExampleReducerState'.
|
||||
Object literal may only specify known properties, and 'nonExistingProperty' does not exist in type 'ExampleReducerState'. [2322]
|
||||
```
|
||||
|
||||
## New test functionality
|
||||
|
||||
### reducerTester
|
||||
|
||||
Fluent API that simplifies the testing of reducers
|
||||
|
||||
#### Example
|
||||
|
||||
```typescript
|
||||
reducerTester()
|
||||
.givenReducer(someReducer, initialState)
|
||||
.whenActionIsDispatched(someAction('reducer tests'))
|
||||
.thenStateShouldEqual({ ...initialState, data: 'reducer tests' });
|
||||
```
|
||||
Reference in New Issue
Block a user