# React Flow
# Starting a React project
# Create
When creating React applications we use our custom Three-11 (opens new window) templates. According to our preference to use or not to use TypeScript
, you can choose by two type of templates: the JS (opens new window) standard one, or the one with TypeScript (opens new window).
Considering the advantages TypeScript
gives us, we recommend to use the template (opens new window) with TypeScript
included.
Download the required React template: React template JS standard (opens new window) or React template with TypeScript (opens new window).
Open the project in VS Code, and run
yarn
/yarn install
, to install all the dependencies.Checkout all the files and make sure you are familiar with the initial project structure and all the pre-configure goodies such as styles, classes, mixins, variables, components, containers, etc.
Great, you are good to go!!!
# When created
- Checkout and evaluate the provided design.
- Define the components.
- Define which components and elements will be reusable.
- Define which elements are repeatable and plan their style: text blocks, headings, icons, etc.
- For images:
- Use SVGs for all icons.
- Use PNGs for icons which cannot be exported as SVGs.
- Use JPGs for content images (images which are subject to change vithe WPadmin).
- Define which fonts are used and if they are provided or available.
# Before Develop
- Export needed images in right format from the provided design.
- Open
package.json
file and change the data (name
,description
..), where needed. - Add all the meta information needed in the
<head>
ofindex.html
located in the root directory. - Add favicon to the project. All the favicon images should go to
assets/images/favicon
, and thelinks
for them, in the<head>
ofindex.html
file, located at the root directory.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="mobile-web-app-capable" content="yes" />
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="/assets/>apple-touch-icon-57x57.png" />
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="/assets/>apple-touch-icon-114x114.png" />
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="/assets/>apple-touch-icon-72x72.png" />
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="/assets/>apple-touch-icon-144x144.png" />
<link rel="apple-touch-icon-precomposed" sizes="60x60" href="/assets/>apple-touch-icon-60x60.png" />
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="/assets/>apple-touch-icon-120x120.png" />
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="/assets/>apple-touch-icon-76x76.png" />
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="/assets/>apple-touch-icon-152x152.png" />
<link rel="icon" type="image/png" href="/assets/favicon-196x196.png" />
sizes="196x196" />
<link rel="icon" type="image/png" href="/assets/favicon-96x96.png" />
sizes="96x96" />
<link rel="icon" type="image/png" href="/assets/favicon-32x32.png" />
sizes="32x32" />
<link rel="icon" type="image/png" href="/assets/favicon-16x16.png" />
sizes="16x16" />
<link rel="icon" type="image/png" href="/assets/favicon-128.png" />
sizes="128x128" />
<meta name="application-name" content="NAME" />
<meta name="msapplication-TileColor" content="#fff" />
<meta name="msapplication-TileImage" content="/mstile-144x144.png" />
<meta name="msapplication-square70x70logo" content="/mstile-70x70.png" />
<meta name="msapplication-square150x150logo" content="/mstile-150x150.png" />
<meta name="msapplication-wide310x150logo" content="/mstile-310x150.png" />
<meta name="msapplication-square310x310logo" content="/mstile-310x310.png" />
<meta name="theme-color" content="#fff" />
//Include the font needed
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open>+Sans:400,600,700&display=swap" />
<link href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700,900" />
rel="stylesheet" />
<title>Project title</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
- Erase all the stuff you don't need. If you need them anyway, go to Three-11 Github (opens new window).
# Develop
# 1. Folder/File structure
(The example is for React project with TypeScript.Тhe structure is similar with no TypeScript, just the extension on the files is not .tsx/ts
but .js
)
More info (opens new window)
src/
directory is the directory where our project content and styles are located.assets/
is the place, where all fonts, images, icons, svgs, videos, etc. are located.assets/styles/utilities
here you can place all the global/generic styles, mixins, colors, etc..
components/
is the directory where are all the components(stateless components). When new component is created in order to use it, we should export it, in theindex.ts
file, located in thecomponents
directory.containers/
is where all the container(pages(statefull components) are located. Each container can export more than one component. An example folder structure is included in (src/containers/.boilerplate
).enums.ts
- each container has its own enumsinterfaces.ts
- each container has its own interfacesreducer.ts
- the container reducersagas.ts
- the container sagas
utilities/
all the helpers and utilityfunctions
,enums
,interfaces
, etc.. are located.app.scss
is the Application's global SCSS entry pointapp.tsx
is the Application's main component. The React router is used to decide which component to show and which to hide.custom.d.ts
Custom type definitionsindex.html
is the Application's HTML file 10.index.tsx
- The main entry pointlodables.tsx
- Code split and lazy loaded componentsreducers.ts
- Application's root reducersagas.ts
- Application's sagassettings.scss
is the Application's SCSS settings (variables, mixins, etc)store.ts
- Application's Redux store
# 2. Guidelines
CSS/SCSS in React
In our React projects we useSCSS
syntax ofSass
preprocessor.
Sass - guidelines (opens new window)Every new component which is created should contain it's own
index.tsx
file, where all the JSX will be written. If this component has it's own styles, the directory should have alsoindex.scss
file. Examplenav
component directory should look like this:
components/
btn/
nav/
index.tsx
index.scss
- When importing package in a file it should follow some order. On top of the imports are all React imports, then all the other external packages and then all the local files we need. All the imports in a file should be arranged like a 'ladder' based on
from
word. Here is a base example.
import * as React from 'react';
import SVG from 'react-inlinesvg';
import { useSelector, useDispatch } from 'react-redux';
import * as moment from 'moment';
import { v4 as uuidv4 } from 'uuid';
import { RootStore } from '@src/store';
import { UserInterface } from '@containers/tasks/interfaces';
import { UsersActionTypes } from '@containers/tasks/enums';
import { TaskPlanning, Event } from './interfaces';
import { ExpensesActionTypes } from '@containers/budget/enums';
import { SuppliersActionTypes } from '@containers/suppliers/enums';
import { PlanningActionTypes, EventActionTypes } from '@containersplanning/enum';
import { Wrapper, CalendarComponent, Popup, Table, TableType } from '@components';
- Use Named Export/Imports instead of Default Export/Imports where possible, or both.
For reference:
MDN - exports (opens new window)
Default exports or named exports: Why not both? (opens new window)
// Named Imports
// ex. importing a single named export
import { MyComponent } from './MyComponent';
// ex. importing multiple named exports
import { MyComponent, MyComponent2 } from './MyComponent';
// ex. giving a named import a different name by using "as":
import { MyComponent2 as MyNewComponent } from './MyComponent';
// Named Export
// exports from ./MyComponent.js file
export const MyComponent = () => {};
export const MyComponent2 = () => {};
// Default Import
// import
import MyDefaultComponent from './MyDefaultExport';
// Default Export
// export
const MyComponent = () => {};
export default MyComponent;
// Both
//import
import { Header } from '@components';
//export
export const Header: React.FunctionComponent = () => {};
export default Header;
- Use
Function Component
(Function Expression) to create containers and components, instead ofClass Component
. Take these examples below. The first one is aClass Component
, the second one is aFunction Component
. They both do exactly the same thing:
// Example stateless class component
class MyComponent extends React.Component {
render() {
return <p>Hello, {this.props.name}
}
}
// Example stateless function component
function MyComponent(props) {
return <p>Hello, { props.name }</p>
}
// Example stateful class component
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {s
count: props.count || 0
}
this.onClickHandler = this.onClickHandler.bind(this);
}
public onClickHandler(e): void {
this.setState({
count: this.state.count + 1;
});
}
public render(): React.ReactNode {
return (
<div>
<p>Count is: {this.state.count}</p>
<button onClick={onClickHandler}>Increase Count</button>
</div>
);
}
}
// Example stateful function component
import, React {useState} from 'react';
function MyComponent(props) {
const [count, setCount] = useState(props.count || 0);
const onClickHandler = (): void => {
setCount(count + 1);
};
return (
<div>
<p>Count is: {count}</p>
<button onClick={onClickHandler}>Increase count</button>
</div>
);
}
2.5. Use React Hooks (opens new window) instead of lifecycle methods.
// Example stateful class component
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
isLoading: false,
error: null
};
}
async loadAsyncData() {
this.setState({ isLoading: true, error: null });
try {
const resp = await fetch('https://...').then(r => r.json());
this.setState({ isLoading: false, data: resp });
} catch (e) {
this.setState({ isLoading: false, error: e });
}
}
componentDidMount() {
loadAsyncData();
}
render() {
if (this.state.isLoading) return <p>Loading...</p>;
if (this.state.error) return <p>Something went wrong</p>;
if (this.state.data) return <p>The data is: {data}</p>;
return <p>No data yet</p>;
}
}
// Example stateful function component
import React, { useEffect, useState } from 'react';
function MyComponent() {
const [data, setData] = useState();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState();
const loadAsyncData = async () => {
setIsLoading(true);
setError(null);
try {
const resp = await fetch('https://...').then(r => r.json());
setData(resp);
setIsLoading(false);
} catch (e) {
setError(e);
setIsLoading(false);
}
};
useEffect(() => {
loadAsyncData();
}, []);
if (this.state.isLoading) return <p>Loading...</p>;
if (this.state.error) return <p>Something went wrong</p>;
if (this.state.data) return <p>The data is: {data}</p>;
return <p>No data yet</p>;
}
2.6. Use MDN - Destructuring assignment (opens new window) and rest
syntax where possible.
// Bad example
const hero = {
name: 'Batman',
realName: 'Bruce Wayne'
};
const name = hero.name;
const realName = hero.realName;
name; // => 'Batman',
realName; // => 'Bruce Wayne'
// Good example
const hero = {
name: 'Batman',
realName: 'Bruce Wayne'
};
const { name, realName } = hero;
name; // => 'Batman',
realName; // => 'Bruce Wayne'
// =======
const name = hero.name;
const realName = hero.realName;
// is equivalent to:
const { name, realName } = hero;
// Rest syntax
const hero = {
name: 'Batman',
realName: 'Bruce Wayne'
};
const { name, ...realHero } = hero;
realHero; // => { realName: 'Bruce Wayne' }
2.7. When using TypeScript
, every object, array or variable, should have their own type or interface declared. See TypeScript docs (opens new window)
const name: string = 'John';
const index: number = 3;
const isDone: boolean = true;
const lucky-numbers: number[] = [22, 18, 10];
const namesArray: ReadonlyArray<string> = ['Ivan', 'Petio', 'Bojan'];
export interface User {
readonly id: string;
readonly name: string;
readonly active: boolean;
}
export interface Supplier {
readonly id: string;
readonly color: string;
readonly 'supplier-name': string;
readonly type: string;
readonly 'contact-name': string;
readonly 'service-description': string;
readonly phone: string;
readonly email: string;
readonly status: string;
readonly isActive: boolean;
}
# 3. Redux
We have Redux
store already implemented in our React
templates.
To make use of it do the following steps:
- Define schema for store branch
- Save the schema in
interfaces.ts
.
export interface Car {
readonly brand: string;
readonly model: string;
readonly doors: number;
}
export interface CarsGroup {
readonly title: string;
readonly rows: Car[];
}
export interface CarActions {
type: PlanningActionTypes;
payload: any;
}
- Create enumeration for actions which are going to make changes to the store object.
export enum CarActionTypes {
ADD_CAR = 'ADD_CAR',
DELETE_CAR = 'DELETE_CAR',
MODIFY_CAR = 'MODIFY_CAR'
}
- Create initial state
export const carsGroup: CarsGroup[] = [
{
title: 'Family Cars',
rows: [
{
brand: 'Ford',
model: 'Mondeo',
doors: 5
},
{
brand: 'Kia',
model: 'Sportage',
doors: 5
},
{
brand: 'Opel',
model: 'Insignia',
doors: 4
}
]
}
];
- Create reducer(reducer is pure function that take the CURRENT state of an application, perform an action, and return a NEW state)
import { CarActionTypes } from './enum';
import { CarActions } from './interfaces';
import { carsGroup } from './initial-state';
export const eventReducer = (state = carsGroup, { type, payload }: CarActions): Event[] => {
switch (type) {
case EventActionTypes.ADD_CAR:
return payload;
case EventActionTypes.DELETE_CAR:
return payload;
case EventActionTypes.MODIFY_CAR:
return payload;
default:
return state;
}
};
- Set the branch in the store
import { routerMiddleware } from 'connected-react-router';
import { composeWithDevTools } from 'redux-devtools-extension';
import { History, createBrowserHistory } from 'history';
import createSagaMiddleware, { Saga, SagaMiddleware } from 'redux-saga';
import { Store, Middleware, createStore, applyMiddleware } from 'redux';
import sagas from './sagas';
import rootReducer from './reducers';
import { AuthState, initialState as authInitialState } from '@containers/uth';
import { Cars } from '@containers/cars/interfaces'
import { carsInitialState } from '@containers/cars/initial-state'
export interface RootStore {
auth: AuthState;
readonly cars: carsGroup[];
}
export const history: History = createBrowserHistory();
export const sagaMiddleware: SagaMiddleware = createSagaMiddleware();
export function configureStore(): Store<RootStore> {
const historyMiddleware: Middleware = routerMiddleware(history);
const store: Store<RootStore> = createStore(
rootReducer(history),
{
auth: authInitialState;
cars: carsInitialState;
},
composeWithDevTools(applyMiddleware(sagaMiddleware, historyMiddleware))
);
if (module.hot) {
module.hot.accept();
// eslint-disable-next-line
store.replaceReducer(require('./reducers').default(history));
}
sagas.forEach((saga: Saga) => {
sagaMiddleware.run(saga);
});
return store;
}
- Select the branch in the component and use it instead of local state
const carsState = useSelector((state: RootStore) => state.cars);
- Dispatch actions (defined in
enums.ts
) to update the branch
const dispatch = useDispatch();
const handleAddCarClick = (): void => {
dispatch({
type: EventActionTypes.ADD_CAR,
payload: [
{
brand: 'Reno',
model: 'Talisman',
doors: 4
}
]
});
};
After all the steps your component's folder should look something like this:
component/
enums.ts
index.tsx
initial-state.ts
interfaces.ts
reducer.ts
# 4. Redux - Saga
Redux-Saga
is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, easy to test, and better at handling failures.
The mental model is that a saga is like a separate thread in your application that's solely responsible for side effects. Redux-Saga
is a redux middleware, which means this thread can be started, paused and cancelled from the main application with normal redux actions, it has access to the full redux application state and it can dispatch redux actions as well.
Redux - Saga docs (opens new window)
We already have Redux-Saga
implemented in our React
templates. To start using it, you should do the following steps.
- Go to
sagas.js
and define your saga. We'll create a Saga that watches for allUSER_FETCH_DATA
actions and triggers an API call to fetch the user data.
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';
import Api from '...';
// worker Saga: will be fired on USER_FETCH_DATA actions
export function* fetchUserData(action) {
try {
const user = yield call(Api.fetchUserData, action.payload.userId);
// handle success
yield put({ type: 'USER_FETCH_SUCCEEDED', user: user });
} catch (e) {
// handle error
yield put({ type: 'USER_FETCH_FAILED', message: e.message });
}
}
// Starts fetchUserData on each dispatched `USER_FETCH_DATA` action.
export function* getUserData() {
yield takeLatest('USER_FETCH_DATA', fetchUserData);
}
- Dispatch action from the component (on some button click) to fetch user data. The Component dispatches a plain Object action to the Store
export const Component {
...
onButtonClicked() {
dispatch({type: 'USER_FETCH_DATA'})
}
...
}