├── .gitignore ├── LICENSE ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── server.js ├── src ├── api │ └── posts.api.json ├── app.tsx ├── index.tsx ├── layout.tsx ├── pages │ └── homePage.tsx ├── routes.tsx ├── shared │ ├── about.tsx │ └── footer.tsx ├── stores │ ├── about.store.tsx │ ├── posts.store.tsx │ └── root.store.tsx └── tools │ ├── StoreProvider.tsx │ ├── useRootData.ts │ └── useStoreData.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Anna Janicka 4 | Forked from: https://github.com/annajanicka/react-typescript-mobx-state-tree-mobx-react-lite-boilerplate 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Frontend Boilerplate with React, TypeScript, MST(mobx-state-tree), mobx-react-lite, webpack 2 | 3 | Ideal for creating React apps from scratch. Contains examples and simulates using of an API. 4 | 5 | ## Contains 6 | - [x] [Typescript](https://www.typescriptlang.org/) 3.5 7 | - [x] [React](https://facebook.github.io/react/) 16.8 8 | - [x] [React Router](https://github.com/ReactTraining/react-router) 16.8 9 | - [x] [Mobx State Tree](https://github.com/mobxjs/mobx-state-tree) 3.14 10 | - [x] [Mobx React lite](https://github.com/mobxjs/mobx-react-lite) 1.4 11 | - [x] [Webpack](https://github.com/webpack/webpack) 4.35 12 | - [x] [Webpack Dev Server](https://github.com/webpack/webpack-dev-server) 3.7 13 | 14 | ## Setup 15 | ``` 16 | npm install 17 | cd src 18 | npm start 19 | ``` 20 | 21 | ## License 22 | MIT -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Boilerplate template! 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-typescript-mobx-state-tree-mobx-react-lite-boilerplate", 3 | "version": "1.0.0", 4 | "description": "Boilerplate: React project with Typescript + MST + mobx-react-lite, ES6 compilation and hot code reloading", 5 | "scripts": { 6 | "start": "node server.js" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/annajanicka/react-typescript-mobx-state-tree-mobx-react-lite-boilerplate.git" 11 | }, 12 | "keywords": [ 13 | "react", 14 | "reactjs", 15 | "boilerplate", 16 | "mobx", 17 | "starter-kit", 18 | "mobx-state-tree", 19 | "mobx-react-lite" 20 | ], 21 | "author": "Anna Janicka (http://github.com/annajanicka)", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/annajanicka/react-typescript-mobx-state-tree-mobx-react-lite-boilerplate/issues" 25 | }, 26 | "homepage": "https://github.com/annajanicka/react-typescript-mobx-state-tree-mobx-react-lite-boilerplate", 27 | "devDependencies": { 28 | "@types/react": "^16.8.22", 29 | "@types/react-dom": "^16.8.4", 30 | "@types/react-router-dom": "^4.3.4", 31 | "awesome-typescript-loader": "^5.2.1", 32 | "copy-webpack-plugin": "^5.0.3", 33 | "file-loader": "^4.0.0", 34 | "mobx-devtools-mst": "^0.9.21", 35 | "typescript": "^3.5.2", 36 | "webpack": "^4.35.2", 37 | "webpack-dev-server": "^3.7.2" 38 | }, 39 | "dependencies": { 40 | "@types/mobx-devtools-mst": "^0.9.0", 41 | "mobx": "^5.10.1", 42 | "mobx-react-lite": "^1.4.1", 43 | "mobx-state-tree": "^3.14.0", 44 | "react": "^16.8.6", 45 | "react-dom": "^16.8.6", 46 | "react-router-dom": "^5.0.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | publicPath: config.output.publicPath, 7 | hot: false, 8 | historyApiFallback: true 9 | }).listen(3000, 'localhost', function (err, result) { 10 | if (err) { 11 | console.log(err); 12 | } 13 | 14 | console.log('Listening at localhost:3000'); 15 | }); 16 | -------------------------------------------------------------------------------- /src/api/posts.api.json: -------------------------------------------------------------------------------- 1 | { 2 | "total": 1, 3 | "data": [ 4 | { 5 | "title": "Hello World", 6 | "date": "2019-04-11T03:11:29.904Z" 7 | }, 8 | { 9 | "title": "Another post", 10 | "date": "2019-05-11T03:11:29.904Z" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Layout from "./layout"; 3 | import StoreProvider from "./tools/StoreProvider"; 4 | 5 | export interface IAppProps { 6 | router: React.ComponentType; 7 | // tslint:disable-next-line:no-any 8 | routes: React.ComponentType; 9 | } 10 | 11 | const App: React.FC = ({ router, routes }) => ( 12 | 13 | 14 | 15 | ); 16 | 17 | export default App; -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { BrowserRouter, RouteComponentProps } from 'react-router-dom'; 4 | import Routes from './routes'; 5 | import App from './app'; 6 | 7 | let routes: React.ComponentType> = Routes; 8 | 9 | ReactDOM.render( 10 | , 11 | document.getElementById('root') as HTMLElement 12 | ); 13 | -------------------------------------------------------------------------------- /src/layout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Footer from './shared/footer'; 3 | import About from './shared/about'; 4 | 5 | export interface ILayoutProps { 6 | router: React.ComponentType; 7 | // tslint:disable-next-line:no-any 8 | routes: React.ComponentType; 9 | } 10 | 11 | class Layout extends React.Component { 12 | render() { 13 | const Router = this.props.router; 14 | const Routes = this.props.routes; 15 | 16 | return ( 17 |
18 | 19 | 20 | 21 |
22 |
23 | ) 24 | } 25 | }; 26 | 27 | export default Layout; -------------------------------------------------------------------------------- /src/pages/homePage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { PostModelType } from '../stores/posts.store'; 3 | import About from '../shared/about'; 4 | import { useRootData } from '../tools/useRootData'; 5 | 6 | export const HomePageView: React.FC<{ load: any, posts: Array }> = ({ posts, load }) => 7 |
8 | 9 | {posts.length === 0 && } 10 | {!!posts.length &&

List of posts

} 11 |
    12 | {posts.map((post: any) =>
  • Title: {post.title}

    Date: {post.date}

  • )} 13 |
14 |
15 | 16 | export const HomePage = () => { 17 | const { posts, load } = useRootData(store => ({ 18 | posts: store.posts.data.toJS(), 19 | load: store.posts.load 20 | })); 21 | return 22 | } 23 | 24 | export default HomePage; 25 | -------------------------------------------------------------------------------- /src/routes.tsx: -------------------------------------------------------------------------------- 1 | import { Switch, Route } from "react-router"; 2 | import * as React from 'react'; 3 | import HomePage from "./pages/homePage"; 4 | 5 | const Routes = () => 6 | 7 | 8 | 9 | 10 | export default Routes; -------------------------------------------------------------------------------- /src/shared/about.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useRootData } from '../tools/useRootData'; 3 | 4 | export const AboutView: React.FC<{ toggle: () => void, isVisible: boolean }> = ({ toggle, isVisible }) => { 5 | return ( 6 |
7 | {isVisible &&

This is a boilerplate project with

8 | 14 |
} 15 | 16 |
17 | ); 18 | } 19 | 20 | export const About = () => { 21 | const { toggle, isVisible } = useRootData(store => ({ 22 | toggle: store.about.toggle, 23 | isVisible: store.about.isVisible 24 | })); 25 | return 26 | } 27 | 28 | export default About; -------------------------------------------------------------------------------- /src/shared/footer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const Footer = () => ( 4 |
5 | ©{new Date().getFullYear()} Anna Janicka 6 |
7 | ); 8 | 9 | export default Footer; 10 | -------------------------------------------------------------------------------- /src/stores/about.store.tsx: -------------------------------------------------------------------------------- 1 | import { types, Instance } from "mobx-state-tree"; 2 | 3 | export interface IAboutModel extends Instance { }; 4 | 5 | export const AboutModel = types 6 | .model('AboutModel', { 7 | isVisible: types.optional(types.boolean, true) 8 | }) 9 | .actions(self => ({ 10 | toggle() { 11 | self.isVisible = !self.isVisible; 12 | } 13 | })); 14 | -------------------------------------------------------------------------------- /src/stores/posts.store.tsx: -------------------------------------------------------------------------------- 1 | import { types, Instance, flow } from "mobx-state-tree"; 2 | 3 | export interface IPostModel extends Instance { }; 4 | export type PostModelType = typeof PostModel.Type; 5 | 6 | export const PostModel = types 7 | .model('PostModel', { 8 | title: types.optional(types.string, ''), 9 | date: types.optional(types.string, '') 10 | }); 11 | 12 | export interface IPostsModel extends Instance { }; 13 | type PostsModelType = typeof PostsModel.Type; 14 | 15 | export const PostsModel = types 16 | .model('PostsModel', { 17 | total: types.optional(types.number, 0), 18 | data: types.array(types.frozen()) 19 | }) 20 | .actions(self => { 21 | const load = flow(function* () { 22 | let posts = yield fetch('http://localhost:3000/static/api/posts.api.json').then(d => d.json()); 23 | self.data = posts.data; 24 | }); 25 | return { 26 | load 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /src/stores/root.store.tsx: -------------------------------------------------------------------------------- 1 | import { types, Instance } from "mobx-state-tree" 2 | import { AboutModel } from "./about.store"; 3 | import { PostsModel } from "./posts.store"; 4 | 5 | export interface IRootStoreModel extends Instance { }; 6 | export type RootStoreType = typeof RootStore.Type; 7 | 8 | export const RootStore = types.model('RootStore', { 9 | posts: types.optional(PostsModel, {}), 10 | about: types.optional(AboutModel, {}) 11 | }); 12 | 13 | export const getDetaultStore = () => RootStore.create({}); 14 | -------------------------------------------------------------------------------- /src/tools/StoreProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useLocalStore } from 'mobx-react-lite'; 3 | import makeInspectable from 'mobx-devtools-mst'; 4 | import { RootStoreType, getDetaultStore } from '../stores/root.store'; 5 | 6 | export const storeContext = React.createContext(null); 7 | 8 | export const StoreProvider: React.FC = ({ children }) => { 9 | const store = useLocalStore(getDetaultStore); 10 | makeInspectable(store); 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | }; 17 | 18 | export default StoreProvider; -------------------------------------------------------------------------------- /src/tools/useRootData.ts: -------------------------------------------------------------------------------- 1 | import { storeContext } from "./StoreProvider"; 2 | import { RootStoreType } from "../stores/root.store"; 3 | import { useStoreData } from "./useStoreData"; 4 | 5 | export const useRootData = (dataSelector: (store: RootStoreType) => Selection) => 6 | useStoreData(storeContext, contextData => contextData!, dataSelector); -------------------------------------------------------------------------------- /src/tools/useStoreData.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useObserver } from 'mobx-react-lite'; 3 | 4 | export const useStoreData = ( 5 | context: React.Context, 6 | storeSelector: (contextData: ContextData) => Store, 7 | dataSelector: (store: Store) => Selection 8 | ) => { 9 | const value = React.useContext(context); 10 | if (!value) { 11 | console.log('React context does not exits!') 12 | throw new Error(); 13 | } 14 | const store = storeSelector(value); 15 | return useObserver(() => dataSelector(store)); 16 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "removeComments": true, 5 | "experimentalDecorators": true, 6 | "jsx": "react", 7 | "outDir": "dist", 8 | "lib": ["dom", "es2015"], 9 | "sourceMap": true, 10 | "strict": true, 11 | "strictNullChecks": true, 12 | "noImplicitReturns": true, 13 | "noImplicitThis": true, 14 | "noImplicitAny": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "skipLibCheck": true, 17 | "esModuleInterop":true 18 | }, 19 | "exclude": [ 20 | "node_modules", 21 | "static" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | const CopyPlugin = require('copy-webpack-plugin'); 4 | 5 | module.exports = { 6 | devtool: 'eval', 7 | mode: 'development', 8 | entry: [ 9 | 'webpack-dev-server/client?http://localhost:3000', 10 | './src/index' 11 | ], 12 | output: { 13 | path: path.join(__dirname, 'dist'), 14 | filename: 'bundle.js', 15 | publicPath: '/static/' 16 | }, 17 | resolve: { 18 | extensions: ['.js', '.ts', '.tsx'] 19 | }, 20 | module: { 21 | rules: [{ 22 | test: /\.tsx?$/, 23 | loader: "awesome-typescript-loader", 24 | include: path.join(__dirname, 'src') 25 | }] 26 | }, 27 | plugins: [ 28 | new CopyPlugin([ 29 | { 30 | from: path.join(__dirname, 'src/api'), 31 | to: path.join(__dirname, 'dist/api') 32 | 33 | }, 34 | ]), 35 | ], 36 | devtool: 'source-map' 37 | }; 38 | --------------------------------------------------------------------------------