├── .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 &&
load()}>Load posts }
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 | toggle()}>Toggle about
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 |
--------------------------------------------------------------------------------