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