├── public
├── asset
│ └── css
│ │ └── react.css
└── index.html
├── src
├── reduxStores
│ ├── configureStore.js
│ ├── reducers
│ │ ├── allReducers.js
│ │ └── videoReducers.js
│ ├── actions
│ │ └── allActions.js
│ ├── configureStore.dev.js
│ └── configureStore.prod.js
├── constants
│ ├── config.js
│ └── axiosApi.js
├── components
│ ├── footer
│ │ ├── RelatedFiles.js
│ │ └── FooterPresent.js
│ ├── reduxApp
│ │ ├── ReduxRelatedFiles.js
│ │ ├── VideoPreview.js
│ │ ├── VideoPlay.js
│ │ ├── VideoPlayButtons.js
│ │ ├── ReduxApp.js
│ │ ├── VideoSearchResults.js
│ │ ├── ReduxPlayedVideos.js
│ │ ├── ReduxLikedVideos.js
│ │ ├── ReduxAllVideos.js
│ │ └── VideoSearchForm.js
│ ├── websiteApp
│ │ ├── WebsiteAppFileContent.js
│ │ ├── WebsiteAppResults.js
│ │ ├── WebsiteAppPresent.js
│ │ └── WebsiteApp.js
│ ├── axiosApp
│ │ ├── AxiosAppResults.js
│ │ ├── AxiosAppPresent.js
│ │ └── AxiosApp.js
│ ├── basicApp
│ │ ├── BasicAppPresent.js
│ │ └── BasicApp.js
│ ├── header
│ │ └── HeaderPresent.js
│ └── hookApp
│ │ └── BasicHooks.js
├── middlewares
│ ├── sagaApiServices.js
│ └── saga.js
└── reactApp.js
├── .babelrc
├── .styleci.yml
├── .gitignore
├── doc
└── websiteApp
│ ├── 0.websiteConfig.json
│ ├── 3.Code-examples.html
│ ├── 1.What-included-in-this-starter.html
│ └── 2.How-to-install-this-starter.html
├── .editorconfig
├── .eslintrc.json
├── webpack.config.js
├── package.json
└── readme.md
/public/asset/css/react.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: azure;
3 | color: rgb(20, 20, 20);
4 | }
5 |
6 | .redux-buttons a.active {
7 | color: yellow !important;
8 | }
9 |
--------------------------------------------------------------------------------
/src/reduxStores/configureStore.js:
--------------------------------------------------------------------------------
1 | if (process.env.NODE_ENV === "production") {
2 | module.exports = require("./configureStore.prod");
3 | } else {
4 | module.exports = require("./configureStore.prod");
5 | }
6 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/env", {
4 | "useBuiltIns": false,
5 | }],
6 | "@babel/react"
7 | ],
8 | "plugins": [
9 | "@babel/plugin-proposal-class-properties",
10 | "@babel/plugin-transform-runtime"
11 | ]
12 | }
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: laravel
2 | risky: false
3 | enabled:
4 | - align_double_arrow
5 | - align_equals
6 | disabled:
7 | - unused_use
8 | - unalign_equals
9 | finder:
10 | exclude:
11 | - modules
12 | - node_modules
13 | - storage
14 | - vendor
15 | name: "*.php"
16 | not-name: "*.blade.php"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 |
7 | # misc
8 | .DS_Store
9 | .env.local
10 | .env.development.local
11 | .env.test.local
12 | .env.production.local
13 |
14 | npm-debug.log*
15 | yarn-debug.log*
16 | yarn-error.log*
--------------------------------------------------------------------------------
/src/reduxStores/reducers/allReducers.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import { combineReducers } from "redux";
4 | import { connectRouter } from "connected-react-router";
5 |
6 | import videoReducers from "./videoReducers";
7 |
8 | export default (storeHistory) =>
9 | combineReducers({
10 | router: connectRouter(storeHistory),
11 | videos: videoReducers,
12 | });
13 |
--------------------------------------------------------------------------------
/doc/websiteApp/0.websiteConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "siteName": "Simple website using Github as backend",
3 | "siteDescription": "Want built a simple ReactJs website? Why not use Github to maintain all the contents and use it as the backend API or database. This example list all .html files from your public Github repository through Github RESTful API",
4 | "pageTitleClass": "text-info"
5 | }
6 |
--------------------------------------------------------------------------------
/doc/websiteApp/3.Code-examples.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | ReactJs code examples:
4 |
5 |
6 | Online DEMO https://alexstack.github.io/reactStarter/public/
7 | Basic ReactJS example: using React state and components only
8 | Axios RESTful API search example: A form using axios to implement a RESTful API search
9 | Redux Saga reducer example:
10 | Hooks example:
11 |
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # For more information about the properties used in this file,
2 | # please see the EditorConfig documentation:
3 | # http://editorconfig.org
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | indent_size = 4
9 | indent_style = space
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.{yml,js,json,css,scss,feature,eslintrc}]
14 | indent_size = 2
15 | indent_style = space
16 |
17 | [composer.json]
18 | indent_size = 4
19 |
--------------------------------------------------------------------------------
/src/constants/config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | export const REACTPATH = {
3 | Basic: "/basic1",
4 | Axios: "/axios2",
5 | Redux: "/redux3",
6 | Website: "/document",
7 | BasicHooks: "/basicHooks",
8 | CustomHooks: "customHooks",
9 | };
10 |
11 | export const REDUXPATH = {
12 | LikedVideos: "/LikedVideos2",
13 | PlayedVideos: "/PlayedVideos5",
14 | SearchVideos: "/SearchVideos3",
15 | };
16 |
17 | export const myLog = (var1, var2, var3) => {
18 | console.log(var1, var2, var3);
19 | };
20 |
21 | export const lastPath = (prefix = "/") =>
22 | prefix + document.location.pathname.split("/").slice(-1)[0];
23 |
--------------------------------------------------------------------------------
/doc/websiteApp/1.What-included-in-this-starter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | What included?
4 |
5 | Lots of React required packages, such as react-router-dom, axios, redux, saga, hook, webpack ... You do not
6 | need
7 | install
8 | them again, use them directly in your code.
9 | Using webpack to export the compiled js file, tidy and clean and flexible.
10 | npm run dev will automatically open a local dev demo page which can detect js file changes and show the result
11 | immediately.
12 | There are some code examples for demo and reuse purpose. We can easy change them to fit our requirements.
13 |
14 |
--------------------------------------------------------------------------------
/src/components/footer/RelatedFiles.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | const RelatedFiles = (props) => {
5 | return (
6 |
7 |
8 |
9 | Related js files for this example
10 |
11 |
12 | {props.children}
13 |
14 |
15 | You can edit above files to see the changes here immediately
16 |
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | // RelatedFiles.propTypes = {};
24 | export default RelatedFiles;
25 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true,
5 | "es6": true
6 | },
7 | "parser": "babel-eslint",
8 | "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:prettier/recommended"],
9 | "globals": {
10 | "Atomics": "readonly",
11 | "SharedArrayBuffer": "readonly"
12 | },
13 | "parserOptions": {
14 | "ecmaFeatures": {
15 | "jsx": true
16 | },
17 | "ecmaVersion": 2018,
18 | "sourceType": "module"
19 | },
20 | "plugins": [
21 | "react",
22 | "prettier"
23 | ],
24 | "rules": {
25 | "strict": 0,
26 | "valid-jsdoc": 2,
27 | "react/jsx-uses-react": 2,
28 | "react/jsx-uses-vars": 2,
29 | "react/react-in-jsx-scope": 2,
30 | "react/jsx-filename-extension": [1, {
31 | "extensions": [".js", ".jsx"]
32 | }]
33 | }
34 | }
--------------------------------------------------------------------------------
/src/components/reduxApp/ReduxRelatedFiles.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | import React, { Component } from "react";
3 | import RelatedFiles from "../footer/RelatedFiles";
4 |
5 | const ReduxRelatedFiles = () => (
6 |
7 |
8 |
9 | src/components/reduxApp/ReduxApp.js --- React-Redux main entry point
10 |
11 |
12 | src/components/reduxApp/*.js --- React Components
13 |
14 |
15 | src/reduxStores/*.js --- Redux configuration & actions & reducers
16 |
17 |
18 | src/middlewares/*.js --- Redux middlewares: saga, api services
19 |
20 |
21 |
22 | );
23 | export default ReduxRelatedFiles;
24 |
--------------------------------------------------------------------------------
/src/components/footer/FooterPresent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | const FooterPresent = () => {
5 | return (
6 |
7 |
8 | Page loaded at {new Date().toLocaleDateString()} -
9 | {new Date().toLocaleTimeString()}
10 |
20 |
21 |
22 | );
23 | };
24 |
25 | FooterPresent.propTypes = {};
26 | export default FooterPresent;
27 |
--------------------------------------------------------------------------------
/src/reduxStores/actions/allActions.js:
--------------------------------------------------------------------------------
1 | import { createActions } from "reduxsauce";
2 |
3 | /* ------------- Types and Action Creators ------------- */
4 |
5 | const { Types, Creators } = createActions({
6 | listItemRequest: ["keyword", "nextPageToken"],
7 | listItemSuccess: ["allData", "keyword", "nextPageToken"],
8 | listItemFailure: ["error"],
9 |
10 | viewItemRequest: ["item"],
11 | viewItemSuccess: ["backendData", "item"],
12 | viewItemFailure: ["error"],
13 |
14 | likeItemRequest: ["item"],
15 | likeItemSuccess: ["backendData", "item"],
16 | likeItemFailure: ["error"],
17 |
18 | unlikeItemRequest: ["item"],
19 | unlikeItemSuccess: ["backendData", "item"],
20 | unlikeItemFailure: ["error"],
21 |
22 | deleteItemRequest: ["id"],
23 | deleteItemSuccess: ["id", "page"],
24 | deleteItemFailure: ["error"],
25 |
26 | setFilterKeyRequest: ["value"],
27 | setFilterKeySuccess: ["value"],
28 | setFilterKeyFailure: ["error"],
29 |
30 | showLoading: ["loading", "nextPageToken"],
31 | });
32 |
33 | export const actionTypes = Types;
34 | export const taskTypes = Types;
35 | export default Creators;
36 |
--------------------------------------------------------------------------------
/src/components/reduxApp/VideoPreview.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import VideoPlayButtons from "./VideoPlayButtons";
4 |
5 | const VideoPreview = ({ item, handleFieldChange, index, wasLiked }) => {
6 | return (
7 |
8 |
16 |
17 |
18 |
19 | {index + 1}
20 | {item.snippet.title}
21 |
22 |
28 |
29 |
30 | );
31 | };
32 |
33 | VideoPreview.propTypes = {
34 | item: PropTypes.object,
35 | handleFieldChange: PropTypes.func,
36 | };
37 |
38 | export default VideoPreview;
39 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 | const { CleanWebpackPlugin } = require("clean-webpack-plugin");
4 |
5 | module.exports = {
6 | // mode: "development",
7 | entry: "./src/reactApp.js",
8 | output: {
9 | path: path.resolve("public/asset/js"),
10 | filename: "reactApp.js",
11 | },
12 | // define babel loader
13 | module: {
14 | rules: [
15 | {
16 | test: /\.jsx?$/,
17 | loader: "babel-loader",
18 | // use: ["babel-loader", "style-loader", "css-loader"],
19 | exclude: /node_modules/,
20 | },
21 | ],
22 | },
23 | devtool: "inline-source-map",
24 | devServer: {
25 | historyApiFallback: true,
26 | // contentBase: "public",
27 | compress: false,
28 | // inline: true,
29 | // progress: true,
30 | hot: true,
31 | port: 9999,
32 | },
33 | plugins: [
34 | new CleanWebpackPlugin(),
35 | new HtmlWebpackPlugin({
36 | title: "Hot Module Replacement",
37 | template: "public/index.html",
38 | }),
39 | ],
40 | resolve: {
41 | extensions: [".vue", ".js", ".css", "jsx"],
42 | },
43 | };
44 |
--------------------------------------------------------------------------------
/doc/websiteApp/2.How-to-install-this-starter.html:
--------------------------------------------------------------------------------
1 |
2 |
How to install
3 |
4 |
5 |
6 | git clone https://github.com/AlexStack/React-Hook-Redux-Saga-Webpack-Starter.git reactApp
7 |
8 |
9 | cd reactApp
10 |
11 |
12 | npm install --- This will install all related react packages in 3 minutes. You need install npm first if you
13 | never use npm before. The npm can be download here
14 |
15 |
16 | npm run dev --- Now you will see a local ReactJs demo page opened in your browser: http://localhost:9999/ -
17 | this
18 | dev demo page will automatically refresh when it detect any react js code changes
19 |
20 |
21 |
22 |
23 | npm run build --- This will build the production js file into public/js/reactApp.js
24 |
25 |
26 |
27 | Start from scratch : Edit or delete all files in the src folder. The main entry point file is
28 | src/reactApp.js
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/components/reduxApp/VideoPlay.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import VideoPlayButtons from "./VideoPlayButtons";
4 |
5 | const VideoPlay = ({ item, handleFieldChange, index, wasLiked }) => {
6 | return (
7 |
8 |
9 | VIDEO
16 |
17 |
18 |
19 |
20 | {index + 1}
21 | {item.snippet.title}
22 |
23 |
24 |
30 |
31 |
32 | );
33 | };
34 |
35 | VideoPlay.propTypes = {
36 | item: PropTypes.object,
37 | handleFieldChange: PropTypes.func,
38 | };
39 |
40 | export default VideoPlay;
41 |
--------------------------------------------------------------------------------
/src/middlewares/sagaApiServices.js:
--------------------------------------------------------------------------------
1 | import { youtubeApi, backendApi } from "../constants/axiosApi";
2 |
3 | const listItem = (keyword, nextPageToken) => {
4 | return youtubeApi.get(`/search`, {
5 | params: {
6 | part: "snippet",
7 | order: "rating", // date, viewCount
8 | q: keyword,
9 | type: "video",
10 | videoDefinition: "high",
11 | maxResults: 10,
12 | pageToken: nextPageToken,
13 | },
14 | });
15 | };
16 |
17 | const viewItem = (item) => {
18 | return true;
19 | };
20 |
21 | const likeItem = (item) => {
22 | return backendApi.post(`/videos`, {
23 | method: "POST",
24 | params: {
25 | videoId: item.id.videoId,
26 | userId: 1,
27 | },
28 | });
29 | };
30 |
31 | const unlikeItem = (item) => {
32 | return backendApi.delete(`/videos/${item.id.videoId}`, {
33 | method: "DELETE",
34 | params: {
35 | videoId: item.id.videoId,
36 | userId: 1,
37 | },
38 | });
39 | };
40 |
41 | const deleteItem = (id) => {
42 | return backendApi.delete(`/videos/${id}`, {
43 | method: "DELETE",
44 | params: {
45 | videoId: id,
46 | userId: 1,
47 | },
48 | });
49 | };
50 |
51 | const ApiMethods = {
52 | listItem,
53 | viewItem,
54 | likeItem,
55 | unlikeItem,
56 | deleteItem,
57 | };
58 |
59 | export default ApiMethods;
60 |
--------------------------------------------------------------------------------
/src/components/websiteApp/WebsiteAppFileContent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | const WebsiteAppFileContent = ({
5 | total,
6 | fileContent,
7 | handleFieldChange,
8 | loading,
9 | }) => {
10 | return (
11 |
12 |
18 | Return to list
19 | {loading && }
20 |
21 |
22 |
28 |
29 |
30 |
36 | Return to list
37 | {loading && }
38 |
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | WebsiteAppFileContent.propTypes = {
46 | total: PropTypes.number,
47 | fileContent: PropTypes.string,
48 | handleFieldChange: PropTypes.func,
49 | loading: PropTypes.bool,
50 | };
51 |
52 | export default WebsiteAppFileContent;
53 |
--------------------------------------------------------------------------------
/src/reactApp.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
4 | import BasicApp from "./components/basicApp/BasicApp";
5 | import AxiosApp from "./components/axiosApp/AxiosApp";
6 | import WebsiteApp from "./components/websiteApp/WebsiteApp";
7 | import HeaderPresent from "./components/header/HeaderPresent";
8 | import FooterPresent from "./components/footer/FooterPresent";
9 | import ReduxApp from "./components/reduxApp/ReduxApp";
10 | import BasicHooks from "./components/hookApp/BasicHooks";
11 | import { REACTPATH } from "./constants/config";
12 |
13 | export default class ReactApp extends React.Component {
14 | render() {
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
34 | if (document.getElementById("react-js-basic-app")) {
35 | ReactDOM.render( , document.getElementById("react-js-basic-app"));
36 | }
37 |
--------------------------------------------------------------------------------
/src/reduxStores/configureStore.dev.js:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory } from "history";
2 | import { createStore, applyMiddleware, compose } from "redux";
3 | import { createLogger } from "redux-logger";
4 | import createRootReducer from "../reduxStores/reducers/allReducers";
5 | import { routerMiddleware } from "connected-react-router";
6 | import { persistStore, persistReducer } from "redux-persist";
7 | import storage from "redux-persist/lib/storage";
8 | import { REACTPATH } from "../constants/config";
9 |
10 | export const history = createBrowserHistory({ basename: REACTPATH.Redux });
11 |
12 | const initialState = {
13 | //router: null,
14 | // videos: null,
15 | };
16 |
17 | const persistConfig = {
18 | key: "root",
19 | storage,
20 | /**
21 | * Blacklist state that we do not need/want to persist
22 | */
23 | blacklist: ["router"],
24 | };
25 |
26 | const configureStore = (sagaMiddleware, storeHistory) => {
27 | //const sagaMiddleware = createSagaMiddleware()
28 | const reducers = createRootReducer(storeHistory);
29 | // console.log(reducers.router);
30 | const store = createStore(
31 | persistReducer(persistConfig, reducers), // root reducer with router state
32 | initialState,
33 | compose(
34 | applyMiddleware(
35 | routerMiddleware(storeHistory), // for dispatching history actions
36 | sagaMiddleware,
37 | createLogger()
38 | )
39 | // window.__REDUX_DEVTOOLS_EXTENSION__ &&
40 | // window.__REDUX_DEVTOOLS_EXTENSION__()
41 | //DevTools.instrument()
42 | )
43 | );
44 | //sagaMiddleware.run(rootSaga)
45 | const persistor = persistStore(store);
46 |
47 | return { store, persistor };
48 | };
49 |
50 | export default configureStore;
51 |
--------------------------------------------------------------------------------
/src/reduxStores/configureStore.prod.js:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory } from "history";
2 | import { createStore, applyMiddleware, compose } from "redux";
3 | //import { createLogger } from "redux-logger";
4 | import createRootReducer from "./reducers/allReducers";
5 | import { routerMiddleware } from "connected-react-router";
6 | import { persistStore, persistReducer } from "redux-persist";
7 | import storage from "redux-persist/lib/storage";
8 | import { REACTPATH } from "../constants/config";
9 |
10 | export const history = createBrowserHistory({ basename: REACTPATH.Redux });
11 |
12 | const initialState = {
13 | //router: null,
14 | assignedTasks: [],
15 | login: {},
16 | myOwnTasks: {},
17 | };
18 |
19 | const persistConfig = {
20 | key: "root",
21 | storage,
22 | /**
23 | * Blacklist state that we do not need/want to persist
24 | */
25 | blacklist: ["router"],
26 | };
27 |
28 | const configureStore = (sagaMiddleware, storeHistory) => {
29 | //const sagaMiddleware = createSagaMiddleware()
30 | const reducers = createRootReducer(storeHistory);
31 | //console.log(reducers.router);
32 | const store = createStore(
33 | persistReducer(persistConfig, reducers), // root reducer with router state
34 | initialState,
35 | compose(
36 | applyMiddleware(
37 | routerMiddleware(storeHistory), // for dispatching history actions
38 | sagaMiddleware
39 | //createLogger()
40 | )
41 | // window.__REDUX_DEVTOOLS_EXTENSION__ &&
42 | // window.__REDUX_DEVTOOLS_EXTENSION__()
43 | //DevTools.instrument()
44 | )
45 | );
46 | //sagaMiddleware.run(rootSaga)
47 | const persistor = persistStore(store);
48 |
49 | return { store, persistor };
50 | };
51 |
52 | export default configureStore;
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-hook-redux-starter",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "webpack.config.js",
6 | "dependencies": {
7 | "axios": "^0.21.2",
8 | "babel-preset-env": "^1.7.0",
9 | "connected-react-router": "^6.8.0",
10 | "js-base64": "^2.6.4",
11 | "react": "^16.13.1",
12 | "react-dom": "^16.13.1",
13 | "react-hot-loader": "^4.13.0",
14 | "react-redux": "^7.2.1",
15 | "react-router-dom": "^5.2.0",
16 | "redux": "^4.0.5",
17 | "redux-logger": "^3.0.6",
18 | "redux-persist": "^6.0.0",
19 | "redux-saga": "^1.1.3",
20 | "reduxsauce": "^1.2.0",
21 | "webpack-cli": "^4.9.1",
22 | "webpack-dev-server": "^4.6.0"
23 | },
24 | "devDependencies": {
25 | "@babel/core": "^7.11.6",
26 | "@babel/plugin-transform-runtime": "^7.11.5",
27 | "@babel/preset-env": "^7.11.5",
28 | "@babel/preset-react": "^7.10.4",
29 | "babel-core": "^6.26.3",
30 | "babel-loader": "^8.1.0",
31 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
32 | "babel-plugin-transform-runtime": "^6.23.0",
33 | "babel-preset-es2015": "^6.24.1",
34 | "babel-preset-react": "^6.24.1",
35 | "babel-runtime": "^6.26.0",
36 | "clean-webpack-plugin": "^3.0.0",
37 | "css-loader": "^5.2.6",
38 | "html-webpack-plugin": "^4.5.0",
39 | "mini-css-extract-plugin": "^0.9.0",
40 | "node-forge": ">=0.10.0",
41 | "style-loader": "^1.2.1",
42 | "webpack": "^5.65.0"
43 | },
44 | "scripts": {
45 | "test": "echo \"Error: no test specified\" && exit 1",
46 | "dev": "webpack-dev-server --open --hot --mode development",
47 | "watch": "webpack --watch",
48 | "build": "webpack --mode production"
49 | },
50 | "author": "",
51 | "license": "ISC"
52 | }
53 |
--------------------------------------------------------------------------------
/src/components/axiosApp/AxiosAppResults.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | const AxiosAppResults = ({
5 | total,
6 | searchResults,
7 | handleFieldChange,
8 | loading,
9 | }) => {
10 | return (
11 | <>
12 | {searchResults.length > 0 &&
13 | searchResults.map((item, index) => (
14 |
15 |
25 |
{item.description}
26 |
27 | ))}
28 | {total > 0 && total > searchResults.length && (
29 |
35 | Load More Data
36 | {loading && }
37 |
38 | )}
39 | {/* Show additional information, pages, total */}
40 | {total === 0 && No result
}
41 | {total > 0 && (
42 |
43 | Total results: {total} - Displaying {searchResults.length} of {total}
44 |
45 | )}
46 | >
47 | );
48 | };
49 |
50 | AxiosAppResults.propTypes = {
51 | total: PropTypes.number,
52 | searchResults: PropTypes.array,
53 | handleFieldChange: PropTypes.func,
54 | loading: PropTypes.bool,
55 | };
56 |
57 | export default AxiosAppResults;
58 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # React-Hook-Redux-Saga-Webpack-Starter
2 |
3 | - Create a new ReactJs project without use Create-React-App. Popular packages such as Axios,Redux, Saga, Hooks are ready to use immediately. Compile codes by webpack and can hot reload code changes - it will automatically refresh the dev page. save your time!
4 |
5 | ## What included?
6 |
7 | - Lots of React required packages, such as react-router-dom, axios, redux, saga, hook, webpack ... You do not need install them again, use them directly in your code.
8 | - Using webpack to export the compiled js file, tidy and clean and flexible.
9 | - **npm run dev** will automatically open a local dev demo page which can detect js file changes and show the result immediately.
10 | - There are some code examples for demo and reuse purpose. We can easy change them to fit our requirements.
11 |
12 | ## ReactJs code examples:
13 |
14 | - **Online DEMO** https://alexstack.github.io/reactStarter/
15 | - Basic ReactJS example: using React state and components only
16 | - Axios RESTful API search example: A form using axios to implement a RESTful API search
17 | - Redux Saga reducer example:
18 | - Hooks example:
19 |
20 | ## How to use?
21 |
22 | - git clone https://github.com/AlexStack/React-Hook-Redux-Saga-Webpack-Starter.git reactApp
23 | - cd reactApp
24 | - **npm install** --- This will install all related react packages in 3 minutes. You need install npm first if you never use npm before. The npm can be [download here](https://nodejs.org/en/download/)
25 | - **npm run dev** --- Now you will see a local ReactJs demo page opened in your browser: http://localhost:9999/ - this dev demo page will automatically refresh when it detect any react js code changes
26 | - **npm run build** --- This will build the production js file into public/js/reactApp.js
27 | - Start from scratch : Edit or delete all files in the src folder. The main entry point file is src/reactApp.js
28 |
--------------------------------------------------------------------------------
/src/components/reduxApp/VideoPlayButtons.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Link } from "react-router-dom";
4 | import { REDUXPATH, lastPath } from "../../constants/config";
5 |
6 | const VideoPlayButtons = ({
7 | isPlaying,
8 | wasLiked,
9 | handleFieldChange,
10 | index,
11 | }) => {
12 | return (
13 |
14 | {!isPlaying && (
15 |
21 | Play Video
22 |
23 | )}
24 |
25 | {wasLiked ? (
26 | <>
27 | {lastPath() != REDUXPATH.LikedVideos && (
28 |
33 | All Liked Videos
34 |
35 | )}
36 |
37 |
43 | Unlike
44 |
45 | >
46 | ) : (
47 |
53 | Like This Video
54 |
55 | )}
56 |
57 | );
58 | };
59 |
60 | VideoPlayButtons.propTypes = {
61 | isPlaying: PropTypes.bool,
62 | wasLiked: PropTypes.bool,
63 | handleFieldChange: PropTypes.func,
64 | index: PropTypes.number,
65 | };
66 |
67 | export default VideoPlayButtons;
68 |
--------------------------------------------------------------------------------
/src/components/reduxApp/ReduxApp.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import React from "react";
3 | import PropTypes from "prop-types";
4 |
5 | import { Provider } from "react-redux";
6 | import { Route, Switch } from "react-router-dom";
7 | import { ConnectedRouter } from "connected-react-router";
8 | import { PersistGate } from "redux-persist/integration/react";
9 | import configureStore, {
10 | history as storeHistory,
11 | } from "../../reduxStores/configureStore";
12 | import rootSaga from "../../middlewares/saga";
13 | import createSagaMiddleware from "redux-saga";
14 |
15 | import ReduxAllVideos from "./ReduxAllVideos";
16 | import ReduxLikedVideos from "./ReduxLikedVideos";
17 | import ReduxPlayedVideos from "./ReduxPlayedVideos";
18 | import ReduxRelatedFiles from "./ReduxRelatedFiles";
19 | import { REDUXPATH } from "../../constants/config";
20 |
21 | const sagaMiddleware = createSagaMiddleware();
22 | const { store, persistor } = configureStore(sagaMiddleware, storeHistory);
23 | sagaMiddleware.run(rootSaga);
24 |
25 | export default class ReduxApp extends React.Component {
26 | render() {
27 | return (
28 |
29 |
30 |
31 | {/* */}
32 |
33 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {/* */}
48 |
49 |
50 |
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/websiteApp/WebsiteAppResults.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | const WWebsiteAppResults = ({
5 | total,
6 | searchResults,
7 | handleFieldChange,
8 | loading,
9 | }) => {
10 | return (
11 | <>
12 | {searchResults.length > 0 &&
13 | searchResults.map((item, index) => (
14 |
15 |
16 | {index + 1} {" "}
17 |
23 | {item.name
24 | .replace(/-/g, " ")
25 | .replace(/.html/g, "")
26 | .replace(/(^\d+\.)/, "")}
27 |
28 |
29 |
30 | ))}
31 | {total > 0 && total > searchResults.length && (
32 |
38 | Display All Data
39 | {loading && }
40 |
41 | )}
42 |
43 | {/* Show additional information, pages, total */}
44 | {total === 0 && No result
}
45 | {total > 0 && searchResults.length != total && (
46 |
47 | Found {searchResults.length} result
48 | {searchResults.length > 1 && <>s>} in {total}
49 |
50 | )}
51 | >
52 | );
53 | };
54 |
55 | WWebsiteAppResults.propTypes = {
56 | total: PropTypes.number,
57 | searchResults: PropTypes.array,
58 | handleFieldChange: PropTypes.func,
59 | loading: PropTypes.bool,
60 | };
61 |
62 | export default WWebsiteAppResults;
63 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | AlexStack ReactJs Starter
8 |
9 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Loading the react example...
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
44 |
49 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/components/basicApp/BasicAppPresent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { REACTPATH, lastPath } from "../../constants/config";
4 |
5 | const BasicAppPresent = ({
6 | clickTimes,
7 | keyPressTimes,
8 | pressedKey,
9 | secretKey,
10 | handleFieldChange,
11 | }) => {
12 | return (
13 |
14 |
15 |
Secret key guess game!
16 |
17 | {REACTPATH.BasicHooks !== document.location.pathname ? (
18 |
19 | Basic ReactJS example: using React this.state and components only
20 |
21 | ) : (
22 |
23 | Basic Hooks example: DO NOT
24 | USE this.state and componentDidMount
25 |
26 | )}
27 |
28 |
29 | {secretKey
30 | ? "Secret key pressed, Game start now!"
31 | : "First person: Press a secret key first!"}
32 |
33 |
34 |
35 | Second person: Try to guess what is the secret key by press other keys
36 |
37 |
38 | {secretKey === pressedKey && (
39 | <>
40 |
Congratulation, you won!
41 |
42 | You've tried {keyPressTimes} times, your score is{" "}
43 | {100 - (keyPressTimes - 1) * 2}/100
44 |
45 | Press Esc key to start the game again! or
46 |
47 |
51 | Click here to restart
52 |
53 | >
54 | )}
55 | {secretKey && secretKey !== pressedKey && (
56 |
57 | Pressed Key: {pressedKey}
58 | Key Press Times: {keyPressTimes}
59 |
63 | Click here to restart
64 |
65 |
66 | )}
67 |
68 | );
69 | };
70 |
71 | BasicAppPresent.propTypes = {
72 | clickTimes: PropTypes.number,
73 | keyPressTimes: PropTypes.number,
74 | pressedKey: PropTypes.string,
75 | secretKey: PropTypes.string,
76 | handleFieldChange: PropTypes.func,
77 | };
78 | export default BasicAppPresent;
79 |
--------------------------------------------------------------------------------
/src/components/reduxApp/VideoSearchResults.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import VideoPreview from "./VideoPreview";
4 | import VideoPlay from "./VideoPlay";
5 |
6 | const VideoSearchResults = ({
7 | total,
8 | searchResults,
9 | likedItems,
10 | handleFieldChange,
11 | loading,
12 | playVideoId,
13 | }) => {
14 | return (
15 |
16 | {searchResults.length > 0 &&
17 | searchResults.map((item, index) => {
18 | const likedItem = likedItems.find(
19 | (obj) => obj.id.videoId == item.id.videoId
20 | );
21 | const wasLiked = likedItem ? true : false;
22 | return (
23 |
24 | {playVideoId !== item.id.videoId ? (
25 |
31 | ) : (
32 |
38 | )}
39 |
40 | );
41 | })}
42 |
43 | {total > 0 && total > searchResults.length && searchResults.length > 0 && (
44 |
45 |
51 | Load More Data
52 | {loading && }
53 |
54 |
55 | )}
56 | {/* Show additional information, pages, total */}
57 | {total === 0 && (
58 |
59 | No result
60 |
61 | )}
62 | {total > 0 && (
63 |
64 | Displaying{" "}
65 | {searchResults.length} of {total} Videos
66 |
67 | )}
68 |
69 | );
70 | };
71 |
72 | VideoSearchResults.propTypes = {
73 | total: PropTypes.number,
74 | searchResults: PropTypes.array,
75 | likedItems: PropTypes.array,
76 | handleFieldChange: PropTypes.func,
77 | loading: PropTypes.bool,
78 | playVideoId: PropTypes.string,
79 | };
80 |
81 | export default VideoSearchResults;
82 |
--------------------------------------------------------------------------------
/src/components/header/HeaderPresent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NavLink, Link } from "react-router-dom";
3 | import { REACTPATH, REDUXPATH } from "../../constants/config";
4 |
5 | const hideNavbar = (action) => {
6 | document.getElementById("collapsibleNavbar").classList.remove("show");
7 | };
8 |
9 | const HeaderPresent = () => {
10 | return (
11 |
12 |
ReactJS Starter
13 |
14 |
15 |
16 | React Examples
17 |
18 |
19 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | hideNavbar("")}
35 | >
36 | Basic React
37 |
38 |
39 |
40 | hideNavbar("")}
44 | >
45 | Axios API
46 |
47 |
48 |
49 |
50 | hideNavbar("")}
54 | >
55 | Redux Saga
56 |
57 |
58 |
59 |
60 | hideNavbar("")}
64 | >
65 | Basic Hooks
66 |
67 |
68 |
69 |
70 | hideNavbar("")}
74 | >
75 | Document
76 |
77 |
78 |
79 |
80 |
81 |
82 | );
83 | };
84 |
85 | export default HeaderPresent;
86 |
--------------------------------------------------------------------------------
/src/components/basicApp/BasicApp.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | import React, { Component } from "react";
3 | import PropTypes from "prop-types";
4 | import BasicAppPresent from "./BasicAppPresent";
5 | import RelatedFiles from "../footer/RelatedFiles";
6 |
7 | export default class BasicApp extends Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | clickTimes: 0,
13 | keyPressTimes: 0,
14 | pressedKey: "null",
15 | secretKey: null,
16 | };
17 |
18 | this.handleFieldChange = this.handleFieldChange.bind(this);
19 | this.handleKeyPress = this.handleKeyPress.bind(this);
20 | }
21 |
22 | componentDidMount() {
23 | console.log("componentDidMount", this.state);
24 | document.addEventListener("keydown", this.handleKeyPress, false);
25 | }
26 |
27 | componentDidUpdate() {
28 | console.log("componentDidUpdate", this.state);
29 | }
30 |
31 | handleFieldChange = (event) => {
32 | this.setState({
33 | clickTimes: this.state.clickTimes + 1,
34 | keyPressTimes: 0,
35 | pressedKey: "null",
36 | secretKey: null,
37 | });
38 | };
39 |
40 | handleKeyPress = (event) => {
41 | if (this.state.secretKey === null) {
42 | this.setState({
43 | secretKey: event.key,
44 | });
45 | } else if (event.key === "Escape") {
46 | this.setState({
47 | clickTimes: 0,
48 | keyPressTimes: 0,
49 | pressedKey: "null",
50 | secretKey: null,
51 | });
52 | } else if (this.state.pressedKey === this.state.secretKey) {
53 | // do nothing
54 | } else {
55 | this.setState({
56 | pressedKey: event.key,
57 | keyPressTimes: this.state.keyPressTimes + 1,
58 | });
59 | }
60 |
61 | console.log("key pressed event:", event);
62 | };
63 |
64 | render() {
65 | return (
66 |
67 |
74 |
75 |
76 |
77 | src/reactApp.js --- React main entry point
78 |
79 |
80 | src/components/BasicApp/*.js --- React Components
81 |
82 |
83 | src/components/header/*.js --- Reusable Header Components
84 |
85 |
86 | src/components/footer/*.js --- Reusable Footer Components
87 |
88 |
89 |
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/middlewares/saga.js:
--------------------------------------------------------------------------------
1 | import { call, put, takeLatest } from "redux-saga/effects";
2 | import sagaApi from "./sagaApiServices";
3 | // import { push } from "connected-react-router";
4 | import allActions, { actionTypes } from "../reduxStores/actions/allActions";
5 | import { myLog } from "../constants/config";
6 |
7 | function* listItem(action) {
8 | yield put(allActions.showLoading(true, action.nextPageToken));
9 |
10 | try {
11 | const json = yield call(
12 | sagaApi.listItem,
13 | action.keyword,
14 | action.nextPageToken
15 | );
16 | console.log("listItem-mylog1", action.type, json);
17 | yield put(
18 | allActions.listItemSuccess(
19 | json.data,
20 | action.keyword,
21 | action.nextPageToken
22 | )
23 | );
24 | } catch (e) {
25 | yield put(allActions.listItemFailure(e.message));
26 | }
27 | }
28 |
29 | function* viewItem(action) {
30 | try {
31 | const json = yield call(sagaApi.viewItem, action.item);
32 | console.log("viewItem-mylog2", action.type, json);
33 | yield put(allActions.viewItemSuccess(json.data, action.item));
34 | } catch (e) {
35 | yield put(allActions.viewItemFailure(e.message));
36 | }
37 | }
38 |
39 | function* likeItem(action) {
40 | try {
41 | const json = yield call(sagaApi.likeItem, action.item);
42 | console.log("likeItem-mylog3", action.type, json);
43 | yield put(allActions.likeItemSuccess(json.data, action.item));
44 | } catch (e) {
45 | yield put(allActions.likeItemFailure(e.message));
46 | }
47 | }
48 |
49 | function* unlikeItem(action) {
50 | try {
51 | const json = yield call(sagaApi.unlikeItem, action.item);
52 | console.log("unlikeItem-mylog3", action.type, json);
53 | yield put(allActions.unlikeItemSuccess(json.data, action.item));
54 | } catch (e) {
55 | yield put(allActions.unlikeItemFailure(e.message));
56 | }
57 | }
58 |
59 | function* deleteItem(action) {
60 | try {
61 | const json = yield call(sagaApi.deleteItem, action.id);
62 | myLog(action, json);
63 | yield put(allActions.deleteItemSuccess(action.id));
64 | // yield put(push("/listPage"));
65 | } catch (e) {
66 | yield put(allActions.deleteItemFailure(e.message));
67 | }
68 | }
69 |
70 | function* setFilterKey(action) {
71 | try {
72 | yield put(allActions.setFilterKeySuccess(action.value));
73 | } catch (e) {
74 | yield put(allActions.setFilterKeyFailure(e.message));
75 | }
76 | }
77 |
78 | function* rootSaga() {
79 | yield takeLatest(actionTypes.LIST_ITEM_REQUEST, listItem);
80 | yield takeLatest(actionTypes.VIEW_ITEM_REQUEST, viewItem);
81 | yield takeLatest(actionTypes.LIKE_ITEM_REQUEST, likeItem);
82 | yield takeLatest(actionTypes.UNLIKE_ITEM_REQUEST, unlikeItem);
83 | yield takeLatest(actionTypes.DELETE_ITEM_REQUEST, deleteItem);
84 |
85 | yield takeLatest(actionTypes.SET_FILTER_KEY_REQUEST, setFilterKey);
86 | }
87 |
88 | export default rootSaga;
89 |
--------------------------------------------------------------------------------
/src/components/axiosApp/AxiosAppPresent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import AxiosAppResults from "./AxiosAppResults";
4 |
5 | const AxiosAppPresent = ({
6 | keyword,
7 | total,
8 | searchResults,
9 | handleFieldChange,
10 | handleSearchSubmit,
11 | loading
12 | }) => {
13 | return (
14 |
15 |
16 |
Axios RESTful API search example!
17 |
18 | Axios RESTful API search example: A form using axios to implement a
19 | RESTful API search
20 |
21 |
22 |
23 |
57 |
58 | {loading && searchResults.length == 0 &&
59 |
60 | Loading...
61 |
62 | }
63 |
64 | {/* Display results */}
65 |
71 |
72 | );
73 | };
74 |
75 | AxiosAppPresent.propTypes = {
76 | keyword: PropTypes.string,
77 | total: PropTypes.number,
78 | searchResults: PropTypes.array,
79 | handleFieldChange: PropTypes.func,
80 | handleSearchSubmit: PropTypes.func,
81 | loading: PropTypes.bool,
82 | };
83 |
84 | export default AxiosAppPresent;
85 |
--------------------------------------------------------------------------------
/src/constants/axiosApi.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | // for axiosApp example
4 | export const packagistApi = axios.create({
5 | baseURL: "https://packagist.org",
6 | timeout: 3000,
7 | responseType: "json",
8 | params: {
9 | per_page: 5,
10 | },
11 | headers: {
12 | "Content-Type": "application/json",
13 | "X-Custom-Header": "AlexStack react starter example",
14 | Authorization: "Client Id change-here-if-authorization-needed",
15 | },
16 | });
17 |
18 | // for reduxApp example
19 | export const youtubeApi = axios.create({
20 | baseURL: "https://www.googleapis.com/youtube/v3",
21 | timeout: 3000,
22 | responseType: "json",
23 | params: {
24 | // replace below key to your own google-youtube-api-key for more quotas
25 | // key: "AIzaSyBLd6DZyQNP"+"CudkOueydclFOp"+"SJklOMvnw",
26 | key: "AIza"+"SyCSUjqlOe2fK8_2Iw8IXTy9RZ8"+"XOGQ3Mvg",
27 | // key: "AIzaSyB5i9Kkxj8j"+"33eO9rKWx82Xda1"+"XmDovy5I",
28 | maxResults: 10,
29 | crossDomain: true,
30 | },
31 | headers: {
32 | "Content-Type": "application/json",
33 | // "X-Custom-Header": "AlexStack react starter example",
34 | // Authorization: "Client Id change-here-if-authorization-needed",
35 | },
36 | });
37 |
38 | // FAKE backend api for create/update/delete action of a particular user
39 | export const backendApi = axios.create({
40 | baseURL: "https://reqres.in/api",
41 | timeout: 5000,
42 | responseType: "json",
43 | params: {
44 | from: "AlexStack react starter",
45 | },
46 | headers: {
47 | "Content-Type": "application/json",
48 | "X-Custom-Header": "AlexStack react starter example",
49 | Authorization: "Client Id change-here-if-authorization-needed",
50 | },
51 | });
52 |
53 | // githubContentsApi for websiteApp example
54 | export const githubRepository = {
55 | name: "AlexStack/React-Hook-Redux-Saga-Webpack-Starter",
56 | branch: "master",
57 | initDirectory: "doc/websiteApp",
58 | configFile: "0.websiteConfig.json",
59 | wrongFormatMsg:
60 | "Error: File source not start with div, please ask the admin to fix it
",
61 | axiosErrorMsg:
62 | "Get API result failed, please ask the admin to check the console log
",
63 | token1: "0655a262b7ca299897f235",
64 | token2: "cde0d924c4e4c05cde",
65 | };
66 |
67 | export const githubContentsApi = axios.create({
68 | baseURL:
69 | "https://api.github.com/repos/" + githubRepository.name + "/contents",
70 | timeout: 3000,
71 | responseType: "json",
72 | params: {
73 | ref: githubRepository.branch,
74 | },
75 | headers: {
76 | "Content-Type": "application/json",
77 | Authorization: "token " + githubRepository.token1 + githubRepository.token2,
78 | },
79 | });
80 |
81 | // get github raw file to avoid api rate limit
82 | export const githubRawFile = axios.create({
83 | baseURL:
84 | "https://raw.githubusercontent.com/" +
85 | githubRepository.name +
86 | "/" +
87 | githubRepository.branch,
88 | timeout: 3000,
89 | responseType: "text",
90 | headers: {
91 | "Content-Type": "text/plain",
92 | },
93 | });
94 |
95 | export default packagistApi;
96 |
--------------------------------------------------------------------------------
/src/components/hookApp/BasicHooks.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from "react";
2 | import BasicAppPresent from "../basicApp/BasicAppPresent";
3 | import RelatedFiles from "../footer/RelatedFiles";
4 |
5 | const BasicHooks = () => {
6 | const [clickTimes, setClickTimes] = useState(0);
7 | const [keyPressTimes, setKeyPressTimes] = useState(0);
8 | const [pressedKey, setPressedKey] = useState("null");
9 | const [secretKey, setSecretKey] = useState(null);
10 | const gameRef = useRef();
11 | const pressedKeyRef = useRef();
12 |
13 | useEffect(() => {
14 | document.addEventListener("keydown", handleKeyPress, false);
15 | // invoke below cleanup function first when the useEffect re-call again(not the initial render)
16 | return () => {
17 | document.removeEventListener("keydown", handleKeyPress);
18 |
19 | pressedKeyRef.current.focus();
20 | };
21 | }, [secretKey, pressedKey]);
22 |
23 | useEffect(() => {
24 | // example of use ref
25 | gameRef.current.style.background = "rgba(0, 123, 255, 0.17)";
26 | }, []); // Only invoke once == ComponentDidMount()
27 |
28 | const handleFieldChange = () => {
29 | setSecretKey(null);
30 | setClickTimes(clickTimes + 1);
31 | setKeyPressTimes(0);
32 | setPressedKey("null");
33 | };
34 |
35 | const handleKeyPress = (event) => {
36 | if (secretKey === null) {
37 | setSecretKey(event.key);
38 | } else if (event.key === "Escape") {
39 | setSecretKey(null);
40 | setClickTimes(0);
41 | setKeyPressTimes(0);
42 | setPressedKey("null");
43 | } else if (pressedKey === secretKey) {
44 | // do nothing
45 | } else {
46 | setPressedKey(event.key);
47 | setKeyPressTimes(keyPressTimes + 1);
48 | }
49 | };
50 |
51 | return (
52 |
53 |
54 |
61 |
62 |
63 |
71 |
72 |
73 |
74 |
75 |
76 | src/reactApp.js --- React main entry point
77 |
78 |
79 | src/hookApp/BasicHooks.js --- Hooks Components NOT use this.state &
80 | componentDidMount
81 |
82 |
83 | src/components/basicApp/BasicAppPresent.js --- The same child
84 | component as the basic react example
85 |
86 |
87 |
88 | );
89 | };
90 |
91 | export default BasicHooks;
92 |
--------------------------------------------------------------------------------
/src/components/websiteApp/WebsiteAppPresent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import WebsiteAppResults from "./WebsiteAppResults";
4 | import WebsiteAppFileContent from "./WebsiteAppFileContent";
5 |
6 | const WebsiteAppPresent = ({
7 | config,
8 | keyword,
9 | total,
10 | searchResults,
11 | fileContent,
12 | handleFieldChange,
13 | handleSearchSubmit,
14 | loading,
15 | }) => {
16 | return (
17 |
18 |
19 |
{config && config.siteName}
20 |
{config && config.siteDescription}
21 |
22 |
23 |
65 |
66 | {loading && searchResults.length == 0 && (
67 |
68 | Loading...
69 |
70 | )}
71 |
72 | {/* Display results */}
73 |
74 | {fileContent ? (
75 |
81 | ) : (
82 |
88 | )}
89 |
90 | );
91 | };
92 |
93 | WebsiteAppPresent.propTypes = {
94 | config: PropTypes.object,
95 | keyword: PropTypes.string,
96 | total: PropTypes.number,
97 | searchResults: PropTypes.array,
98 | fileContent: PropTypes.string,
99 | handleFieldChange: PropTypes.func,
100 | handleSearchSubmit: PropTypes.func,
101 | loading: PropTypes.bool,
102 | };
103 |
104 | export default WebsiteAppPresent;
105 |
--------------------------------------------------------------------------------
/src/components/axiosApp/AxiosApp.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | import React, { Component } from "react";
3 | import PropTypes from "prop-types";
4 | import packagistApi from "../../constants/axiosApi";
5 | import AxiosAppPresent from "./AxiosAppPresent";
6 | import RelatedFiles from "../footer/RelatedFiles";
7 |
8 | export default class AxiosApp extends Component {
9 | constructor(props) {
10 | super(props);
11 |
12 | this.state = {
13 | keyword: "react api",
14 | searchResults: [],
15 | page: 1,
16 | perPage: 5,
17 | total: null,
18 | loading: false,
19 | };
20 |
21 | this.handleFieldChange = this.handleFieldChange.bind(this);
22 | this.handleSearchSubmit = this.handleSearchSubmit.bind(this);
23 | }
24 |
25 | componentDidMount() {
26 | console.log("componentDidMount", this.state);
27 | }
28 |
29 | componentDidUpdate() {
30 | console.log("componentDidUpdate", this.state);
31 | }
32 |
33 | handleFieldChange = (event) => {
34 | if (event.target.name == "keyword") {
35 | this.setState({
36 | keyword: event.target.value,
37 | });
38 | } else if (event.target.name == "perPage") {
39 | this.setState({
40 | perPage: event.target.value,
41 | });
42 | } else if (event.target.name == "loadMore") {
43 | const pageNumber = this.state.page + 1;
44 | this.setState({
45 | loading: true,
46 | });
47 | this.getApiResult(pageNumber);
48 | }
49 |
50 | console.log(this.state);
51 | };
52 |
53 | handleSearchSubmit = (event) => {
54 | event.preventDefault();
55 | this.setState({
56 | searchResults: [],
57 | total: null,
58 | loading: true,
59 | });
60 | this.getApiResult(1);
61 | };
62 |
63 | async getApiResult(pageNumber) {
64 | const res = await packagistApi
65 | .get("/search.json", {
66 | params: {
67 | q: this.state.keyword,
68 | per_page: this.state.perPage,
69 | page: pageNumber,
70 | },
71 | })
72 | .catch((error) => {
73 | console.log(error, res);
74 | this.setState({
75 | loading: false,
76 | });
77 | });
78 |
79 | if (res.data) {
80 | console.log(res.data);
81 | if (pageNumber > 1) {
82 | this.setState({
83 | searchResults: [...this.state.searchResults, ...res.data.results],
84 | total: res.data.total,
85 | loading: false,
86 | page: pageNumber,
87 | });
88 | } else {
89 | this.setState({
90 | searchResults: res.data.results,
91 | total: res.data.total,
92 | loading: false,
93 | page: 1,
94 | });
95 | }
96 | }
97 | return res;
98 | }
99 |
100 | render() {
101 | return (
102 |
103 |
111 |
112 |
113 |
114 | src/components/AxiosApp/*.js --- React Components
115 |
116 |
117 | src/api/AxiosApi.js --- Axios common settings
118 |
119 |
120 |
121 | );
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/components/reduxApp/ReduxPlayedVideos.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | import React, { Component } from "react";
3 | import { connect } from "react-redux";
4 | import PropTypes from "prop-types";
5 | import allActions from "../../reduxStores/actions/allActions";
6 | import VideoSearchForm from "./VideoSearchForm";
7 | import VideoSearchResults from "./VideoSearchResults";
8 |
9 | class ReduxPlayedVideos extends Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = {
14 | keyword: "funny",
15 | searchResults: [],
16 | page: 1,
17 | perPage: 5,
18 | total: null,
19 | loading: false,
20 | playVideoId: null,
21 | };
22 |
23 | this.handleFieldChange = this.handleFieldChange.bind(this);
24 | this.handleSearchSubmit = this.handleSearchSubmit.bind(this);
25 | }
26 |
27 | componentDidMount() {
28 | window.scrollTo(0, 0);
29 | // console.log("componentDidMount", this.state);
30 | }
31 |
32 | componentDidUpdate() {
33 | // console.log("componentDidUpdate", this.state);
34 | }
35 |
36 | handleFieldChange = (event) => {
37 | if (event.target.name == "keyword") {
38 | this.setState({
39 | keyword: event.target.value,
40 | });
41 | } else if (event.target.name == "playVideo") {
42 | const item = this.props.videos.viewedItems[
43 | event.target.getAttribute("data-index")
44 | ];
45 | this.setState({
46 | playVideoId: item.id.videoId,
47 | });
48 | this.props.dispatch(allActions.viewItemRequest(item));
49 | } else if (event.target.name == "like") {
50 | this.props.dispatch(
51 | allActions.likeItemRequest(
52 | this.props.videos.viewedItems[event.target.getAttribute("data-index")]
53 | )
54 | );
55 | } else if (event.target.name == "unlike") {
56 | this.props.dispatch(
57 | allActions.unlikeItemRequest(
58 | this.props.videos.viewedItems[event.target.getAttribute("data-index")]
59 | )
60 | );
61 | } else if (event.target.name == "perPage") {
62 | this.setState({
63 | perPage: event.target.value,
64 | });
65 | }
66 | // console.log(event.target.getAttribute("data-index"));
67 | };
68 |
69 | handleSearchSubmit = (event) => {
70 | event.preventDefault();
71 | this.setState({
72 | searchResults: [],
73 | total: null,
74 | loading: true,
75 | });
76 | this.props.dispatch(allActions.listItemRequest(this.state.keyword));
77 | };
78 |
79 | render() {
80 | return (
81 |
82 |
90 |
91 |
99 |
100 | );
101 | }
102 | }
103 |
104 | const mapStateToProps = (reduxState) => {
105 | // console.log("mapStateToProps ", reduxState);
106 | return reduxState;
107 | };
108 |
109 | ReduxPlayedVideos.propTypes = {
110 | dispatch: PropTypes.func.isRequired,
111 | };
112 |
113 | export default connect(mapStateToProps, null)(ReduxPlayedVideos);
114 |
--------------------------------------------------------------------------------
/src/components/reduxApp/ReduxLikedVideos.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | import React, { Component } from "react";
3 | import { connect } from "react-redux";
4 | import PropTypes from "prop-types";
5 | import allActions from "../../reduxStores/actions/allActions";
6 | import VideoSearchForm from "./VideoSearchForm";
7 | import VideoSearchResults from "./VideoSearchResults";
8 |
9 | class ReduxLikedVideos extends Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = {
14 | keyword: "funny",
15 | searchResults: [],
16 | page: 1,
17 | perPage: 5,
18 | total: null,
19 | loading: false,
20 | playVideoId: null,
21 | };
22 |
23 | this.handleFieldChange = this.handleFieldChange.bind(this);
24 | this.handleSearchSubmit = this.handleSearchSubmit.bind(this);
25 | }
26 |
27 | componentDidMount() {
28 | window.scrollTo(0, 0);
29 | // console.log("componentDidMount", this.state);
30 | // this.props.dispatch(allActions.listItemRequest(this.state.keyword));
31 | }
32 |
33 | componentDidUpdate() {
34 | // console.log("componentDidUpdate", this.state);
35 | }
36 |
37 | handleFieldChange = (event) => {
38 | if (event.target.name == "keyword") {
39 | this.setState({
40 | keyword: event.target.value,
41 | });
42 | } else if (event.target.name == "playVideo") {
43 | const item = this.props.videos.likedItems[
44 | event.target.getAttribute("data-index")
45 | ];
46 | this.setState({
47 | playVideoId: item.id.videoId,
48 | });
49 | this.props.dispatch(allActions.viewItemRequest(item));
50 | } else if (event.target.name == "like") {
51 | this.props.dispatch(
52 | allActions.likeItemRequest(
53 | this.props.videos.likedItems[event.target.getAttribute("data-index")]
54 | )
55 | );
56 | } else if (event.target.name == "unlike") {
57 | this.props.dispatch(
58 | allActions.unlikeItemRequest(
59 | this.props.videos.likedItems[event.target.getAttribute("data-index")]
60 | )
61 | );
62 | } else if (event.target.name == "perPage") {
63 | this.setState({
64 | perPage: event.target.value,
65 | });
66 | }
67 |
68 | // console.log(event.target.getAttribute("data-index"));
69 | };
70 |
71 | handleSearchSubmit = (event) => {
72 | event.preventDefault();
73 | this.setState({
74 | searchResults: [],
75 | total: null,
76 | loading: true,
77 | });
78 | this.props.dispatch(allActions.listItemRequest(this.state.keyword));
79 | };
80 |
81 | render() {
82 | return (
83 |
84 |
92 |
93 |
101 |
102 | );
103 | }
104 | }
105 |
106 | const mapStateToProps = (reduxState) => {
107 | // console.log("mapStateToProps ", reduxState);
108 | return reduxState;
109 | };
110 |
111 | ReduxLikedVideos.propTypes = {
112 | dispatch: PropTypes.func.isRequired,
113 | };
114 |
115 | export default connect(mapStateToProps, null)(ReduxLikedVideos);
116 |
--------------------------------------------------------------------------------
/src/reduxStores/reducers/videoReducers.js:
--------------------------------------------------------------------------------
1 | import { createReducer } from "reduxsauce";
2 | import { actionTypes } from "../actions/allActions";
3 | import { REDUXPATH, lastPath } from "../../constants/config";
4 |
5 | // the initial state of this reducer
6 | export const INITIAL_STATE = {
7 | allItems: [],
8 | likedItems: [],
9 | viewedItems: [],
10 | keywords: ["Just for laugh", "Cat", "Dog", "Mac Pro", "SpaceX", "Covid-19"],
11 | extraInfo: {
12 | etag: null,
13 | loading: false,
14 | nextPageToken: null,
15 | resultsPerPage: 5,
16 | totalResults: null,
17 | errorMsg: null,
18 | },
19 | };
20 |
21 | const listItems = (state = INITIAL_STATE, action) => {
22 | const allItems = action.nextPageToken
23 | ? [...state.allItems, ...action.allData.items]
24 | : action.allData.items;
25 | return {
26 | ...state,
27 | ...{
28 | allItems: allItems,
29 | keywords: [
30 | action.keyword,
31 | ...state.keywords.filter((t) => t !== action.keyword),
32 | ],
33 | extraInfo: {
34 | loading: false,
35 | nextPageToken: action.allData.nextPageToken,
36 | resultsPerPage: action.allData.pageInfo.resultsPerPage,
37 | totalResults: action.allData.pageInfo.totalResults,
38 | errorMsg: null,
39 | etag: action.allData.etag,
40 | },
41 | },
42 | };
43 | };
44 |
45 | const showLoading = (state = INITIAL_STATE, action) => {
46 | const allItems = action.nextPageToken ? state.allItems : [];
47 | const totalResults = action.nextPageToken
48 | ? state.extraInfo.totalResults
49 | : null;
50 | return {
51 | ...state,
52 | ...{
53 | extraInfo: {
54 | loading: action.loading,
55 | totalResults: totalResults,
56 | },
57 | allItems: allItems,
58 | },
59 | };
60 | };
61 |
62 | const listItemFailure = (state = INITIAL_STATE, action) => {
63 | return {
64 | ...state,
65 | ...{
66 | extraInfo: {
67 | loading: false,
68 | errorMsg: "Something wrong with the youtube API response",
69 | },
70 | },
71 | };
72 | };
73 |
74 | const likeItem = (state = INITIAL_STATE, action) => {
75 | return {
76 | ...state,
77 | ...{ likedItems: [action.item, ...state.likedItems] },
78 | };
79 | };
80 |
81 | const unlikeItem = (state = INITIAL_STATE, action) => {
82 | const newLikedItems = state.likedItems.filter(
83 | (t) => t.id.videoId !== action.item.id.videoId
84 | );
85 | return {
86 | ...state,
87 | likedItems: newLikedItems,
88 | };
89 | };
90 |
91 | const viewItem = (state = INITIAL_STATE, action) => {
92 | if (lastPath() == REDUXPATH.PlayedVideos) {
93 | return state; // nothing should change when you are on the PlayedVideos page
94 | }
95 |
96 | // remove the duplicate videos and put the latest played video on the top
97 | return {
98 | ...state,
99 | ...{
100 | viewedItems: [
101 | action.item,
102 | ...state.viewedItems.filter(
103 | (item) => item.id.videoId !== action.item.id.videoId
104 | ),
105 | ],
106 | },
107 | };
108 | };
109 |
110 | const deleteItem = (state = INITIAL_STATE, action) => {
111 | // console.log(action, action, action);
112 | return {
113 | ...state,
114 | items: state.items.filter((item) => item.id !== action.id),
115 | };
116 | };
117 |
118 | const setFilterKey = (state = INITIAL_STATE, action) => {
119 | return { ...state, filterKey: action.value };
120 | };
121 |
122 | // map our action types to our reducer functions
123 | export const HANDLERS = {
124 | [actionTypes.LIST_ITEM_SUCCESS]: listItems,
125 | [actionTypes.VIEW_ITEM_SUCCESS]: viewItem,
126 | [actionTypes.LIKE_ITEM_SUCCESS]: likeItem,
127 | [actionTypes.UNLIKE_ITEM_SUCCESS]: unlikeItem,
128 | [actionTypes.DELETE_ITEM_SUCCESS]: deleteItem,
129 | [actionTypes.SET_FILTER_KEY_SUCCESS]: setFilterKey,
130 | [actionTypes.SHOW_LOADING]: showLoading,
131 | };
132 |
133 | export default createReducer(INITIAL_STATE, HANDLERS);
134 |
--------------------------------------------------------------------------------
/src/components/reduxApp/ReduxAllVideos.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | import React, { Component } from "react";
3 | import { connect } from "react-redux";
4 | import PropTypes from "prop-types";
5 |
6 | import allActions from "../../reduxStores/actions/allActions";
7 | import VideoSearchForm from "./VideoSearchForm";
8 | import VideoSearchResults from "./VideoSearchResults";
9 |
10 | class ReduxAllVideos extends Component {
11 | constructor(props) {
12 | super(props);
13 |
14 | this.state = {
15 | keyword: "funny",
16 | searchResults: [],
17 | total: null,
18 | playVideoId: null,
19 | };
20 |
21 | this.handleFieldChange = this.handleFieldChange.bind(this);
22 | this.handleSearchSubmit = this.handleSearchSubmit.bind(this);
23 | }
24 |
25 | componentDidMount() {
26 | if (this.props.videos.keywords.length > 0) {
27 | this.setState({
28 | keyword: this.props.videos.keywords[0],
29 | });
30 | }
31 | console.log("componentDidMount", this.state);
32 | }
33 |
34 | componentDidUpdate() {
35 | console.log("componentDidUpdate", this.state);
36 | }
37 |
38 | handleFieldChange = (event) => {
39 | if (event.target.name == "keyword") {
40 | this.setState({
41 | keyword: event.target.value.trim(),
42 | });
43 | } else if (event.target.name == "playVideo") {
44 | const item = this.props.videos.allItems[
45 | event.target.getAttribute("data-index")
46 | ];
47 | this.setState({
48 | playVideoId: item.id.videoId,
49 | });
50 | this.props.dispatch(allActions.viewItemRequest(item));
51 | } else if (event.target.name == "like") {
52 | this.props.dispatch(
53 | allActions.likeItemRequest(
54 | this.props.videos.allItems[event.target.getAttribute("data-index")]
55 | )
56 | );
57 | } else if (event.target.name == "unlike") {
58 | this.props.dispatch(
59 | allActions.unlikeItemRequest(
60 | this.props.videos.allItems[event.target.getAttribute("data-index")]
61 | )
62 | );
63 | } else if (event.target.name == "keywordHistory") {
64 | this.setState({
65 | keyword: event.target.value,
66 | searchResults: [],
67 | total: null,
68 | });
69 | this.startSearch(event.target.value);
70 | } else if (event.target.name == "loadMore") {
71 | this.startSearch(
72 | this.state.keyword,
73 | this.props.videos.extraInfo.nextPageToken
74 | );
75 | }
76 |
77 | console.log(event.target.getAttribute("data-index"));
78 | };
79 |
80 | handleSearchSubmit = (event) => {
81 | event.preventDefault();
82 | this.setState({
83 | searchResults: [],
84 | total: null,
85 | });
86 |
87 | this.startSearch(this.state.keyword);
88 | };
89 |
90 | startSearch = (keyword, nextPageToken) => {
91 | this.props.dispatch(allActions.listItemRequest(keyword, nextPageToken));
92 | };
93 |
94 | render() {
95 | return (
96 |
97 |
105 |
106 | {this.props.videos.extraInfo.errorMsg && (
107 |
108 | {this.props.videos.extraInfo.errorMsg}
109 |
110 | )}
111 |
112 |
120 |
121 | );
122 | }
123 | }
124 |
125 | const mapStateToProps = (reduxState) => {
126 | // console.log("mapStateToProps ", reduxState);
127 | return reduxState;
128 | };
129 |
130 | ReduxAllVideos.propTypes = {
131 | dispatch: PropTypes.func.isRequired,
132 | };
133 |
134 | export default connect(mapStateToProps, null)(ReduxAllVideos);
135 |
--------------------------------------------------------------------------------
/src/components/reduxApp/VideoSearchForm.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { NavLink, Link } from "react-router-dom";
4 | import { REDUXPATH, lastPath, myLog } from "../../constants/config";
5 |
6 | const VideoSearchForm = ({
7 | keyword,
8 | keywordHistory,
9 | searchResults,
10 | handleFieldChange,
11 | handleSearchSubmit,
12 | loading,
13 | }) => {
14 | const showSearchForm =
15 | lastPath() == REDUXPATH.SearchVideos || lastPath() == "/" ? true : false;
16 | // myLog(lastPath(), showSearchForm);
17 | return (
18 |
19 | {showSearchForm ? (
20 |
72 | ) : (
73 |
74 |
Redux User Dashboard Example
75 |
76 | This is the user dashboard example
77 |
78 |
79 | )}
80 |
81 |
82 |
83 | {showSearchForm &&
84 | keywordHistory.length > 0 &&
85 | keywordHistory.map((k, index) => {
86 | if (k != keyword) {
87 | return (
88 |
95 | {k}
96 |
97 | );
98 | }
99 | })}
100 |
101 |
102 |
103 | {!showSearchForm && (
104 |
109 | Search Videos
110 |
111 | )}
112 |
113 |
118 | My Liked Videos
119 |
120 |
121 |
126 | Recently Played Videos
127 |
128 |
129 |
130 | {loading && searchResults.length == 0 && (
131 |
132 | Loading ......
133 |
134 | )}
135 |
136 |
137 | );
138 | };
139 |
140 | VideoSearchForm.propTypes = {
141 | keyword: PropTypes.string,
142 | keywordHistory: PropTypes.array,
143 | searchResults: PropTypes.array,
144 | handleFieldChange: PropTypes.func,
145 | handleSearchSubmit: PropTypes.func,
146 | loading: PropTypes.bool,
147 | };
148 |
149 | export default VideoSearchForm;
150 |
--------------------------------------------------------------------------------
/src/components/websiteApp/WebsiteApp.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | import React, { Component } from "react";
3 | import PropTypes from "prop-types";
4 | import {
5 | githubContentsApi,
6 | githubRawFile,
7 | githubRepository,
8 | } from "../../constants/axiosApi";
9 | import WebsiteAppPresent from "./WebsiteAppPresent";
10 | import RelatedFiles from "../footer/RelatedFiles";
11 | import { Base64 } from "js-base64";
12 |
13 | export default class WebsiteApp extends Component {
14 | constructor(props) {
15 | super(props);
16 |
17 | this.state = {
18 | keyword: "",
19 | searchResults: [],
20 | config: null,
21 | fileContent: null,
22 | total: null,
23 | loading: false,
24 | };
25 |
26 | this.handleFieldChange = this.handleFieldChange.bind(this);
27 | this.handleSearchSubmit = this.handleSearchSubmit.bind(this);
28 | }
29 |
30 | componentDidMount() {
31 | console.log("componentDidMount", this.state);
32 | this.getRawFileContent(
33 | githubRepository.initDirectory + "/" + githubRepository.configFile
34 | );
35 | this.listAllFiles();
36 | }
37 |
38 | componentDidUpdate() {
39 | console.log("componentDidUpdate", this.state);
40 | }
41 |
42 | handleFieldChange = (event) => {
43 | if (event.target.name == "keyword") {
44 | this.setState({
45 | keyword: event.target.value.trim(),
46 | });
47 | } else if (event.target.name == "showDetails") {
48 | event.preventDefault();
49 | this.setState({
50 | loading: true,
51 | });
52 | // this.getApiResult(event.target.value);
53 | this.getRawFileContent(event.target.value);
54 | } else if (event.target.name == "returnToList") {
55 | event.preventDefault();
56 | this.setState({
57 | fileContent: null,
58 | });
59 | } else if (event.target.name == "displayAll") {
60 | event.preventDefault();
61 | this.setState({
62 | keyword: "",
63 | });
64 | }
65 |
66 | console.log(this.state);
67 | };
68 |
69 | handleSearchSubmit = (event) => {
70 | event.preventDefault();
71 | if (!this.state.keyword && this.state.searchResults.length == 0) {
72 | this.setState({
73 | searchResults: [],
74 | total: null,
75 | loading: true,
76 | fileContent: null,
77 | });
78 | this.listAllFiles();
79 | } else if (this.state.fileContent) {
80 | this.setState({
81 | fileContent: null,
82 | });
83 | } else {
84 | console.log("do nothing");
85 | }
86 | };
87 |
88 | listAllFiles = (dir = githubRepository.initDirectory) => {
89 | this.getApiResult(dir);
90 | };
91 |
92 | async getApiResult(fileName) {
93 | const results = await githubContentsApi
94 | .get("/" + fileName, {
95 | params: {
96 | ref: githubRepository.branch,
97 | },
98 | })
99 | .catch((error) => {
100 | // console.log(error, error.response);
101 | this.setState({
102 | loading: false,
103 | fileContent:
104 | githubRepository.axiosErrorMsg +
105 | "" +
106 | error.response.data.message +
107 | "
",
108 | });
109 | });
110 |
111 | if (results.data) {
112 | if (results.data.type) {
113 | // console.log(Base64.decode(results.data.content));
114 | this.setState({
115 | fileContent: Base64.decode(results.data.content),
116 | loading: false,
117 | });
118 | } else {
119 | const searchResults = results.data.filter(
120 | (item) => item.type == "file" && item.name.indexOf(".html") != -1
121 | );
122 |
123 | this.setState({
124 | searchResults: searchResults,
125 | total: searchResults.length,
126 | loading: false,
127 | });
128 | }
129 | }
130 | return results;
131 | }
132 |
133 | async getRawFileContent(fileName) {
134 | const results = await githubRawFile.get("/" + fileName).catch((error) => {
135 | // console.log(error, error.response);
136 | this.setState({
137 | loading: false,
138 | fileContent:
139 | githubRepository.axiosErrorMsg +
140 | "" +
141 | error.response.data.message +
142 | "
",
143 | });
144 | });
145 |
146 | if (results.data) {
147 | if (fileName.indexOf(githubRepository.configFile) != -1) {
148 | console.log(results);
149 | this.setState({
150 | config: results.data,
151 | });
152 | }
153 | const fileContent =
154 | results.data.trim().indexOf(" {
167 | if (this.state.keyword) {
168 | return this.state.searchResults.filter(
169 | (item) =>
170 | item.name
171 | .toLowerCase()
172 | .replace(/-/g, " ")
173 | .indexOf(this.state.keyword.toLowerCase()) != -1
174 | );
175 | }
176 |
177 | return this.state.searchResults;
178 | };
179 |
180 | render() {
181 | return (
182 |
183 |
193 |
194 |
195 |
196 | src/components/WebsiteApp/*.js --- React Components
197 |
198 |
199 | src/api/AxiosApi.js --- Axios common settings
200 |
201 |
202 |
203 | );
204 | }
205 | }
206 |
--------------------------------------------------------------------------------