From a726c76004fc8285864d0060f1190eda4594ff90 Mon Sep 17 00:00:00 2001 From: Nirro Date: Tue, 10 May 2022 22:01:14 +0200 Subject: [PATCH] refactor: simplify Router component state handling --- README.md | 8 +-- example/src/App.tsx | 4 +- example/src/index.tsx | 16 ++--- package-lock.json | 4 +- package.json | 2 +- src/Router/index.tsx | 134 ++++++++++++------------------------------ 6 files changed, 55 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index 422a96b..74ba030 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # React Awesome Router -A simple, lightweight, middleware oriented router for react applications. +A simple, lightweight, middleware oriented router for React applications. ## Motivation -Comming from non-react world, routing throgh JSX components feels annoying to me. I don't like to spread the routing logic between different react components or write JSX components to extend router capabilities (like auth). I also missed other features I enjoy and was used to like [Angular guards](https://angular.io/api/router/CanActivate) and [Koa](https://github.com/koajs/koa) middleware based architecture. +Comming from non-React world, routing throgh JSX components feels annoying to me. I don't like to spread the routing logic between different react components or write JSX components to extend router capabilities (like auth). I also missed other features I enjoy and was used to like [Angular guards](https://angular.io/api/router/CanActivate) and [Koa](https://github.com/koajs/koa) middleware based architecture. -When starting with react hooks, I realized how simple it will be to write a react router with hooks, [history.js](https://github.com/ReactTraining/history) and [path-to-regexp](https://github.com/pillarjs/path-to-regexp); indeed I think the whole module is far below 200 lines of code. This module provides basic routing features to small applications, and allows more advanced features on bigger applications through the use of custom ad-hoc middlewares. +When starting with React hooks, I realized how simple it will be to write a React router with hooks, [history.js](https://github.com/ReactTraining/history) and [path-to-regexp](https://github.com/pillarjs/path-to-regexp); indeed I think the whole module is far below 200 lines of code. This module provides basic routing features to small applications, and allows more advanced features on bigger applications through the use of custom ad-hoc middlewares. ## Installation @@ -163,7 +163,7 @@ const authGuard = (router, next) => { }; ``` -## Running for developement +## Running for development To run both the router module and the example together with live reloading, first clone the repository: diff --git a/example/src/App.tsx b/example/src/App.tsx index b422e84..eb495ba 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -4,12 +4,10 @@ import logo from './logo.svg'; import {Routes, useLocation} from 'react-awesome-router'; const App: React.FC = () => { - const {location, context, setLocation, setContext} = useLocation(); + const {context, setLocation, setContext} = useLocation(); const login = () => { setContext({auth: {logued: true, username: 'notadmin'}}); - //Optional, force refresh on login - setLocation(location); }; const logout = () => { diff --git a/example/src/index.tsx b/example/src/index.tsx index 67fb604..1d5ed71 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -1,17 +1,17 @@ import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; +import { Router } from 'react-awesome-router'; +import { createRoot } from 'react-dom/client'; import App from './App'; +import './index.css'; +import { routes } from './routes'; import * as serviceWorker from './serviceWorker'; -import {Router} from 'react-awesome-router'; -import {routes} from './routes'; - -ReactDOM.render( +const container = document.getElementById('root'); +const root = createRoot(container!); +root.render( - , - document.getElementById('root') + ); // If you want your app to work offline and load faster, you can change diff --git a/package-lock.json b/package-lock.json index dea0d1a..d9dba38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-awesome-router", - "version": "2.0.0", + "version": "2.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "react-awesome-router", - "version": "2.0.0", + "version": "2.0.2", "license": "MIT", "dependencies": { "history": "^5.3.0", diff --git a/package.json b/package.json index 6f60bad..4206efa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-awesome-router", - "version": "2.0.1", + "version": "2.0.2", "description": "Lightweight middleware-based react router", "repository": { "type": "git", diff --git a/src/Router/index.tsx b/src/Router/index.tsx index fab91eb..601b47c 100644 --- a/src/Router/index.tsx +++ b/src/Router/index.tsx @@ -1,123 +1,67 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; import { createBrowserHistory, History } from 'history'; import { RouterContext } from '../Context'; -import { Route } from '../types'; +import { Route, Router as IRouter } from '../types'; import Path from '../PathUtils'; -export interface RouterState { - location: string; - params: Object; - routes: Array; - context: Object; - forceRefresh: number; - routedElement: React.ReactNode | undefined; -} - export interface IRouterProps { routes: Array; children: React.ReactNode } -export const Router: React.FC = props => { - const history = useRef(undefined); - const initialState: RouterState = { - location: '', - params: {}, - context: {}, - routes: props.routes, - routedElement: undefined, - forceRefresh: 0 - }; +const browserHistory = createBrowserHistory() - const [state, setState] = useState(initialState); +const getComponentFromRoute = (route: Route, router: IRouter) => { + let guardReturn: React.ReactNode = undefined; - const setLocation = (location: string) => { - if (state.location !== location) { - history.current?.push(location); - } else { - setState({ - ...state, - forceRefresh: state.forceRefresh + 1 - }); + if (route.guards) { + let nexted = false; + for (const guard of route.guards) { + guardReturn = guard(router, () => { + nexted = true + return undefined + }) + if (!nexted && guardReturn) break } - }; - - const setContext = (context: Object) => { - setState({ - ...state, - context: Object.assign(state.context, context) - }); - }; + } - useEffect(() => { - history.current = createBrowserHistory(); - setState({ - ...state, - location: history.current.location.pathname - }); - - const unlisten = history.current.listen(update => { - setState({ - ...state, - location: update.location.pathname - }); - }); + return guardReturn ?? route.component +} - return () => { - unlisten(); - }; - }, []); +export const Router = ({ routes, children }: IRouterProps) => { + const [location, setLocation] = useState(browserHistory.location.pathname) + const [context, setContext] = useState({}) + // Listen to location changes useEffect(() => { - let route = state.routes.find(route => { - return Path.match(route.path, state.location); + browserHistory.listen(update => { + setLocation(update.location.pathname); }); + }, []); - if (route) { - let guardReturn: React.ReactNode = undefined; - let nexted = false; - - if (route.guards) { - for (const guard of route.guards) { - guardReturn = guard({ - location: state.location, - setLocation, - setContext, - context: state.context, - params: state.params, - }, () => { - nexted = true - return undefined - }); - if (!nexted && guardReturn) break; - } - } - - setState({ - ...state, - params: Path.parse(route.path, state.location), - routedElement: guardReturn ?? route.component - }); - - } - + const route = routes.find(route => Path.match(route.path, location)); + if (!route) { + console.error(`Current location ${location} did not match any defined route`) + return null; + } - }, [state.location, state.forceRefresh]); + const params = Path.parse(route.path, location) + const component = getComponentFromRoute(route, { location, setLocation, context, setContext, params }) return ( - {props.children} + {children} - ); -}; + ) +}