Last active
December 7, 2020 22:53
-
-
Save brlafreniere/78276e74552730b50f4f260a17c1bf7b to your computer and use it in GitHub Desktop.
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
// This code is from a language learning web application that I've been building as a hobby project. | |
// The `Login` function is a React component. It displays a login form to the user if the user is not logged in. | |
// If they are already logged in, they are redirected to the home page. | |
// The form is submitted to a Rails backend, which uses the Devise gem configured in API mode. | |
// A JSON Web Token is returned, and the client stores it as a cookie. | |
// The other file in this gist, `store.js`, is a data structure which represents the front-end's global state. | |
import Layout from "components/layout" | |
import { useState } from "react" | |
import Axios from "axios" | |
import {useRouter} from "next/router" | |
import { hasAuthorizationCookie } from "lib/auth" | |
import { initializeStore } from "lib/store" | |
async function submitLogin(email, password, router) { | |
let formData = new FormData(); | |
formData.append("user[email]", email) | |
formData.append("user[password]", password) | |
let result = await Axios.post(`${process.env.API_URL}/users/sign_in.json`, formData, { | |
headers: { | |
'Content-Type': 'text/json' | |
} | |
}) | |
if (result.headers.authorization) { | |
document.cookie = `authorization=${result.headers.authorization}; path=/` | |
router.push('/') | |
} | |
} | |
export default function Login(props) { | |
const [email, setEmail] = useState(null) | |
const [password, setPassword] = useState(null) | |
const router = useRouter(); | |
if (!props.loggedIn) { | |
return ( | |
<Layout> | |
<h2 className="mb-5">Login</h2> | |
<form onSubmit={(e) => { e.preventDefault(); submitLogin(email, password, router)}} method="POST"> | |
<div className="form-group"> | |
<label htmlFor="email">Email</label> | |
<input type="text" name="email" className="form-control" onChange={(e) => { setEmail(e.target.value) }} /> | |
</div> | |
<div className="form-group"> | |
<label htmlFor="password">Password</label> | |
<input type="password" name="password" className="form-control" onChange={(e) => { setPassword(e.target.value) }} /> | |
</div> | |
<div className="form-group"> | |
<input type="submit" value="Submit" className="form-control btn btn-primary mt-3" /> | |
</div> | |
</form> | |
</Layout> | |
) | |
} else { | |
if (typeof window !== 'undefined') { | |
router.push('/') | |
} | |
return null; | |
} | |
} | |
export function getServerSideProps(context) { | |
const reduxStore = initializeStore() | |
const result = hasAuthorizationCookie(context) | |
const { dispatch } = reduxStore | |
if (result) { | |
dispatch({ type: "LOGIN" }) | |
} else { | |
dispatch({ type: "LOGOUT" }) | |
} | |
return { props: {initialReduxState: reduxStore.getState()} } | |
} |
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
// this code works with a front-end react framework called Next.js, a framework which provides some nice features such as | |
// server-side rendering for react. | |
// | |
// Next.js also provides a nice page structure. | |
// | |
// this code was taken from an example provided by the next.js example repository, | |
// and then changed/adapted for my project's needs... it's mostly boilerplate stuff, | |
// not super important to understand but it essentially provides a "global state" to the frontend of my app. | |
// | |
// For now, the state is used to determine whether the user is logged in. | |
// This could store other user information as well in the future, like the user's profile information, | |
// without having to fetch it from the server on every page load. | |
// | |
// this global state functionality is provided by a library called Redux, which is a part of the React ecosystem of libraries. | |
import { useMemo } from 'react' | |
import { createStore, applyMiddleware } from 'redux' | |
import { composeWithDevTools } from 'redux-devtools-extension' | |
let store | |
const initialState = { | |
loggedIn: false, | |
} | |
const reducer = (state = initialState, action) => { | |
switch (action.type) { | |
case 'LOGIN': | |
return { | |
...state, | |
loggedIn: true, | |
} | |
case 'LOGOUT': | |
return { | |
...state, | |
loggedIn: false, | |
} | |
default: | |
return state | |
} | |
} | |
function initStore(preloadedState = initialState) { | |
return createStore( | |
reducer, | |
preloadedState, | |
composeWithDevTools(applyMiddleware()) | |
) | |
} | |
export const initializeStore = (preloadedState) => { | |
let _store = store ?? initStore(preloadedState) | |
// After navigating to a page with an initial Redux state, merge that state | |
// with the current state in the store, and create a new store | |
if (preloadedState && store) { | |
_store = initStore({ | |
...store.getState(), | |
...preloadedState, | |
}) | |
// Reset the current store | |
store = undefined | |
} | |
// For SSG and SSR always create a new store | |
if (typeof window === 'undefined') return _store | |
// Create the store once in the client | |
if (!store) store = _store | |
return _store | |
} | |
export function useStore(initialState) { | |
const store = useMemo(() => initializeStore(initialState), [initialState]) | |
return store | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment