mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
* [Blocks Fixture] Add a tiny router * Add a way to load nested entrypoint * Only expose URL params to nested routers * Add keys to route definitions * [Blocks Fixture] Drop the Blocks
106 lines
2.4 KiB
JavaScript
106 lines
2.4 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
import React, {
|
|
useReducer,
|
|
useEffect,
|
|
unstable_useTransition as useTransition,
|
|
useCallback,
|
|
useMemo,
|
|
Suspense,
|
|
} from 'react';
|
|
import {createCache, CacheProvider} from 'react/unstable-cache';
|
|
import {RouterProvider} from './client/RouterContext';
|
|
// TODO: can't really import a server component on the client.
|
|
import App from './server/App';
|
|
|
|
const initialUrl = window.location.pathname;
|
|
const initialState = {
|
|
// TODO: use this for invalidation.
|
|
cache: createCache(),
|
|
url: initialUrl,
|
|
pendingUrl: initialUrl,
|
|
root: <App route={initialUrl} />,
|
|
};
|
|
|
|
function reducer(state, action) {
|
|
switch (action.type) {
|
|
case 'startNavigation':
|
|
return {
|
|
...state,
|
|
pendingUrl: action.url,
|
|
};
|
|
case 'completeNavigation':
|
|
// TODO: cancel previous fetch?
|
|
return {
|
|
...state,
|
|
url: action.url,
|
|
pendingUrl: action.url,
|
|
root: action.root,
|
|
};
|
|
default:
|
|
throw new Error();
|
|
}
|
|
}
|
|
|
|
function Router() {
|
|
const [state, dispatch] = useReducer(reducer, initialState);
|
|
const [startTransition, isPending] = useTransition({
|
|
timeoutMs: 1500,
|
|
});
|
|
|
|
useEffect(() => {
|
|
document.body.style.cursor = isPending ? 'wait' : '';
|
|
}, [isPending]);
|
|
|
|
const navigate = useCallback(
|
|
url => {
|
|
startTransition(() => {
|
|
// TODO: Here, There, and Everywhere.
|
|
// TODO: Instant Transitions, somehow.
|
|
dispatch({
|
|
type: 'completeNavigation',
|
|
root: <App route={url} />,
|
|
url,
|
|
});
|
|
});
|
|
dispatch({
|
|
type: 'startNavigation',
|
|
url,
|
|
});
|
|
},
|
|
[startTransition]
|
|
);
|
|
|
|
useEffect(() => {
|
|
const listener = () => {
|
|
navigate(window.location.pathname);
|
|
};
|
|
window.addEventListener('popstate', listener);
|
|
return () => window.removeEventListener('popstate', listener);
|
|
}, [navigate]);
|
|
|
|
const routeContext = useMemo(
|
|
() => ({
|
|
pendingUrl: state.pendingUrl,
|
|
url: state.url,
|
|
navigate,
|
|
}),
|
|
[state.url, state.pendingUrl, navigate]
|
|
);
|
|
|
|
return (
|
|
<Suspense fallback={<h2>Loading...</h2>}>
|
|
<CacheProvider value={state.cache}>
|
|
<RouterProvider value={routeContext}>{state.root}</RouterProvider>
|
|
</CacheProvider>
|
|
</Suspense>
|
|
);
|
|
}
|
|
|
|
export default Router;
|