Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add the first How Tos. * Uncomment imports in example.
- Loading branch information
Showing
8 changed files
with
408 additions
and
214 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,5 +3,6 @@ | |
"line_length": false, | ||
"MD024": { | ||
"siblings_only": true | ||
} | ||
} | ||
}, | ||
"MD014": false | ||
} |
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 |
---|---|---|
|
@@ -319,6 +319,8 @@ dynamike | |
fillable | ||
symlinked | ||
dp3-engineering | ||
ripgrep | ||
entr | ||
github.com | ||
namsral | ||
float64 | ||
|
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,176 @@ | ||
# How To Call Swagger Endpoints from React | ||
|
||
## 1. Verify the Schema is Defined | ||
|
||
For each model type returned by the backend, there needs to be an `Entity` defined and exported in `src/shared/Entities/schema.js`. | ||
|
||
Here is the definition for `Shipment`: | ||
|
||
```javascript | ||
export const shipment = new schema.Entity('shipments'); | ||
|
||
// add any embedded objects that should be extracted during normalization | ||
shipment.define({ | ||
pickup_address: address, | ||
secondary_pickup_address: address, | ||
delivery_address: address, | ||
partial_sit_delivery_address: address, | ||
}); | ||
|
||
export const shipments = new schema.Array(shipment); | ||
``` | ||
|
||
## 2. Call the Swagger Operation | ||
|
||
Add a function to `src/shared/Entities/modules/$MODEL.js` that calls the `operationId` defined in the | ||
swagger YAML. Action creator functions should take a `label` argument, which will be used to allow the calling component to determine the status of any requests with that label. | ||
|
||
`swaggerRequest` returns a promise, so it is possible to chain behavior onto its result, for example to perform a few requests in sequence. | ||
|
||
```javascript | ||
import { swaggerRequest } from 'shared/Swagger/request'; | ||
import { getClient } from 'shared/Swagger/api'; | ||
|
||
export function getShipment(label, shipmentId, moveId) { | ||
return swaggerRequest( | ||
getClient, // function returning a promise that will resolve to a Swagger client instance | ||
'shipments.getShipment', // what operation to perform, including tag namespace | ||
{ moveId, shipmentId }, // parameters to pass to the operation | ||
{ label }, // optional params for swaggerRequest, such as label | ||
); | ||
} | ||
``` | ||
|
||
By directing all Swagger Client calls through the `swaggerRequest` function, we can have a centralized place to manage how to track | ||
the lifecycle of the request. This allows us to dispatch actions to Redux that represent these events, currently `@@swagger/${operation}/START`, `@@swagger/${operation}/SUCCESS` and `@@swagger/${operation}/FAILURE`. These actions will appear in the Redux debugger along with any other state changes. | ||
|
||
## 3. Dispatch an Action when Component Mounts | ||
|
||
The following pattern, using `onDidMount`, allows the data fetching to be defined outside the component. | ||
|
||
```javascript | ||
export class ShipmentDisplay extends Component { | ||
|
||
componentDidMount() { | ||
this.props.onDidMount && this.props.onDidMount(); | ||
} | ||
|
||
render { | ||
const { shipment } = this.props; | ||
|
||
return ( | ||
<div> | ||
<p>You are moving on { shipment.requested_move_date }.</p> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
ShipmentDisplay.propTypes = { | ||
shipmentID: PropTypes.string.isRequired, | ||
|
||
onDidMount: PropTypes.function, | ||
shipment: PropTypes.object, | ||
}; | ||
|
||
const requestLabel = 'ShipmentDisplay.getShipment'; | ||
|
||
function mapDispatchToProps(dispatch, ownProps) { | ||
return { | ||
onDidMount: function() { | ||
dispatch(getShipment(requestLabel, ownProps.shipmentID)); } | ||
}; | ||
} | ||
|
||
function mapStateToProps(state, ownProps) { | ||
return { | ||
shipment: selectShipment(ownProps.shipmentID), | ||
}; | ||
} | ||
|
||
export default connect(mapStateToProps, mapDispatchToProps)(ShipmentDisplay); | ||
``` | ||
|
||
If you need to load data based on a value that isn't passed in as a `prop`, it's best to embed another component and pass that value into it as a `prop`. This can be thought of as an extension of the container pattern. | ||
|
||
## 4. Use a Selector to Access the Data | ||
|
||
All data access should be done through selectors and not by directly accessing the global Redux state. | ||
|
||
Add a function to `src/shared/Entities/modules/$MODEL.js` that returns the value from Redux. This example uses `denormalize`: | ||
|
||
```javascript | ||
// Return a shipment identified by its ID | ||
export function selectShipment(state, id) { | ||
if (!id) { | ||
return null; | ||
} | ||
return denormalize([id], shipments, state.entities)[0]; | ||
} | ||
``` | ||
|
||
This one returns a value that doesn't need `denormalize`: | ||
|
||
```javascript | ||
// Return a shipment identified by its ID | ||
export function selectShipment(state, id) { | ||
if (!id) { | ||
return null; | ||
} | ||
return get(state, `entities.shipments.${id}`); | ||
} | ||
``` | ||
|
||
## 5. Handle Fetch Errors | ||
|
||
The `lastError` selector provides access to the last error for a specified request label. | ||
|
||
```javascript | ||
import { lastError } from 'shared/Swagger/selectors'; | ||
|
||
export class ShipmentDisplay extends Component { | ||
|
||
componentDidMount() { | ||
this.props.onDidMount && this.props.onDidMount(); | ||
} | ||
|
||
render { | ||
const { shipment, error } = this.props; | ||
|
||
return ( | ||
{ error && <p>An error has occurred.</p> } | ||
|
||
<div> | ||
<p>You are moving on { shipment.requested_move_date }.</p> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
ShipmentDisplay.propTypes = { | ||
shipmentID: PropTypes.string.isRequired, | ||
|
||
onDidMount: PropTypes.function, | ||
shipment: PropTypes.object, | ||
error: PropTypes.object, | ||
}; | ||
|
||
const requestLabel = 'ShipmentDisplay.getShipment'; | ||
|
||
function mapDispatchToProps(dispatch, ownProps) { | ||
return { | ||
onDidMount: function() { | ||
dispatch(getShipment(requestLabel, ownProps.shipmentID)); | ||
} | ||
}; | ||
} | ||
|
||
function mapStateToProps(state, ownProps) { | ||
return { | ||
shipment: selectShipment(ownProps.shipmentID), | ||
error: lastError(state, requestLabel), | ||
}; | ||
} | ||
|
||
export default connect(mapStateToProps, mapDispatchToProps)(ShipmentDisplay); | ||
``` |
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,45 @@ | ||
# How To Run Go Tests | ||
|
||
## Run All Go Tests | ||
|
||
```console | ||
$ make server_test | ||
``` | ||
|
||
## Run Specific Tests | ||
|
||
All of the commands in this section assume that `test_db` is setup properly. This can be done using: | ||
|
||
```console | ||
$ make db_test_reset | ||
``` | ||
|
||
### Run All Tests in a Single Package | ||
|
||
```console | ||
$ go test ./pkg/handlers/internalapi/ | ||
``` | ||
|
||
### Run Tests with Names Matching a String | ||
|
||
The following will run any Testify tests that have a name matching `Test_Name` in the `handlers/internalapi` package: | ||
|
||
```console | ||
$ go test ./pkg/handlers/internalapi/ -testify.m Test_Name | ||
``` | ||
|
||
## Run Tests when a File Changes | ||
|
||
You'll need to install [ripgrep](https://github.com/BurntSushi/ripgrep) and [entr](http://www.entrproject.org/) (`brew install ripgrep entr`): | ||
|
||
```console | ||
$ rg -t go --files | entr -c $YOUR_TEST_COMMAND | ||
``` | ||
|
||
Here is an example that will run all model tests when any Go file in the project is changed: | ||
|
||
```console | ||
$ rg -t go --files | entr -c go test ./pkg/models | ||
``` | ||
|
||
There is generally no need to be any more specific than `rg -t go`, as watching all `.go` files is plenty fast enough. |
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 @@ | ||
# How To Store Data in Redux | ||
|
||
The specific layout of data within the Redux store should generally be considered an implementation detail and we should strive to avoid coupling any Components to this structure directly. Selectors provide the best way to decouple component data access from store layout. | ||
|
||
The current layout of data in Redux, however, is the following: | ||
|
||
```javascript | ||
{ | ||
entities: { | ||
shipments: { | ||
'123e4567-e89b-12d3-a456-426655440000': { /* shipment properties */ }, | ||
}, | ||
addresses: { | ||
'123e4567-e89b-12d3-a456-426655440000': { /* address properties */ }, | ||
} | ||
}, | ||
requests: { | ||
byID: { | ||
'req_0': { /* request properties */}, | ||
'req_1': { /* request properties */}, | ||
}, | ||
errored: { | ||
'req_1': { /* request properties */}, | ||
}, | ||
lastErrorByLabel: { | ||
'ShipmentForm.loadShipments': { /* error properties */ }, | ||
} | ||
}, | ||
ui: { | ||
'currentShipmentID': '123e4567-e89b-12d3-a456-426655440000', | ||
}, | ||
} | ||
``` |
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,64 @@ | ||
# How To Store UI State in Redux | ||
|
||
State that is specific to the UI should be set by dispatching an action and accessed using a selector. Here is an | ||
example of how this might work for managing which of a list of Shipments is currently selected in the UI: | ||
|
||
```javascript | ||
import { get } from 'lodash'; | ||
import PropTypes from 'prop-types'; | ||
import React, { Component} from 'react'; | ||
import { connect } from 'react-redux'; | ||
import { bindActionCreators } from 'redux'; | ||
|
||
import { lastError } from 'shared/Swagger/ducks'; | ||
import { allShipments, selectShipment } from 'shared/Entities/modules/shipments'; | ||
import { setCurrentShipmentID, currentShipmentID } from 'shared/UI/ducks'; | ||
|
||
const requestLabel = 'ShipmentForm.loadShipments'; | ||
|
||
export class ShipmentList extends Component { | ||
componentDidMount() { | ||
const id = get(this.props, 'shipmentID'); | ||
if (!id) return; | ||
|
||
this.props.listShipments(requestLabel); | ||
} | ||
|
||
shipmentClicked = (id) => { | ||
this.props.setCurrentShipmentID(id); | ||
} | ||
|
||
render { | ||
const { shipments, selectedShipment, error } = this.props; | ||
|
||
return ( | ||
<div> | ||
{ error && <p>An error has occurred.</p> } | ||
|
||
<ul> | ||
{ shipments.map(shipment => (<li> | ||
<button onClick={this.shipmentClicked.bind(shipment.id)}> { shipment.id } </button> | ||
</li>))} | ||
</ul> | ||
|
||
<p>The selected shipment is { selectedShipment.id }.</p> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
function mapDispatchToProps(dispatch) { | ||
return bindActionCreators({ request, setCurrentShipmentID }, dispatch); | ||
} | ||
|
||
function mapStateToProps(state) { | ||
return { | ||
shipments: allShipments(state), | ||
selectedShipment: selectShipment(state, currentShipmentID(state)), | ||
error: lastError(state, requestLabel), | ||
}; | ||
} | ||
``` | ||
|
||
Note that the above use of defining an inline event handler for `onClick` is not considered a | ||
best practice. This technique is used above only for its brevity. |
Oops, something went wrong.