Last active
July 15, 2021 23:57
-
-
Save praveen001/9beba17006dde3b03af31e25af9e811b to your computer and use it in GitHub Desktop.
React Clean Architecture
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"semi": true, | |
"singleQuote": true, | |
"trailingComma": "none", | |
"tabWidth": 2, | |
"useTabs": false, | |
"bracketSpacing": true | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { createStyles, Theme, withStyles, WithStyles } from '@material-ui/core/styles'; | |
import Button from '@material-ui/core/Button'; | |
import Divider from '@material-ui/core/Divider'; | |
import TextField from '@material-ui/core/TextField'; | |
import { ITodoItem, ApiStatus } from '../models'; | |
import Paper from '@material-ui/core/Paper'; | |
import { CircularProgress, Typography } from '@material-ui/core'; | |
const styles = (theme: Theme) => createStyles({ | |
wrap: { | |
display: 'flex', | |
justifyContent: 'center' | |
}, | |
content: { | |
width: 500 | |
}, | |
addButton: { | |
marginTop: theme.spacing.unit | |
}, | |
divider: { | |
marginTop: theme.spacing.unit * 2, | |
marginBottom: theme.spacing.unit * 2 | |
} | |
}); | |
class App extends React.Component<AppProps> { | |
constructor(props) { | |
super(props); | |
this.state = { | |
desc: '' | |
} | |
} | |
componentDidMount() { | |
// Load todos on mount | |
this.props.loadTodos(); | |
} | |
addTodo = () => { | |
this.props.addTodo(this.state.desc); | |
} | |
onDescChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
this.setState({ | |
desc: e.target.value | |
}); | |
} | |
render() { | |
const { classes, todos, loadingStatus } = this.props; | |
return ( | |
<div className={classes.wrap}> | |
<div className={classes.content}> | |
<div> | |
<TextField multiline placeholder="Enter todo message" rows="5" variant="outlined" onChange={this.onDescChange} value={this.state.desc} fullWidth /> | |
<Button className={classes.addButton} color="primary" variant="contained" onClick={this.addTodo} fullWidth>Add Todo</Button> | |
</div> | |
<Divider className={classes.divider} /> | |
<div> | |
{loadingStatus === ApiStatus.LOADING && <CircularProgress />} | |
{loadingStatus === ApiStatus.FAILED && <Typography color="error">Failed to load todos</Typography>} | |
{loadingStatus === ApiStatus.LOADED && todos.map(todo => ( | |
<Paper key={todo.id}> | |
{todo.description} | |
</Paper> | |
))} | |
</div> | |
</div> | |
</div> | |
); | |
} | |
} | |
export default withStyles(styles)(App); | |
// Define props coming from redux store | |
export interface IAppStateProps { | |
loadingStatus: ApiStatus; | |
todos: ITodoItem[]; | |
} | |
// Define props that are action creators | |
export interface IAppDispatchProps { | |
loadTodos: () => void; | |
addTodo: (todo: ITodoItem) => void; | |
} | |
type AppProps = IAppStateProps & IAppDispatchProps & WithStyles<typeof styles> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect, useState } from 'react'; | |
import { useDispatch, useSelector } from 'react-redux'; | |
import { makeStyles, Theme } from '@material-ui/core/styles'; | |
import Button from '@material-ui/core/Button'; | |
import Divider from '@material-ui/core/Divider'; | |
import TextField from '@material-ui/core/TextField'; | |
import { ITodoItem, ApiStatus } from '../models'; | |
import { loadTodos } from '../actions/todosActions'; | |
import Paper from '@material-ui/core/Paper'; | |
import { CircularProgress, Typography } from '@material-ui/core'; | |
const useStyles = makeStyles((theme: Theme) => ({ | |
wrap: { | |
display: 'flex', | |
justifyContent: 'center' | |
}, | |
content: { | |
width: 500 | |
}, | |
addButton: { | |
marginTop: theme.spacing.unit | |
}, | |
divider: { | |
marginTop: theme.spacing.unit * 2, | |
marginBottom: theme.spacing.unit * 2 | |
} | |
})); | |
const App: React.FC<AppProps> = (props) => { | |
const [desc, setDesc] = useState(''); | |
const todos = useSelector(state => state.todos.todos); | |
const loadingStatus = useSelector(state => state.todos.loadingStatus); | |
const classes = useStyles(); | |
const dispatch = useDispatch(); | |
useEffect(() => { | |
dispatch(loadTodos()); | |
}, []); | |
const addNewTodo = () => { | |
dispatch(addTodo(desc)); | |
} | |
const onDescChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
setDesc(e.target.value); | |
} | |
return ( | |
<div className={classes.wrap}> | |
<div className={classes.content}> | |
<div> | |
<TextField multiline placeholder="Enter todo message" rows="5" variant="outlined" onChange={onDescChange} value={desc} fullWidth /> | |
<Button className={classes.addButton} color="primary" variant="contained" onClick={addNewTodo} fullWidth>Add Todo</Button> | |
</div> | |
<Divider className={classes.divider} /> | |
<div> | |
{loadingStatus === ApiStatus.LOADING && <CircularProgress />} | |
{loadingStatus === ApiStatus.FAILED && <Typography color="error">Failed to load todos</Typography>} | |
{loadingStatus === ApiStatus.LOADED && todos.map(todo => ( | |
<Paper key={todo.id}> | |
{todo.description} | |
</Paper> | |
))} | |
</div> | |
</div> | |
</div> | |
); | |
} | |
export default App; | |
type AppProps = WithStyles<typeof styles> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import App, { IAppStateProps, IAppDispatchProps } from '../components/App'; | |
import { IState } from '../reducers'; | |
import { addTodo, loadTodos } from '../actions/todosActions'; | |
import { connect } from 'react-redux'; | |
function mapStateToDispatch(state: IState): IAppStateProps { | |
return { | |
todos: state.todos.todos, | |
loadingStatus: state.todos.loadingStatus | |
} | |
} | |
const mapDispatchToProps: IAppDispatchProps = { | |
addTodo, | |
loadTodos | |
} | |
export default connect(mapStateToDispatch, mapDispatchToProps)(App); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { combineReducers } from 'redux'; | |
import todosReducer, { ITodoState, initialTodoState } from './todosReducer'; | |
export interface IState { | |
todos: ITodoState; | |
} | |
export const initialState: IState = { | |
todos: initialTodoState | |
}; | |
export default combineReducers({ | |
todos: todosReducer | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { combineEpics, createEpicMiddleware } from 'redux-observable'; | |
import todoEpics from './todoEpics'; | |
export const rootEpic = combineEpics(todoEpics); | |
export default createEpicMiddleware(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import ReactDOM from 'react-dom'; | |
import { Provider } from 'react-redux'; | |
import store from './store'; | |
import AppContainer from './containers/AppContainer'; | |
ReactDOM.render( | |
<Provider store={store}> | |
<AppContainer /> | |
</Provider>, | |
document.getElementById("app") | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface ITodoItem { | |
id: number; | |
description: string; | |
} | |
export enum ApiStatus { | |
LOADING = 'loading', | |
LOADED = 'loaded', | |
FAILED = 'failed' | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"husky": { | |
"hooks": { | |
"pre-commit": "lint-staged" | |
} | |
}, | |
"lint-staged": { | |
"*.{json}": [ | |
"prettier --write" | |
], | |
"*.{ts,tsx}": [ | |
"prettier --write", | |
"tslint --fix", | |
"git add" | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { applyMiddleware, createStore } from 'redux'; | |
import { composeWithDevTools } from 'redux-devtools-extension'; | |
import epicMiddleware, { rootEpic } from './epics'; | |
import rootReducer, { initialState } from './reducers'; | |
const composeEnhancer = composeWithDevTools({ | |
name: 'React Clean Architecture' | |
}); | |
const store = createStore( | |
rootReducer, | |
initialState, | |
composeEnhancer(applyMiddleware(epicMiddleware)) | |
); | |
epicMiddleware.run(rootEpic); | |
export default store; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export enum TodosActionTypes { | |
LOAD_TODOS = 'todos/load', | |
LOADING_TODOS = 'todos/loading', | |
LOADED_TODOS = 'todos/loaded', | |
LOADING_TODOS_FAILED = 'todos/loading_failed', | |
ADD_TODO = 'todos/add', | |
ADDING_TODO = 'todos/adding', | |
ADDED_TODOS = 'todos/added', | |
ADDING_TODOS_FAILED = 'todos/adding_failed' | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface ILoadTodosAction { | |
type: TodosActionTypes.LOAD_TODOS; | |
} | |
export interface ILoadingTodosAction { | |
type: TodosActionTypes.LOADING_TODOS; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ITodoItem } from "../models"; | |
export function loadTodos(): ILoadTodosAction { | |
return { | |
type: TodosActionTypes.LOAD_TODOS | |
} | |
} | |
export function loadingTodos(): ILoadingTodosAction { | |
return { | |
type: TodosActionTypes.LOADING_TODOS | |
} | |
} | |
export function loadedTodos(todos: ITodoItem[]): ILoadedTodosAction { | |
return { | |
type: TodosActionTypes.LOADED_TODOS, | |
payload: { | |
todos | |
} | |
} | |
} | |
export function loadingTodosFailed(): ILoadingTodosFailedAction { | |
return { | |
type: TodosActionTypes.LOADING_TODOS_FAILED | |
} | |
} | |
export function addTodo(description: string): IAddTodoAction { | |
return { | |
type: TodosActionTypes.ADD_TODO, | |
payload: { | |
description | |
} | |
} | |
} | |
export function addingTodo(): IAddingTodoAction { | |
return { | |
type: TodosActionTypes.ADDING_TODO | |
} | |
} | |
export function addedTodo(todo: ITodoItem): IAddedTodoAction { | |
return { | |
type: TodosActionTypes.ADDED_TODOS, | |
payload: { | |
todo | |
} | |
} | |
} | |
export function addingTodoFailed(): IAddingTodoFailedAction { | |
return { | |
type: TodosActionTypes.ADDING_TODOS_FAILED | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { combineEpics, Epic } from "redux-observable"; | |
import { switchMap, map, startWith, catchError, filter, mergeMap } from "rxjs/operators"; | |
import axios from "axios"; | |
import { | |
TodosAction, | |
TodosActionTypes, | |
loadedTodos, | |
loadingTodos, | |
loadingTodosFailed, | |
addedTodo, | |
addingTodo, | |
addingTodoFailed | |
} from "../actions/todosActions"; | |
import { IState } from "../reducers"; | |
import { from, of } from "rxjs"; | |
import { isOfType } from "typesafe-actions"; | |
const loadTodosEpic: Epic<TodosAction, TodosAction, IState> = ( | |
action$, | |
state$ | |
) => | |
action$.pipe( | |
filter(isOfType(TodosActionTypes.LOAD_TODOS)), | |
switchMap(action => | |
from(axios.get("http://localhost:5000/todos")).pipe( | |
map(response => loadedTodos(response.data.data)), | |
startWith(loadingTodos()), | |
catchError(() => of(loadingTodosFailed())) | |
) | |
) | |
); | |
const addTodoEpic: Epic<TodosAction, TodosAction, IState> = ( | |
action$, | |
state$ | |
) => action$.pipe( | |
filter(isOfType(TodosActionTypes.ADD_TODO)), | |
mergeMap(action => | |
from(axios.post("http://localhost:5000/todos", action.payload)).pipe( | |
map(response => addedTodo(response.data.data)), | |
startWith(addingTodo()), | |
catchError(() => of(addingTodoFailed())) | |
) | |
) | |
) | |
export default combineEpics(loadTodosEpic, addTodoEpic); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const initialTodoState: ITodoState = { | |
loadingStatus: ApiStatus.LOADING, | |
addingStatus: ApiStatus.LOADED, | |
todos: [] | |
} | |
export interface ITodoState { | |
loadingStatus: ApiStatus; | |
addingStatus: ApiStatus; | |
todos: ITodoItem[]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default function todosReducer(state: ITodoState = initialTodoState, action: TodosAction) { | |
return produce(state, draft => { | |
switch (action.type) { | |
case TodosActionTypes.LOAD_TODOS: | |
case TodosActionTypes.LOADING_TODOS: | |
draft.loadingStatus = ApiStatus.LOADING; | |
break; | |
case TodosActionTypes.LOADING_TODOS_FAILED: | |
draft.loadingStatus = ApiStatus.FAILED; | |
break; | |
case TodosActionTypes.LOADED_TODOS: | |
draft.loadingStatus = ApiStatus.LOADED; | |
draft.todos = action.payload.todos; | |
break; | |
case TodosActionTypes.ADD_TODO: | |
case TodosActionTypes.ADDING_TODO: | |
draft.addingStatus = ApiStatus.LOADING; | |
break; | |
case TodosActionTypes.ADDING_TODOS_FAILED: | |
draft.addingStatus = ApiStatus.FAILED; | |
break; | |
case TodosActionTypes.ADDED_TODOS: | |
draft.todos.push(action.payload.todo); | |
break; | |
} | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Show hidden characters
{ | |
"compilerOptions": { | |
"target": "es5", | |
"outDir": "./dist", | |
"jsx": "react", | |
"allowSyntheticDefaultImports": true, | |
"esModuleInterop": true, | |
"lib": ["es2015", "dom", "es2017"], | |
"strict": true | |
}, | |
"exclude": ["node_modules"] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"defaultSeverity": "error", | |
"extends": [ | |
"tslint:recommended", | |
"tslint-config-standard", | |
"tslint-react", | |
"tslint-config-prettier" | |
], | |
"jsRules": {}, | |
"rules": { | |
"ordered-imports": true, | |
"object-literal-sort-keys": false, | |
"member-ordering": false, | |
"jsx-no-lambda": false, | |
"jsx-boolean-value": false, | |
"member-access": false, | |
"max-line-length": [true, 150], | |
"no-var-requires": false | |
}, | |
"rulesDirectory": [] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const path = require('path'), | |
HtmlWebpackPlugin = require('html-webpack-plugin'), | |
webpack = require('webpack'), | |
ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'), | |
HappyPack = require('happypack'); | |
module.exports = { | |
mode: 'development', | |
entry: './src/index.tsx', | |
resolve: { | |
extensions: ['.ts', '.tsx', '.js', '.jsx'] | |
}, | |
devtool: 'inline-source-map', | |
devServer: { | |
historyApiFallback: { | |
index: '/index.html' | |
} | |
}, | |
output: { | |
path: path.resolve(__dirname, 'dist'), | |
filename: 'bundle.js', | |
publicPath: '/' | |
}, | |
module: { | |
rules: [ | |
{ | |
test: /\.tsx?$/, | |
loader: 'happypack/loader?id=ts' | |
} | |
] | |
}, | |
plugins: [ | |
new HappyPack({ | |
id: 'ts', | |
threads: 2, | |
loaders: [ | |
{ | |
path: 'ts-loader', | |
query: { happyPackMode: true } | |
} | |
] | |
}), | |
new ForkTsCheckerWebpackPlugin(), | |
new HtmlWebpackPlugin({ | |
template: './src/index.html' | |
}), | |
new webpack.HotModuleReplacementPlugin() | |
] | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment