A very opinionated boilerplate by me, mainly for me. Use if you like. This project is basically the blueprint where I start my new projects from. It started by using davezuko's boilerplate and has evolved in my projects, professional and personal. I like JWT and REST. Maybe I'll do one for GraphQL in the future, but this starter optimizes my speed to start new projects.
This readme just combines other docs, mainly to get anyone started quickly, from mine/bootstrap/react-redux-starter/icomoon.
- React
- Redux
- Styled Components
- Redux-Persist
- Eslint & Prettier working together
- React Router v3
- Bootstrap 4 with Reactstrap
- JWT Auth with mocked option
- My most used packages included, such as dropzone, lodash, select etc.
This project structure is something that has
Also known as: Self-Contained Apps, Recursive Route Hierarchy, Providers, etc
Small applications can be built using a flat directory structure, with folders for components
, containers
, etc. However, this structure does not scale and can seriously affect development velocity as your project grows. Starting with a fractal structure allows your application to organically drive its own architecture from day one.
This structure provides many benefits that may not be immediately obvious:
- Routes can be bundled into "chunks" using webpack's code splitting and merging algorithm. This means that the entire dependency tree for each route can be omitted from the initial bundle and then loaded on demand.
- Since logic is self-contained, routes can easily be broken into separate repositories and referenced with webpack's DLL plugin for flexible, high-performance development and scalability.
Large, mature apps tend to naturally organize themselves in this way—analogous to large, mature trees (as in actual trees 🌲). The trunk is the router, branches are route bundles, and leaves are views composed of common/shared components/containers. Global application and UI state should be placed on or close to the trunk (or perhaps at the base of a huge branch, eg. /app
route).
Note: We recommend keeping your store flat, which is not strictly fractal. However, this structure provides a rock-solid foundation for creating or migrating to truly fractal apps by dropping in frameworks such as redux-elm.
We use react-router
route definitions (<route>/index.js
) to define units of logic within our application.
It's important to understand how webpack integrates with react-router
to implement code splitting, and how everything is tied together with redux. Let's dissect the counter route definition:
/* 1. ReactRouter - Create PlainRoute definition object */
export default (store) => ({
path: 'counter',
/* 2. ReactRouter - Invoked when path match (lazy) */
getComponent (nextState, cb) {
/* 3. Webpack (build) - Create split point
4. Webpack (runtime) - Load async chunk with embedded jsonp client */
require.ensure([], (require) => {
/* 5. Webpack (build) - Require all bundle contents */
const Counter = require('./containers/CounterContainer').default
const reducer = require('./modules/counter').default
/* 6. Redux - Use store and helper to add async reducer */
injectReducer(store, { key: 'counter', reducer })
/* 7. ReactRouter - Return component */
cb(null, Counter)
/* 8. Webpack - Provide a name for bundle */
}, 'counter')
}
})
- ReactRouter - We export a function that accepts the instantiated store for async reducer/middleware/etc injection and returns a
PlainRoute
object evaluated by react-router during application bootstrap. - ReactRouter - The
getComponent
callback is registered but it is not invoked until the route is matched, so it is the perfect place to encapsulate and load our bundled logic at runtime. - Webpack (Build) - Webpack uses the
require.ensure
callback to create a split point and replaces it with a call to it's own internaljsonp
client with relevant module information. - Webpack (Runtime) - Webpack loads your bundle over the network.
- Webpack (Build) - Webpack walks the required dependency tree and runs a chunking algorithm to merge modules into an async bundle, also known as code-splitting.
- Redux - Use
injectReducer
helper and instantiated store to injectcounter
reducer on key 'counter' - ReactRouter - Pass resolved component back up to
Router
(using CPS callback signature) - Webpack (Build) - Create named chunk using
require.ensure
callback
Notes
- Your entire route hierarchy can and should be loaded during application bootstrap, since code-splitting and bundle loading happens lazily in
getComponents
the route definitions should be registered in advance! - Additional child routes can be nested in a fractal hierarchy by adding
childRoutes
- This structure is designed to provide a flexible foundation for module bundling and dynamic loading
- Using a fractal structure is optional, smaller apps might benefit from a flat routes directory
Usage with JSX
We recommend using POJO (Plain Old Javascript Object) route definitions, however you can easily integrate them with JSX routes using React Router's createRoutes
helper. Example of POJO routes using JSX:
// ...
import SubRoutes from './routes/SubRoutes' // JSX Routes
export default {
path: '/component',
component: Component,
children: createRoutes(SubRoutes)
}
- Alternatively, the JSX route definition file can
export default createRoutes(<Route />)
- JSX can easily use POJO routes by passing them as a prop, ie
<Route children={PlainRoute} />
Above all, you should seek to find the best solution for the problem you are trying to solve. This setup will not fit every use case, but it is extremely flexible. There is no "right" or "wrong" way to set up your project. Here are some general recommendations that we have found useful. If you would like to add something, please submit a PR.
- A route directory...
- Should contain an
index.js
that returns route definition - Optional: assets, components, containers, redux modules, nested child routes
- Additional child routes can be nested within
routes
directory in a fractal hierarchy
- Should contain an
- Your store should not reflect the hierarchy of your folder structure
- Keep your store as flat and normalized as possible. If you are dealing with deeply nested data structures, we recommend using a tool such as normalizr.
- Note that the
injectReducer
helper can be repurposed to suit your needs.
- Stateless components that dictate major page structure
- Useful for composing
react-router
named components into views
- Prefer stateless functional components
- eg:
const HelloMessage = ({ name }) => <div>Hello {name}</div>
- eg:
- Top-level
components
andcontainers
directories contain reusable components
- Containers only
connect
presentational components to actions/state- Rule of thumb: no JSX in containers!
- One or many container components can be composed in a stateless functional components
- Tip: props injected by
react-router
can be accessed usingconnect
:// CounterWithMusicContainer.js import { connect } from 'react-redux' import Counter from 'components/Counter' export const mapStateToProps = (state, ownProps) => ({ counter: state.counter, music: ownProps.location.query.music // why not }) export default connect(mapStateToProps)(Counter) // Location -> 'localhost:3000/counter?music=reggae' // Counter.props = { counter: 0, music: 'reggae' }
The main scss file is src/styles/core.scss. Any new css should be placed in one of the following sections based on its purpose. CSS for vendor libraries should also be imported here.
- base - fundamental css classes used throughout the application
- ui_components - styling of components like buttons, inputs, etc.
- components - css specific to a React component
All sizes should be defined in rem as opposed to px. (serious)
To include a vendor css file, locate the file you want to include within node_modules and import it starting with a ~.
@import '~bootstrap/dist/css/bootstrap';
These css classes are the fundamental building blocks that all components can utilize. These include colors, layout, and typography.
Spacing and breakpoints should be using Bootstrap 4.
All colors should be placed in the $color-map in _colors.scss. Classes will be generated with the name of the color to easily control the color, background-color, and border-color properties of an element.
-
Apply a color to an element:
<div className="color-(name)"></div>
-
Apply a background-color to an element:
<div className="color-bg-(name)"></div>
-
Apply a border color to an element:
<div className="color-bd-(name)"></div>
-
Within scss files, these classes can be extended, or references from the $color-map variable.
@extend .color-bg-white; border-top: 2px solid map_get($color-map, 'white');
Use the classes generated in _typography.scss for defining the font sizes of elements.
- t1: font size of 3rem (48px)
- t2: font size of 1.5rem (24px)
- t3: font size of 1.25rem (20px)
- t4: default font size of 1rem (16px)
- t5: default font size of .875rem (14px)
- t6: default font size of .625rem (10px)
The font sizes are also applied to h1-h6 elements, which will have a font-weight of 700.
It also contains some utility classes around text, such as text-uc to for a text-transform of uppercase.
Assign responsive-friendly margin
or padding
values to an element or a subset of its sides with shorthand classes. Includes support for individual properties, all properties, and vertical and horizontal properties. Classes are built from a default Sass map ranging from .25rem
to 3rem
.
Spacing utilities that apply to all breakpoints, from xs
to xl
, have no breakpoint abbreviation in them. This is because those classes are applied from min-width: 0
and up, and thus are not bound by a media query. The remaining breakpoints, however, do include a breakpoint abbreviation.
The classes are named using the format {property}{sides}-{size}
for xs and {property}{sides}-{breakpoint}-{size}
for sm
, md
, lg
, and xl
.
Where property is one of:
m
- for classes that setmargin
p
- for classes that setpadding
Where sides is one of:
t
- for classes that setmargin-top
orpadding-top
b
- for classes that setmargin-bottom
orpadding-bottom
l
- for classes that setmargin-left
orpadding-left
r
- for classes that setmargin-right
orpadding-right
x
- for classes that set both*-left
and*-right
y
- for classes that set both*-top
and*-bottom
- blank - for classes that set a margin or padding on all 4 sides of the element
Where size is one of:
0
- for classes that eliminate themargin
orpadding
by setting it to0
1
- (by default) for classes that set themargin
orpadding
to$spacer * .25
2
- (by default) for classes that set themargin
orpadding
to$spacer * .5
3
- (by default) for classes that set themargin
orpadding
to$spacer
4
- (by default) for classes that set themargin
orpadding
to$spacer * 1.5
5
- (by default) for classes that set themargin
orpadding
to$spacer * 3
auto
- for classes that set themargin
toauto
(You can add more sizes by adding entries to the$spacers
Sass map variable.)
Preferred icon usage:
Prefer including icons by using the dashboard-icons font, which can be updated using the IcoMoon app.
Look at src/styles/base/_dashboard_icons.scss for the list of available icons. You can control the icon size and color through the css properties font-size and color.
- HTML:
<i className="icon-(name)"/>
- CSS:
&:before { content: $icon-(name); }
Go to the IcoMoon app, click the 'Import Icons' button, and upload the file src/assets/icon/icomoon/selection.json. Once uploaded, you will see the current set of svg icons available in the font. To add additional icons, click the menu hamburger at the right of the set, and import any additional svg files. The icon name will default to the filename, and to change it click the pencil button to change to edit mode, then click the icon to edit the name.
In order to use an svg in an icon font, it must only have a single color, and all strokes must be converted to outlines. See more information here: Converting Strokes to Fills
Once the set has been updated, select all the icons in the set and click 'Generate Font' at the bottom right of the screen. Click download to generate the updated font files. The generated files mentioned below should be placed in the following locations:
- selection.json -> src/assets/icon/icomoon/selection.json
- fonts/* -> src/styles/fonts/dashboard_icons/
- Update src/styles/base/_dashboard_icons.scss with the content of variables.scss and styles.scss. Ensure the paths all point to ../../fonts/dashboard_icons