Skip to content

Instantly share code, notes, and snippets.

@punmechanic
Created October 26, 2018 18:50
Show Gist options
  • Save punmechanic/3c2bdae913be0959213eb906884d7032 to your computer and use it in GitHub Desktop.
Save punmechanic/3c2bdae913be0959213eb906884d7032 to your computer and use it in GitHub Desktop.
import { Switch, Route, withRouter } from "react-router"
import { BrowserRouter, Link } from "react-router-dom"
import invariant from "invariant"
import { Map } from "immutable"
const AsyncCapableRouteContext = React.createContext({
registerRoute() {},
unregisterRoute() {}
})
class AsyncRenderRouteHandler extends React.Component {
state = {
map: Map()
}
componentDidUpdate() {
console.log("componentDidUpdate", this.props)
}
registerRoute = (path, fetchData, routeComponent) => {
invariant(path !== undefined, "registerRoute(): path cannot be undefined")
this.setState(({ map }) => map.set(path, { fetchData, routeComponent }))
}
unregisterRoute = path => {
this.setState(({ map }) => map.delete(path))
}
render() {
// TODO: Determine if we're fetching/change state/load data
const value = {
registerRoute: this.registerRoute,
unregisterRoute: this.unregisterRoute
}
return (
<AsyncCapableRouteContext.Provider value={value}>
{this.props.children}
</AsyncCapableRouteContext.Provider>
)
}
}
function AsyncCapableRoute(props) {
const context = React.useContext(AsyncCapableRouteContext)
// This runs every time the route is rendered
React.useEffect(
() => {
// Register ourselves when we are rendered
context.registerRoute(props.path)
// Make sure we clean up after we are done
return () => context.unregisterRoute(props.path)
},
// Only run this effect if path or render changes
[props.path, props.render]
)
return <Route {...props} />
}
const AsyncRenderRoute = withRouter(AsyncRenderRouteHandler)
function App() {
// <Switch /> will only render the component that matches; all other components will be unmounted.
// This means that the <AsyncCapableRoute /> component itself is rendered and unrendered depending on what route matches.
// Since we rely on behaviour on <AsyncCapableRoute /> mount to 'register' a data fetching function to associate with a path, if the component never mounts, we never 'learn' about the data fetching function to associate with the path, and so the Route never renders.
// One solution to this would be to implement our own version of <Switch /> that gathers facts about the routes it can render and store those (updating them every time the <Switch /> component is re-rendered). This would allow us to maintain information about the routes independently of those routes being rendered.
return (
<AsyncRenderRoute>
<Link to="/hi">Hi</Link>
<Link to="/goodbye">Goodbye</Link>
<Switch>
<AsyncCapableRoute path="/hi" render={() => <h1>Hello!</h1>} />
<AsyncCapableRoute path="/goodbye" render={() => <h1>Goodbye!</h1>} />
</Switch>
</AsyncRenderRoute>
)
}
@punmechanic
Copy link
Author

Switch doesn't unmount components that don't match - it simply does not render them and will unmount them if they have already been rendered. This doesn't really make sense according to React's reconciliation algorithm: Since both AsyncCapableRoutes are identical across AsyncRenderRoute updates, they should stay the same (as they are of the same type).. so something else must be going on, leading me to think it's behavoiur of the Switch component

@punmechanic
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment