Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
stories: ["../src/**/*.stories.[tj]s"],
};
17 changes: 17 additions & 0 deletions __tests__/useDebouncer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { renderHook, act } from "@testing-library/react-hooks";
import useDebounce from "../src/hooks/useDebounce";

test("should output result in specified seconds", done => {
jest.setTimeout(5000);

const { result } = renderHook(() => useDebounce(2000));
const [debouncer] = result.current;

function callback1(data) {
expect.assertions(1);
expect(data).toBe("Softcom");
done();
}

debouncer(() => callback1("Softcom")); //Softcom will be sent to callback in 2s
});
18,544 changes: 18,544 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"lint": "./node_modules/.bin/eslint ./src",
"pretty": "prettier --write '**/*.{ts,js,css,html,json,md}'",
"predeploy": "yarn build",
"deploy": "gh-pages -d build"
"deploy": "gh-pages -d build",
"storybook": "start-storybook"
},
"eslintConfig": {
"extends": "react-app"
Expand All @@ -48,10 +49,12 @@
]
},
"devDependencies": {
"@babel/core": "^7.5.4",
"@babel/core": "^7.10.2",
"@babel/plugin-proposal-class-properties": "^7.5.0",
"@babel/preset-env": "^7.5.4",
"@babel/preset-react": "^7.0.0",
"@storybook/react": "^5.3.19",
"@testing-library/react-hooks": "^3.3.0",
"babel-jest": "^24.8.0",
"chai": "^4.2.0",
"enzyme": "^3.10.0",
Expand Down Expand Up @@ -80,4 +83,4 @@
"\\.(css|less)$": "<rootDir>/__mocks__/styleMock.js"
}
}
}
}
18 changes: 9 additions & 9 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import React from 'react';
import { Provider } from 'react-redux';
import axios from 'axios';
import jwt_decode from 'jwt-decode';
import store from './redux/store';
import { setCurrentUser, logoutUser } from './redux/actions/authActions';
import Routes from './config/Routes';
import React from "react";
import { Provider } from "react-redux";
import axios from "axios";
import jwt_decode from "jwt-decode";
import store from "./redux/store";
import { setCurrentUser, logoutUser } from "./redux/actions/authActions";
import Routes from "./config/Routes";

if (localStorage.jwt) {
//set auth token header auth;
axios.defaults.headers.common[
'Authorization'
"Authorization"
] = store.getState().auth.user.token;

axios.defaults.headers.common['Authorization'] = localStorage.jwt;
axios.defaults.headers.common["Authorization"] = localStorage.jwt;
//decode token and get user
const decoded = jwt_decode(localStorage.jwt);
//set current user
Expand Down
30 changes: 15 additions & 15 deletions src/config/Routes.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { PageLoader } from '../components/Loaders';
import PrivateRoute from '../layouts/PrivateRoute';
import AuthRoute from '../layouts/AuthRoute';
import PublicRoute from '../layouts/PublicRoute';
import Error404 from '../components/Error404';
import { AlertWrapper } from '../components/alert/AlertComponent';
import React, { lazy, Suspense } from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import { PageLoader } from "../components/Loaders";
import PrivateRoute from "../layouts/PrivateRoute";
import AuthRoute from "../layouts/AuthRoute";
import PublicRoute from "../layouts/PublicRoute";
import Error404 from "../components/Error404";
import { AlertWrapper } from "../components/alert/AlertComponent";

// create Loadable pages
const Home = lazy(() => import('../pages/home/Home'));
const Login = lazy(() => import('../pages/auth/Login'));
const About = lazy(() => import('../pages/about/About'));
const Home = lazy(() => import("../pages/home/Home"));
const Login = lazy(() => import("../pages/auth/Login"));
const About = lazy(() => import("../pages/about/About"));

const Routes = () => (
// eslint-disable-next-line no-undef
Expand All @@ -19,14 +19,14 @@ const Routes = () => (
<AlertWrapper ref={alertRef => (window.alertRef = alertRef)} />
<Switch>
{/* can't access them when you are logged in */}
<AuthRoute exact path='/' component={Login} />
<AuthRoute exact path='/login' component={Login} />
<AuthRoute exact path="/" component={Login} />
<AuthRoute exact path="/login" component={Login} />

{/* can only access them when you are logged in */}
<PrivateRoute exact path='/home' component={Home} />
<PrivateRoute exact path="/home" component={Home} />

{/* public route: accessible to both !!authenticated users */}
<PublicRoute exact path='/about' component={About} />
<PublicRoute exact path="/about" component={About} />

{/* catch all invalid urls */}
<Route component={Error404} />
Expand Down
48 changes: 48 additions & 0 deletions src/hooks/useDebounce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useEffect, useCallback } from "react";

let debounceTimeout;
/**
* This is a custom hook for debouncing events
*
* ------ HOW TO USE -------
*
*
* Import useDebounce to your component.
*
* Destruct the debouncer const [debouncer] = useDebounce(1000)
*
* 1000 is the debounce delay
*
* Debounce delay is OPTIONAL, defaults to 1000
*
* To debounce any method, send the method as a parameter to your debouncer e.g. debouncer(() => console.log(1+1))
*
* The result will be logged after 1000ms
*
* Using in a search input box
*
*
* MOTIVATION This was made to delay server calls when making searches,
* instead of making a request on every keystroke, we debounce to a delayed time after the last keystroke.
*
* @param {number} delay - Delay in milliseconds
*/
const useDebounce = (delay = 1000) => {
const debounceRequest = useCallback(
debounceFunction => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
debounceFunction();
}, delay);
},
[delay]
);

useEffect(() => {
return () => clearTimeout(debounceTimeout);
}, [debounceRequest]);

return [debounceRequest];
};

export default useDebounce;
37 changes: 28 additions & 9 deletions src/pages/about/About.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
import React from 'react';
import React from "react";

const About = () => {
return (
<section id="about">
<h2>About</h2>
<p>Checkout <code>react this repo on <a href='https://github.com/kenshinman/react-redux-base'>Github</a></code></p>
<p>
Checkout{" "}
<code>
react this repo on{" "}
<a href="https://github.com/kenshinman/react-redux-base">Github</a>
</code>
</p>

<p>
Also checkout <a href='https://github.com/chidimo/React-Redux-Starter'>React Redux Starter</a>.
It also incorporates the following CI/CD infrastructures:
Also checkout{" "}
<a href="https://github.com/chidimo/React-Redux-Starter">
React Redux Starter
</a>
. It also incorporates the following CI/CD infrastructures:
</p>
<ol>
<li><code>React testing library</code></li>
<li><code>travis-ci</code></li>
<li><code>AppVeyor</code></li>
<li><code>coveralls</code></li>
<li><code>codeclimate</code></li>
<li>
<code>React testing library</code>
</li>
<li>
<code>travis-ci</code>
</li>
<li>
<code>AppVeyor</code>
</li>
<li>
<code>coveralls</code>
</li>
<li>
<code>codeclimate</code>
</li>
</ol>
</section>
);
Expand Down
17 changes: 10 additions & 7 deletions src/pages/auth/Login.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { doAlert } from '../../components/alert/AlertComponent';

import React from "react";
import { Link } from "react-router-dom";
import { doAlert } from "../../components/alert/AlertComponent";

const Login = () => {
return (
<div>
<h2>Login here</h2>
<button onClick={() => doAlert('This is a message', 'success')}>
<button onClick={() => doAlert("This is a message", "success")}>
click me
</button>

<p><Link to='/about'>About</Link></p>
<p><Link to='/some-unknown-url'>Error 404 page</Link></p>
<p>
<Link to="/about">About</Link>
</p>
<p>
<Link to="/some-unknown-url">Error 404 page</Link>
</p>
</div>
);
};
Expand Down
68 changes: 68 additions & 0 deletions src/stories/debounce.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useState } from "react";
import useDebounce from "../hooks/useDebounce";
import { PageLoader } from "../components/Loaders";

export default { title: "Debouncer" };

export const debouncerTest = () => {
const [debounce] = useDebounce();
const [searchValue, setSearchValue] = useState("");
const [users, setUsers] = useState([]);
const [isSearching, setIsSearching] = useState(false);

const searchUsers = value => {
if (value) {
fetch(`https://api.github.com/search/users?q=${value}`)
.then(res => res.json())
.then(res => {
setIsSearching(false);
setUsers(res.items);
})
.catch(_ => {
alert("An error occured");
});
}
};

return (
<>
<input
placeholder={"Start typing to search"}
style={{ padding: "10px 50px" }}
value={searchValue}
onChange={e => {
let {
target: { value },
} = e;
setIsSearching(true);

// Could just set state here and monitor changes in an effect before debouncing.
setSearchValue(value);
if (value) {
debounce(() => searchUsers(value));
} else {
setIsSearching(false);
setUsers([]);
}
}}
/>

{isSearching ? (
<PageLoader />
) : (
<div>
{users.map(user => (
<div style={{ display: "flex", padding: "20px 0" }}>
<img
src={user.avatar_url}
alt=""
style={{ width: 20, height: 20 }}
/>
<div>{user.login}</div>
</div>
))}
</div>
)}
</>
);
};