V school React/Redux Standard
Introduction
One of the great things about React/Redux is unlike other JavaScript libraries and frameworks React/Redux is very unopinionated. That means you are given a lot of freedom when determining how you build, use and deploy your projects in React/Redux.
This can be confusing when learning/building React as there are many different standards for building projects. You might have noticed this when looking at tutorials or documentation from React Libraries. So what standard does V school use?
V School Standard
V school uses a combination of standards taken from the React team and Industry standards. It can be broken down into three subsections.
Component Types
Stateless Functional Components
The simplest way to define a component is to write a JavaScript function. This should be your default component when programming in React.
const Header = (props) => (
<header">
<h1>Welcome, {props.name}</h1>
</header>
)
Class Components
If your component needs to deal with state, lifecycle methods, refs or form of logic you will then use a class component.
class Counter extends React.Component {
constructor() {
super();
this.state = {index: 0};
this.up = this.up.bind(this);
this.down = this.down.bind(this);
}
up() {
this.setState((prevState) => ({index: prevState.index + 1}));
}
down() {
this.setState((prevState) => ({ index: prevState.index - 1 }));
}
render() {
return (
<div>
<h1>{this.state.index}</h1>
<button onClick={this.up}>Up</button>
<button onClick={this.down}>Down</button>
</div>
)
}
}
Container and Presentational Components
If your component gets larger and starts handling more logic then you can split up your component into a Container (logic) and Presentational (display) components.
const Counter = (props) => (
<div>
<h1>{props.index}</h1>
<button onClick={props.up}>Up</button>
<button onClick={props.down}>Down</button>
<button onClick={props.square}>Square</button>
<button onClick={props.root}>Square Root</button>
</div>
)
class CounterContainer extends React.Component {
constructor() {
super();
this.state = { index: 0 };
this.up = this.up.bind(this);
this.down = this.down.bind(this);
this.square = this.square.bind(this);
this.root = this.root.bind(this)
}
up() {
this.setState((prevState) => ({ index: prevState.index + 1 }));
}
down() {
this.setState((prevState) => ({ index: prevState.index - 1 }));
}
square() {
this.setState((prevState) => ({ index: prevState.index * prevState.index }));
}
root() {
this.setState((prevState) => ({ index: Math.sqrt(prevState.index)}));
}
render() {
return (<Counter index={this.state.index} up={this.up} down={this.down} square={this.square} root={this.root} />)
}
}
Summary
When building components your default component type should be a Functional Component. Your next most common component type is Class Component for when your components need logic. The least common component types are the Container and Presentational Components for when your components become to large.
File Structures
Component File Structure
components/
|_ Counter/
|_ Counter.css
|_ Counter.js
|_ index.js
Every component will have its own folder. Inside there is a .css for styling and an index.js file that will contain the component. If the component becomes large enough a <ComponentName.js> will be added to store the Presentation component and index.js will contain the Container component.
Project File Structure Overview
When structuring our project we keep features in mind.This means we separate components and redux files into folders with the same feature name. In this case, the feature is called todos.
src/
|_ components/
|_Header
|_ index.js
|_ Header.css
|_Todos
|_index.js
|_Form
|_index.js
|_Form.css
|_List
|_index.js
|_List.css
|_Item
|_index.js
|_Item.css
|_Item.js
|_ redux/
|_index.js
|_todos
|_ index.js
|_ App.js
|_ index.css
|_ index.js
Components Breakdown
Components are separated into features folders inside the components Folder (Example Todos). If a component is just a display component and is not a feature it can just be stored in components (Example Header). If a component ever needs to be shared amongst features it would be stored in a folder called common sibling to all features.
Redux
V school embraces the redux duck philosophy. This philosophy breaks a project into ducks. Each 'duck' being a feature (Example todos).
A duck has following rules:
-
Must contain the entire logic for handling only ONE concept in your app, ex: product, cart, session, etc.
-
Must have an index.js file that exports according to the original duck rules.
-
Must keep code with similar purpose in the same file, such as reducers, selectors, and actions
This means that for every feature folder we make we will have an index.js file that exports each action creator and then export default the reducer. Note if your action creators become too large they can be separated into another file called actions.js inside of your features folder.
Todos Example
export const addTodo = (data) => {
return {
type: "ADD_TODO",
data
}
}
export const removeTodo = (index) => {
return {
type: "REMOVE_TODO",
index
}
}
export const updateTodo = (index, data) => {
return {
type: "UPDATE_TODO",
index,
data
}
}
const reducer = (state = [], action) => {
switch (action.type) {
case "ADD_TODO":
return [...state, action.data];
case "REMOVE_TODO":
return state.filter((item, index) => index !== action.index);
case "UPDATE_TODO":
return state.map((item, index) => {
if(index === action.index) {
return action.data;
} else {
return item;
}
});
default:
return state;
}
}
export default reducer;
All reducers are then imported into a main index.js file in the redux file, combined, made into a store and exported.
import {combineReducers, createStore, applyMiddleware} from "redux";
import todos from "./todos";
const rootReducer = combineReducers({
todos
});
export default createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());