Last active
March 19, 2019 11:22
-
-
Save jamesknelson/0a6ddb2807b11051859972b2f48668b9 to your computer and use it in GitHub Desktop.
Two APIs for routing with React that support POST methods and SSR.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
UPDATE: | |
This component and hook based routing/fetching API won't work, as `useAsync()` | |
is an impossible component. | |
In order to use async functions to respond to route changes, the functions will | |
need to be registered with a parent cache/provider with a unique key. As such, a | |
more natural component-based architecture would involve a `<Route path>` component | |
that specifies any dependencies. | |
For details on why, see: https://twitter.com/james_k_nelson/status/1107932897900552194 | |
**/ | |
/* | |
* Perform routing with methods on both the client and server, using a | |
* hypothetical component/hook based API inspired by @reach/router. | |
* | |
* Pros: | |
* - It's "just React" | |
* - Can use React context | |
* | |
* Cons: | |
* - Can't build a list of URLs or find their route details at runtime | |
* - Hook-based routing logic is (in my opinion) harder to follow | |
* - Won't work with suspense until streaming SSR (late this year or next year) | |
*/ | |
// App.js | |
const Login = React.lazy(() => import('./Login')) | |
const App = () => | |
<Router> | |
<Login path='/login' /> | |
</Router> | |
// Login.js | |
const LoginRoute = ({ request }) => { | |
let auth = useAuth() | |
// Store any error on window.history.state, so that a request whose email/ | |
// password is incorrect won't be re-run on forward/back. | |
let [error, setError] = useStateSerializedToHistory('loginerror') | |
// Wait for the argument function to resolve, re-running it each | |
// time `request` or `error` changes. | |
let loginSucceeded = useAsync(async () => { | |
if (request.method === 'post' && !error) { | |
try { | |
let { email, password } = request.body | |
await auth.signInWithEmailAndPassword(email, password) | |
} | |
catch (error) { | |
setError(error) | |
} | |
} | |
}, [request, error]) | |
if (loginSucceeded) { | |
return ( | |
<Redirect to='/dashboard' /> | |
) | |
} | |
return <> | |
<Status code={error ? 400 : 200} error={error} /> | |
<Head> | |
<title>Login</title> | |
</Head> | |
<Login /> | |
</> | |
} | |
function Login() { | |
// Bind the form's state to window.history.state, so that accidentally | |
// going back/forward will not result in the state being lost | |
let [model, setModel] = useStateSerializedToHistory('loginform') | |
return ( | |
<Form method='post' initialValue={model} onChange={setModel}> | |
<Form.Errors /> | |
<Form.Field name='email' type='email' /> | |
<Form.Field name='password' type='password' /> | |
<Form.SubmitButton> | |
Login | |
</Form.SubmitButton> | |
</Form> | |
) | |
} | |
export default LoginRoute |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Perform routing on the client and server, using Navi's routing | |
* existing API. | |
* | |
* Pros: | |
* - Works server-side right now, doesn't need streaming SSR | |
* - Can build a list of the site's URLs and routing data at runtime | |
* - Unlike hooks, you can use if/else/async/await/try/catch without restriction | |
* | |
* Cons: | |
* - Navi's routing functions aren't "just React" | |
* - Unable to access React context or props within routing code | |
*/ | |
// App.js | |
const routes = mount({ | |
'/login': lazy(() => import('./Login')) | |
}) | |
const App = () => | |
<Router routes={routes}> | |
<View /> | |
</Router> | |
// Login.js | |
export default map(async request => { | |
let auth = request.context.auth | |
let state = { ...request.state } | |
// Store any error on window.history.state, so that a request whose email/ | |
// password is incorrect won't be re-run on forward/back. | |
if (request.method === 'post' && !state.error) { | |
try { | |
let { email, password } = request.body | |
await auth.signInWithEmailAndPassword(email, password) | |
return redirect('/dashboard') | |
} | |
catch (error) { | |
state.error = e | |
} | |
} | |
return route({ | |
error: state.error, | |
status: state.error ? 400 : 200, | |
title: 'Login', | |
view: <Login />, | |
// Set's the value of history.state if this request is re-rendered due | |
// to forward/back buttons, or after pre-rendering on the server. | |
state, | |
}) | |
}) | |
const Login = () => { | |
// Bind the form's state to window.history.state, so that accidentally | |
// going back/forward will not result in the state being lost | |
let [model, setModel] = useStateSerializedToHistory('loginform') | |
return ( | |
<Form method='post' initialValue={model} onChange={setModel}> | |
<Form.Errors /> | |
<Form.Field name='email' type='email' /> | |
<Form.Field name='password' type='password' /> | |
<Form.SubmitButton> | |
Login | |
</Form.SubmitButton> | |
</Form> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment