Skip to main content

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:

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.

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.

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:

// 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:

// 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.

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);