From 5d42b78bd800c82b8b5578e4a3e84d1880848730 Mon Sep 17 00:00:00 2001 From: Ashelyn Dawn Date: Thu, 12 Mar 2020 22:32:12 -0600 Subject: [PATCH 1/4] Form controller can handle form submit and errors --- api/users.js | 13 ++++ components/form/form.js | 129 ++++++++++++++++++++++++++-------------- hooks/errorReducer.js | 25 -------- pages/_app.js | 2 +- pages/login.js | 26 ++------ pages/register.js | 27 +++------ 6 files changed, 110 insertions(+), 112 deletions(-) delete mode 100644 hooks/errorReducer.js diff --git a/api/users.js b/api/users.js index 0d98625..8227718 100644 --- a/api/users.js +++ b/api/users.js @@ -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, diff --git a/components/form/form.js b/components/form/form.js index 1410303..5f77304 100644 --- a/components/form/form.js +++ b/components/form/form.js @@ -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 ( -
+ {_children}
) diff --git a/hooks/errorReducer.js b/hooks/errorReducer.js deleted file mode 100644 index 01871dc..0000000 --- a/hooks/errorReducer.js +++ /dev/null @@ -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 ) -} \ No newline at end of file diff --git a/pages/_app.js b/pages/_app.js index 5c38e14..f541e87 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -15,7 +15,7 @@ function Layout({ Component, pageProps, user }){ return ( <>
-
+