The routerMachine works with a class component like a charm. Except, in an all-functional components project like mine it fails to run and produces the following infiniate rerending error:
index.js:2178 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render....
I tried using useService from @xstate/react but that still didn't help, and it seems that's because routerMachine triggers service.onTransition on every render.
The way I solved this problem is by forking your lib, refactoring routerMachine so to reuse its functionality and created a new function: useRouterMachine.
I refactored routerMachine function with the to the following:
export function createRouterMachine<
TContext = any,
TState extends StateSchema = any,
TEvent extends EventObject = any
>({
config,
options = ({} as MachineOptions<TContext, TEvent>),
initialContext = {},
history = createBrowserHistory(),
}: RouterArgs): StateMachine<TContext, TState, TEvent> {
const routes = getRoutes(config)
const enhancedConfig = addRouterEvents(history, config, routes)
const currentLocation = history.location
const enhancedContext = {
...initialContext,
match: resolve(routes, currentLocation),
location: currentLocation,
history
}
return Machine(enhancedConfig, options, enhancedContext);
}
export function routerMachine<
TContext = any,
TState extends StateSchema = any,
TEvent extends EventObject = any
>({
config,
options = ({} as MachineOptions<TContext, TEvent>),
initialContext = {},
history = createBrowserHistory(),
}: RouterArgs) {
const machine = createRouterMachine({config, options, initialContext, history})
const service = interpret(machine)
service.start()
handleTransitionEvents(service, history, getRoutes(config))
return service
}
export function useRouterMachine
<
TContext = any,
TState extends StateSchema = any,
TEvent extends EventObject = any
>({
config,
options = ({} as MachineOptions<TContext, TEvent>),
initialContext = {},
history = createBrowserHistory(),
}: RouterArgs) {
const machine = createRouterMachine({config, options, initialContext, history})
const [state, send, service] = useMachine(machine);
useEffect(() => {
handleTransitionEvents(service, history, getRoutes(config))
}, [])
return {state, send, service};
}
export function handleTransitionEvents (service, history, routes) {
let debounceHistoryFlag = false
let debounceState = false
handleRouterTransition(history.location)
service.onTransition(state => {
const stateNode = getCurrentStateNode(service, state)
const path = findPathRecursive(stateNode)
if (debounceState
// debounce only if no target for event was given e.g. in case of
// fetching 'route-changed' events by the user
&& debounceState[1] === path) {
debounceState = false
return
}
if (!matchURI(path, history.location.pathname)) {
debounceHistoryFlag = true
const uri = buildURI(path, state.context.match)
history.push(uri)
service.send({ type: routerEvent, dueToStateTransition: true, route: path, service: service })
}
})
history.listen(location => {
if (debounceHistoryFlag) {
debounceHistoryFlag = false
return
}
handleRouterTransition(location, true)
})
function handleRouterTransition(location, debounceHistory?: boolean) {
let matchingRoute
for (const route of routes) {
const params = matchURI(route[1], location.pathname)
if (params) {
matchingRoute = route
break
}
}
if (matchingRoute) {
debounceState = matchingRoute[1] // debounce only for this route
service.send({ type: routerEvent, dueToStateTransition: false, route: matchingRoute[1], service: service })
const state = service.state.value
if (!matchesState(state, matchingRoute[0].join('.'))) {
const stateNode = getCurrentStateNode(service, service.state)
if (stateNode.meta && stateNode.meta.path) {
if (debounceHistory) {
debounceHistoryFlag = true
}
history.replace(stateNode.meta.path)
}
}
}
}
}
What do you think? Do you think it's worth including this change and renaming the project to xstate-react-router ?
By the way, I noticed you have 15 months old repo called xstate-react-router which has a different type of implementation. I am not sure what you intend to do with that project although it seemed interesting enough to have a component wrapper around the react-routerRoutes.
A little digression: I wanted to note that your NPM for xstate-router is pointing to xstate-react-router on github rather than xstate-router, I think this is a mistake.
The routerMachine works with a class component like a charm. Except, in an all-functional components project like mine it fails to run and produces the following infiniate rerending error:
Here's my code:
I tried using
useService
from@xstate/react
but that still didn't help, and it seems that's becauserouterMachine
triggersservice.onTransition
on every render.The way I solved this problem is by forking your lib, refactoring
routerMachine
so to reuse its functionality and created a new function:useRouterMachine
.I refactored
routerMachine
function with the to the following:What do you think? Do you think it's worth including this change and renaming the project to
xstate-react-router
?By the way, I noticed you have 15 months old repo called
xstate-react-router
which has a different type of implementation. I am not sure what you intend to do with that project although it seemed interesting enough to have a component wrapper around thereact-router
Route
s.xstate-router
is pointing toxstate-react-router
on github rather thanxstate-router
, I think this is a mistake.Thanks a bunch!
Issa