Form controller can handle form submit and errors

main
Ashelyn Dawn 5 years ago
parent 51f26f5692
commit 5d42b78bd8

@ -17,6 +17,19 @@ router.post('/', parseJSON, registerValidation, async (req, res) => {
req.body.password
)
if(!user){
return res.status(422).json({errors: [{
param: 'email',
msg: 'Unable to complete registration'
},{
param: 'password',
msg: ' '
},{
param: 'password2',
msg: ' '
}]})
}
const session = await db.session.create(
user.uuid,
req.ip,

@ -1,58 +1,80 @@
// TODO: Enable exportDefaultFrom in Babel syntax
import React, { useReducer } from 'react'
import axios from 'axios'
import styles from './styles.module.css'
export const Input = require('./input.js').default
export const Button = require('./button.js').default
export const FormController = function FormController({errors, errorDispatch, children, onSubmit}){
const initialState = {
fields: {}
}
const errorReducer = (errors, action) => {
switch (action.type) {
case 'set_errors':
const newErrors = {}
for(const field of action.errors)
newErrors[field.param] = field.msg
return newErrors
const reducer = (state, action)=>{
let fields = {...state.fields}
const prevField = state.fields[action.name]
case 'clear_error':
console.log('clear_error', action)
return {
...errors,
[action.field]: undefined
}
// If no value (onBlur), just mark touched and clear error
if(action.value === undefined){
errorDispatch({type: 'clear_error', field: action.name})
default:
return errors
}
}
return {
fields: {
...fields,
[action.name]: {
...prevField,
touched: true,
isValid: prevField.validate?prevField.validate(prevField.value, fields):true
}
const formReducer = (state, action)=>{
let fields = {...state.fields}
const prevField = state.fields[action.name]
// If no value (onBlur), just mark touched and clear error
if(action.value === undefined){
errorDispatch({type: 'clear_error', field: action.name})
return {
fields: {
...fields,
[action.name]: {
...prevField,
touched: true,
isValid: prevField.validate?prevField.validate(prevField.value, fields):true
}
}
}
}
// Update currently changing field
const updatedField = {
...prevField,
value: action.value !== undefined ? action.value : prevField.value,
isValid: prevField.validate?prevField.validate(action.value, fields):true
}
fields[action.name] = updatedField
// Update currently changing field
const updatedField = {
...prevField,
value: action.value !== undefined ? action.value : prevField.value,
isValid: prevField.validate?prevField.validate(action.value, fields):true
}
fields[action.name] = updatedField
// Update other fields where the validate function takes whole state
const fieldNames = Object.keys(fields)
for(const name of fieldNames){
if(name === action.name) continue
// Update other fields where the validate function takes whole state
const fieldNames = Object.keys(fields)
for(const name of fieldNames){
if(name === action.name) continue
const field = fields[name]
const field = fields[name]
if(field.validate && field.validate.length > 1)
fields[name] = {
...field,
isValid: field.validate(field.value, fields)
}
}
return {fields}
}
if(field.validate && field.validate.length > 1)
fields[name] = {
...field,
isValid: field.validate(field.value, fields)
}
}
return {fields}
export const FormController = function FormController({children, url, method = 'POST', afterSubmit = ()=>null}){
const initialState = {
fields: {}
}
// Update initial state
@ -68,28 +90,43 @@ export const FormController = function FormController({errors, errorDispatch, c
}
})
// Create reducer
const [state, dispatch] = useReducer(reducer, initialState)
// Create reducers
const [state, dispatch] = useReducer(formReducer, initialState)
const [errors, errorDispatch] = useReducer(errorReducer, {})
const submitForm = ev=>{
// Handle submitting form
const handleSubmit = async (ev) => {
if(ev) ev.preventDefault();
const fields = {}
const data = {}
for(const name in state.fields){
const field = state.fields[name]
fields[field.name] = field.value
data[field.name] = field.value
}
onSubmit(fields)
if(url)
try {
await axios({ method, url, data })
} catch (err) {
if(!err.response || err.response.status !== 422) throw err;
return errorDispatch({
type: 'set_errors',
errors: err.response.data.errors
})
}
afterSubmit(data)
}
// Map children
const _children = React.Children.map(children, child => {
if(child.type === Button && child.props.type.toLowerCase() === 'submit')
return React.cloneElement(child, {
enabled: !Object.values(state.fields).some(field=>!field.isValid),
onClick: submitForm
onClick: handleSubmit
})
const {name} = child.props;
if(!name) return child;
return React.cloneElement(child, {
@ -102,7 +139,7 @@ export const FormController = function FormController({errors, errorDispatch, c
})
return (
<form onSubmit={onSubmit} className={styles.formContainer}>
<form autocomplete="off" onSubmit={handleSubmit} className={styles.formContainer}>
{_children}
</form>
)

@ -1,25 +0,0 @@
import {useReducer} from 'react'
const errorReducer = (errors, action) => {
switch (action.type) {
case 'set_errors':
const newErrors = {}
for(const field of action.errors)
newErrors[field.param] = field.msg
return newErrors
case 'clear_error':
console.log('clear_error', action)
return {
...errors,
[action.field]: undefined
}
default:
return errors
}
}
export default function useErrorReducer(initialState = {}){
return useReducer( errorReducer )
}

@ -15,7 +15,7 @@ function Layout({ Component, pageProps, user }){
return (
<>
<Header user={user} />
<main><Component {...pageProps} /></main>
<main><Component {...{user, ...pageProps}} /></main>
<Footer/>
</>
)

@ -1,31 +1,17 @@
import React from 'react'
import React, {useEffect} from 'react'
import Link from 'next/link'
import Router from 'next/router'
import isEmail from 'validator/lib/isEmail'
import axios from 'axios'
import {FormController, Input, Button} from '~/components/form'
import useErrorReducer from '../hooks/errorReducer'
export default function Login(){
const [errors, dispatch] = useErrorReducer()
const submit = async (values)=>{
try {
await axios.post(`/api/auth`, values)
Router.push('/')
} catch (err) {
if(!err.response || err.response.status !== 422) throw err;
dispatch({
type: 'set_errors',
errors: err.response.data.errors
})
}
}
export default function Login({user}){
useEffect(()=>{
if(user) Router.push('/account')
}, [user])
return (
<FormController errors={errors} errorDispatch={dispatch} onSubmit={submit}>
<FormController url="/api/auth" afterSubmit={()=>Router.push('/account')}>
<h1>Login</h1>
<Input label="Email" type="text" name="email" validate={value=>isEmail(value)} hint="Enter a valid email address" />
<Input label="Password" type="password" name="password" validate={value=>(value.length >= 8)} hint="Password must be at least 8 characters long" />

@ -1,30 +1,17 @@
import React from 'react'
import React, {useEffect} from 'react'
import Link from 'next/link'
import Router from 'next/router'
import isEmail from 'validator/lib/isEmail'
import axios from 'axios'
import {FormController, Input, Button} from '~/components/form'
import useErrorReducer from '../hooks/errorReducer'
export default function Register(){
const [errors, dispatch] = useErrorReducer()
const submit = async (values)=>{
try {
const {data} = await axios.post(`/api/users`, values)
console.log(data)
} catch (err) {
if(!err.response || err.response.status !== 422) throw err;
dispatch({
type: 'set_errors',
errors: err.response.data.errors
})
}
}
export default function Register({user}){
useEffect(()=>{
if(user) Router.push('/account')
}, [user])
return (
<FormController errors={errors} errorDispatch={dispatch} onSubmit={submit}>
<FormController url="/api/users" afterSubmit={()=>Router.push('/account')}>
<h1>Register</h1>
<Input label="Email" type="text" name="email" validate={value=>isEmail(value)} hint="Enter a valid email address" />
<Input label="Password" type="password" name="password" validate={value=>(value.length >= 8)} hint="Password must be at least 8 characters long" />

Loading…
Cancel
Save