React Application for listing clinical trials
API endpoint domain has to be set as an initialization parameter for the application in /src/index.js
.
For example to retrieve geolocation using Google's Geo-coding API (https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA) based on address.
Add API domain base endpoint as an initialization parameter and add to "initialState"
in /src/index.js
to support an additional call for this service.
...snip...
const initialize = ({
...,
googleAPIEndpoint = "https://maps.googleapis.com/maps/api",
} = {}) => {
...snip...
const initialState = {
...snip...,
googleAPIEndpoint,
...snip...
};
...snip...
}
...snip...
Initialize variables and create convenience method to set this variable in /src/services/api/endpoints.js
Initialize "GOOGLE_API_ENDPOINT" and create method "setGoogleAPIEndpoint".
let GOOGLE_API_ENDPOINT;
/**
* Sets Google's base API endpoint
*
* @param {string} endpoint - Base API endpoint
* @return void
*/
export function setGoogleAPIEndpoint(endpoint) {
GOOGLE_API_ENDPOINT = cleanURI(endpoint);
}
Update /src/services/api/axios-client.js
Set "GOOGLE_API_ENDPOINT" by importing "setGoogleAPIEndpoint" from endpoints.js
using "googleAPIEndpoint" that was provided as an initialization parameter by destructuring it from "initialize".
...snip...
import { ..., setGoogleAPIEndpoint } from './endpoints';
export const getAxiosClient = (initialize) => {
const { ..., googleAPIEndpoint } = initialize;
setGoogleAPIEndpoint(googleAPIEndpoint);
...snip...
}
...snip...
Add a service name to "getEndpoints" in /src/services/api/endpoints.js
const endpoints = {
...,
geoCode: `${GOOGLE_API_ENDPOINT}/geocode/json`,
...,
};
A fetch action can then be defined by creating "getGeocodeResults.js" in /src/services/api/actions
import { getEndpoint } from '../endpoints';
export const getGeocodeResults = ({ address }) => {
const endpoint = getEndpoint('geoCode');
return {
method: 'GET',
endpoint: `${endpoint}?address=${address}`,
};
};
NOTE: Remember to create unit tests as well!
Make the fetch calls using "useCustomQuery".
import { useCustomQuery } from 'src/hooks';
import { getGeocodeResults } from 'src/services/api/actions/getGeocodeResults';
const sampleView = () => {
const address = '1600+Amphitheatre+Parkway,+Mountain+View,+CA';
const fetchResults = useCustomQuery(getGeocodeResults({ address }));
};
The "useCustomQuery" hook takes a second boolean parameter "shouldFetch" which allows for conditional fetches.
Handling analytics requires that the following code be used for a page load event:
window.NCIDataLayer = window.NCIDataLayer || [];
window.NCIDataLayer.push({
type: 'PageLoad',
event: '<EVENT_NAME>',
page: {
name: "",
title: "",
metaTitle: "",
language: "",
type: "",
audience: "",
channel: "",
contentGroup: "",
publishedDate: Date
additionalDetails: {}
}
});
and the following for click events:
window.NCIDataLayer.push({
type: 'Other',
event: '<EVENT_NAME>',
data: {},
});
One of the MOST IMPORTANT things is that page load events ALWAYS preceed click events. The EDDL keeps track of the page information raised during a page load, and that information is pushed out to the Analytics Tool with the click/other data payload. So if a click event is raised by the app BEFORE the page load it is associated with, then bad things happen...
The react-tracking library offers a way for embedding analytics agnostic event tracking in a react app. React-tracker OOB allows you to set various contextual data points in each of your components, such that if a nested component raises a tracking event, those data points are included in the data payload.
For example say you are displaying a search results page. You can:
track({ pageName: 'Results Page', searchTerm: 'chicken'})
track({numResults: 322})
tracking.trackEvent({
action: 'result_link_click',
position: thePosition,
title: result.title,
});
Then when a user clicks on a result link a tracking event is dispatched with:
{
pageName: 'Results Page',
searchTerm: 'chicken',
numResults: 322,
action: 'result_link_click',
position: 3,
title: 'A title'
}
We have created an AnalyticsProvider higher-order component that wraps our App "component". This provider wires up a custom dispatch function to the react-tracking library. This custom function is the analyticsHandler
parameter passed into the initialize function.
A react-tracker dispatch function takes in a data payload that has no predefined structured.
src/routing.js
contains a hook, useAppPaths that return helper functions that are used to not only generate URLs for a route, but also can be used to define the routes in your App.js file.
useAppPaths
will return an object with all the route names, as functions, defined in appPaths. The functions each take in an object that maps to the path patterns.
Example
// in routing.js
const appPaths = {
HomePath: '/',
ItemDetailsPath: '/:id',
};
// snippet from Home.jsx
import { useAppPaths } from './hooks';
...snip...
const {
HomePath,
ItemDetailsPath,
} = useAppPaths();
...snip...
<Link
to={ItemDetailsPath({id: "6789"})}
onClick={handleItemClick}>Item 6789</Link>
If you do not pass any parameters into the useAppPaths
functions, then the original path pattern will be returned. This is used for defining routes.
Example
// in routing.js
const appPaths = {
HomePath: '/',
ItemDetailsPath: '/:id',
};
// Route definition from App.js
import { useAppPaths } from './hooks';
...snip...
const {
HomePath,
ItemDetailsPath,
} = useAppPaths();
...snip...
<Router>
<Routes>
<Route path={HomePath()} element={<Home />} />
<Route path={ItemDetailsPath()} element={<ItemDetails />} />
<Route path="/*" element={<PageNotFound />} />
</Routes>
</Router>
Imports at the top of each page should always be alphabetized and follow this order.
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { useTracking } from 'react-tracking';
import './Application.scss';
import { useAppPaths } from '../../hooks';
import { useStateValue } from '../../store/store';
We recommend combining Prettier
with the Eslint
plugins for eslint and formatting while in development and before any code is pushed to a pull request.
A site’s architecture should be based on its goals and purposes. This means the guidance here should be adapted to different sites and situations.
Styles should be organized in the directory with the component it is being consumed by and follow the same naming convention. Such as definition.jsx
and definition.scss
src
├── components/
│ ├── molecules/
│ ├── definition/
│ ├── definition.jsx
│ ├── definition.scss
body,
div {
}
// Bad
// Avoid uppercase
.ClassNAME {
}
// Avoid camel case
.commentForm {
}
// What is a c1-xr? Use a more explicit name.
.c1-xr {
}
// Bad
.blue
.text-gray
.100width-box
// Good
.warning
.primary
.lg-box;
// Danger zone
.product_list
// Better
.item_list;
// Bad
.bm-rd
// Good
.block--lg;
// Good
.top_image[type='text'] {
}
.button {
}
.is_hovered {
}
.f18-component
The recommended way to do this is using an existing BEM methodology.
// block
.inset {
margin-left: 15%;
// element
.inset__content {
padding: 3em;
}
}
// modifier
.inset--sm {
margin-left: 10%;
.inset__content {
padding: 1em;
}
}
// modifier
.inset--lg {
margin-left: 20%;
}