This repository has been archived by the owner on Oct 2, 2022. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #83 from shocknet/auth-preface
Auth preface
- Loading branch information
Showing
15 changed files
with
383 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Reducers | ||
|
||
## Guidelines | ||
|
||
- Define an state interface, named according to the reducer, e.g. `interface NodeState { foo: string }`. | ||
- Declare an initial state, of the state type, e.g: `const INITIAL_STATE: NodeState = { foo: 'baz' }`. | ||
- Import `AnyAction` from `redux`, this will be the type for the `action` the reducer accepts. | ||
- Either import the actions enum from an action creators file, e.g. `import { NODE_ACTIONS } from '../actions/NodeActions'` or use a single action creator's `.match()` method if it was created via `createAction` from `@reduxjs/toolkit`: | ||
|
||
```ts | ||
import { AnyAction } from "redux"; | ||
import { NODE_ACTIONS, setFoo } from "../actions/NodeActions"; | ||
// ... | ||
const node = (state: NodeState, action: ShockAction) => { | ||
try { | ||
if (action.type === NODE_ACTIONS.setFoo) { | ||
// foo will NOT be typed as string | ||
const { foo } = action.payload; | ||
return { ...state, foo }; | ||
} | ||
if (setFoo.match(action)) { | ||
// foo will be typed as string | ||
const { foo } = action.payload; | ||
return { ...state, foo }; | ||
} | ||
} catch (e) { | ||
logger.error(`Error inside Node reducer:`); | ||
logger.error(e); | ||
} | ||
}; | ||
``` | ||
|
||
- Prefer `immer` over homegrown immutable data, especially for complex updates, the resulting code will be cleaner/shorter/less buggier. Small example: | ||
|
||
```ts | ||
const node = produce((draft: NodeState, action: ShockAction) => { | ||
try { | ||
if (action.type === "setFoo") { | ||
draft.foo = action.data.foo; | ||
} | ||
} catch (e) { | ||
logger.error(`Error inside Node reducer:`); | ||
logger.error(e); | ||
} | ||
}, INITIAL_STATE); | ||
``` | ||
|
||
- Wrap every reducer's body in a try-catch. Inside the catch block, if the reducer can reasonably handle the error, then it should handle it, otherwise, return the current state, always log the error and pre-pend a `logger.error('Error inside X reducer:')` before logging the actual error. | ||
|
||
- Only one default export per file, the reducer, if something else "needs" to be exported, then it doesn't belong in a reducer file. E.g. typings should go into `shock-common`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Sagas | ||
|
||
Sagas are preferred over thunks because: | ||
|
||
- No overloading of `dispatch()`. | ||
- More expressive power. | ||
- Called on every store tick so they can react to state changes. | ||
|
||
## Guidelines | ||
|
||
- Sagas can import from services, actions and selectors but not from reducers. | ||
- Only one default export per file, a saga named according to the file name and scope of the code, e.g. `nodeSaga` in `node.ts`, if something else "needs" to be exported, then it doesn't belong in a saga file. E.g. in utils. That saga can then run other sagas as needed. Naming helps error traces. | ||
- Use `getStore()` from `./_store` and not from elsewhere. | ||
- In the future we should switch to channel events to avoid the `getStore()` conundrum. | ||
- Ping saga handles online state. | ||
- Build and connect sockets/polls when `Selectors.isReady(yield select())` returns true, disconnect them and tear them down when false, this simplifies token/user handling. | ||
- Call `_setStore()` on store creation but before running the root saga. | ||
- Wrap every single saga's body in a try-catch. Inside the catch block, if the saga can reasonably handle the error, then it should handle it, always log the error and pre-pend a `logger.error('Error inside [sagaName]* ()')` before logging the actual error. E.g.: | ||
|
||
```ts | ||
function* handlePing() { | ||
try { | ||
// ... | ||
} catch (e) { | ||
logger.error("Error inside handlePing* ()"); | ||
logger.error(e); | ||
} | ||
} | ||
``` | ||
|
||
- Do not do conditional dispatch based on state inside the saga, let the reducer handle the action as it seems fit, reducers are pure and are more easily testable, only check for conditions inside of those. | ||
- Use `Common.YieldReturn<T>` for type safety. | ||
- There's one exception to all of these rules and that is the `_store` file. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Store, AnyAction } from "redux"; | ||
|
||
import { State } from "../../reducers"; | ||
|
||
type ShockStore = Store<State, AnyAction>; | ||
|
||
let currStore = {} as ShockStore; | ||
|
||
export const _setStore = (store: ShockStore) => { | ||
currStore = store; | ||
}; | ||
|
||
export const getStore = () => currStore; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { all } from "redux-saga/effects"; | ||
|
||
import node from "./node"; | ||
import ping from "./ping"; | ||
|
||
import { _setStore } from "./_store"; | ||
|
||
function* rootSaga() { | ||
yield all([node, ping]); | ||
} | ||
|
||
export { _setStore }; | ||
export default rootSaga; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { all, takeEvery } from "redux-saga/effects"; | ||
|
||
import * as Utils from "../../utils"; | ||
|
||
// eslint-disable-next-line require-yield | ||
function* handleTokenInvalidation() { | ||
try { | ||
// Utils.navigate("/"); | ||
} catch (e) { | ||
Utils.logger.error(`Error inside handleTokenInvalidation* ()`); | ||
Utils.logger.error(e); | ||
} | ||
} | ||
|
||
function* auth() { | ||
try { | ||
yield; | ||
} catch (e) { | ||
Utils.logger.error("Error inside auth* ()"); | ||
Utils.logger.error(e); | ||
} | ||
} | ||
|
||
function* nodeSaga() { | ||
yield all([ | ||
auth, | ||
takeEvery("node/tokenDidInvalidate", handleTokenInvalidation) | ||
]); | ||
} | ||
|
||
export default nodeSaga; |
Oops, something went wrong.