Skip to content

Instantly share code, notes, and snippets.

@brlafreniere
Last active December 7, 2020 22:53
Show Gist options
  • Save brlafreniere/78276e74552730b50f4f260a17c1bf7b to your computer and use it in GitHub Desktop.
Save brlafreniere/78276e74552730b50f4f260a17c1bf7b to your computer and use it in GitHub Desktop.
// 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 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