Skip to content

Instantly share code, notes, and snippets.

@JSila
Created Apr 23, 2018
Embed
What would you like to do?
custom router solution for react (experiment)
import React, {Component, createElement} from 'react'
import createHistory from 'history/createBrowserHistory'
import classnames from "classnames"
export const history = createHistory()
/**
* Determines if route path matches history location's pathname. If it does, extracts its params. If not, returns null.
*
* @param routePath
* @param locationPath
* @returns {object|null}
*/
const matchParams = (routePath, locationPath) => {
const l = locationPath.split('/').slice(1)
const r = routePath.split('/').slice(1)
const llen = l.length
if (llen !== r.length) {
return null
}
const params = {}
for (let i = 0; i < llen; i++) {
const [h,] = r[i]
if (h === ":" && l[i]) {
params[r[i].slice(1)] = l[i]
continue
}
if (r[i] !== l[i]) {
return null
}
}
return {
match: routePath,
params
}
}
const RouterContext = React.createContext()
export const Route = ({path, component}) => (
<RouterContext.Consumer>
{({state}) => {
const data = matchParams(path, state.url)
if (!data) return
return createElement(component, data)
}}
</RouterContext.Consumer>
)
export class Router extends Component {
constructor(p) {
super(p)
this.state = {
url: window.location.pathname
}
this.action = {
go: url => {
if (this.state.url === url) return
this.setState(
state => ({...state, url}),
() => history.push(url)
)
}
}
}
componentDidMount() {
this.unsubscribe = history.listen((location, action) => {
if (action !== "POP") return
this.setState({url: location.pathname})
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
const value = {
state: this.state,
action: this.action
}
return (
<RouterContext.Provider value={value}>
{this.props.children}
</RouterContext.Provider>
)
}
}
export const Link = ({to, activeClassName = 'active', children}) => (
<RouterContext.Consumer>
{({state, action}) => {
const className = classnames({
[activeClassName]: state.url === to
})
return (
<a className={className} onClick={(e) => {e.preventDefault(); action.go(to)}} href={to}>
{children}
</a>
)
}}
</RouterContext.Consumer>
)
const onlyKeys = (keys, obj) => {
const res = {}
keys.forEach(k => {
res[k] = obj[k]
})
return res
}
const arrayDeepEqual = (obj1 = {}, obj2 = {}) => {
return Object.keys(obj1).every(k => obj1[k] === obj2[k])
}
const ROUTE_CHANGE = 'router/ROUTE_CHANGE'
const initialState = onlyKeys(['pathname', 'search', 'hash'], history.location)
export const routerReducer = (state = initialState, action) => {
if (action.type !== ROUTE_CHANGE) return state
return action.payload
}
export const routerMiddleware = store => {
return dispatch => {
history.listen(location => {
const payload = {
pathname: location.pathname,
search: location.search,
hash: location.hash
}
if (arrayDeepEqual(payload, store.getState().router)) return
dispatch({
type: ROUTE_CHANGE,
payload
})
})
return action => {
return dispatch(action)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment