Created
August 23, 2019 16:48
-
-
Save jeffscottward/fa647b954a6df6eab6652729f950d069 to your computer and use it in GitHub Desktop.
OrderForm w/ State: Label + Inputs w/ onChange State sync + via HOC (useContext)
This file contains hidden or 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
import React from "react"; | |
import capitalize from "../utils/capitalize"; | |
const FormGroup = props => { | |
const { | |
group, | |
field, | |
onChange, | |
type, | |
size, | |
min, | |
minLength, | |
max, | |
maxLength, | |
required, | |
optional, | |
disabled, | |
state, | |
allCapsLabel, | |
subFormGroup | |
} = props; | |
return ( | |
<div className={"form-group" + (subFormGroup ? " sub-form-group" : "")}> | |
<label htmlFor={group + "-" + field}> | |
<span>{capitalize(field)} </span> | |
{optional ? <span className="flag">(optional)</span> : ""} | |
</label> | |
<input | |
onChange={onChange} | |
id={group + "-" + field} | |
value={state[group][field] || ""} | |
className={field + (size ? " " + size : "")} | |
type={type} | |
min={type === "number" ? min : ""} | |
minLength={type === "text" ? minLength : ""} | |
max={type === "number" ? max : ""} | |
maxLength={type === "text" ? maxLength : ""} | |
required={required !== "" ? required : ""} | |
disabled={disabled !== "" ? disabled : ""} | |
/> | |
<style jsx>{` | |
.form-group { | |
display: flex; | |
justify-content: flex-start; | |
margin-bottom: 1rem; | |
} | |
.form-group * { | |
width: 100%; | |
} | |
.form-group label, | |
.form-group input { | |
font-size: 1.3rem; | |
} | |
.form-group input.small { | |
max-width: 5rem; | |
} | |
.form-group label { | |
text-align: right; | |
display: flex; | |
line-height: 1; | |
align-items: center; | |
justify-content: flex-end; | |
text-transform: ${allCapsLabel ? "uppercase" : "none"}; | |
} | |
.form-group label { | |
max-width: 10rem; | |
margin-right: 0.5rem; | |
} | |
.form-group label .flag { | |
font-size: 0.7rem; | |
line-height: 1; | |
} | |
.sub-form-group { | |
margin-bottom: 0.5rem; | |
margin-right: 1rem; | |
display: flex; | |
flex-direction: column; | |
} | |
.sub-form-group label { | |
justify-content: flex-start; | |
text-align: left; | |
margin: 0; | |
font-size: 0.9rem; | |
} | |
`}</style> | |
</div> | |
); | |
}; | |
export default FormGroup; |
This file contains hidden or 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
const Global = { | |
css: { | |
colors: { | |
state: { | |
active: "#4F9FC7" | |
}, | |
header: { | |
bg: "#333333", | |
items: "#FFFFFF" | |
}, | |
theme: [ | |
{ | |
themec0: "rgba(17, 63, 63, 1)", | |
themec1: "rgba(239, 204, 4, 1)", | |
themec2: "rgba(216, 165, 2, 1)", | |
themec3: "rgba(188, 126, 4, 1)", | |
themec4: "rgba(10, 13, 14, 1)" | |
}, | |
{ | |
themec0: "rgba(31, 54, 107, 1)", | |
themec1: "rgba(232, 237, 242, 1)", | |
themec2: "rgba(144, 156, 167, 1)", | |
themec3: "rgba(43, 75, 91, 1)", | |
themec4: "rgba(14, 31, 40, 1)", | |
} | |
] | |
}, | |
sizes: { | |
pageMaxWidth: "1440px", | |
pageMinWidth: "640px" | |
} | |
} | |
}; | |
export default Global; |
This file contains hidden or 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
const InitialState = { | |
user: {}, | |
cart: { | |
jacket: { | |
crypto: undefined, | |
size: undefined, | |
quantity: 0 | |
}, | |
billing: { | |
name: undefined, | |
addr1: undefined, | |
addr2: undefined, | |
city: undefined, | |
state: undefined, | |
zip: undefined | |
}, | |
creditCard: { | |
name: undefined, | |
number: undefined, | |
month: undefined, | |
year: undefined, | |
cvc: undefined | |
}, | |
shipping: { | |
name: undefined, | |
addr1: undefined, | |
addr2: undefined, | |
city: undefined, | |
state: undefined, | |
zip: undefined | |
} | |
} | |
}; | |
export default InitialState; |
This file contains hidden or 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
function userReducer(state, action) { | |
console.log("user", state) | |
switch (action.type) { | |
case "SOME_ACTION_DISPATCH": | |
// Do something with action.payload | |
return { ...state }; | |
default: | |
return { ...state }; | |
} | |
} | |
function cartReducer(state, action) { | |
console.log("cart", state); | |
switch (action.type) { | |
case "DECREMENT_QUANTITY": | |
if (state.jacket.quantity > 0) { | |
state.jacket.quantity--; | |
} | |
return { ...state }; | |
case "INCREMENT_QUANTITY": | |
state.jacket.quantity++; | |
return { ...state }; | |
case "CHANGE_SIZE": | |
state.jacket.size = action.payload; | |
return { ...state }; | |
case "CHANGE_CRYPTO": | |
state.jacket.crypto = action.payload; | |
return { ...state }; | |
case "CHANGE_USERINFO": | |
let blobarea = Object.keys(action.payload)[0]; | |
let inputField = Object.keys(action.payload[blobarea])[0]; | |
state[blobarea][inputField] = action.payload[blobarea][inputField]; | |
return { ...state }; | |
default: | |
return { ...state }; | |
} | |
} | |
export default function mainReducer ({ user, cart }, action) { | |
// middleware goes here, i.e calling analytics service, etc. | |
// console.log(action) | |
return { | |
user: userReducer(user, action), | |
cart: cartReducer(cart, action) | |
}; | |
} |
This file contains hidden or 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
import React from "react"; | |
import { withRouter } from "next/router"; | |
import { useStateValue } from "../state/state"; | |
// import {get, post} from "axios"; | |
import FormGroup from "./FormGroup"; | |
import SubFormWrap from "./SubFormWrap"; | |
import PurchaseBtn from "./PurchaseBtn" | |
const OrderForm = props => { | |
const [{ cart }, dispatch] = useStateValue(); | |
const handleFormSubmit = e => { | |
e.preventDefault(); | |
console.log(e) | |
} | |
const handleInputChange = e => { | |
e.preventDefault(); | |
console.log(e.target.id.split("-")); | |
dispatch({ | |
type: "CHANGE_USERINFO", | |
payload: { | |
[e.target.id.split("-")[0]]: { | |
[e.target.id.split("-")[1]]: e.target.value | |
} | |
} | |
}); | |
}; | |
// NOTE: value={someVal || '' } | |
// Short-circuit evaluation to force to "controlled" input | |
// https://bit.ly/2TWV7JI | |
return ( | |
<form id="OrderForm" onSubmit={handleFormSubmit}> | |
<div className="form-billing"> | |
<h3>Billing</h3> | |
<div className="form-area"> | |
<h4>Address</h4> | |
<div className="address-input"> | |
<FormGroup | |
group="billing" | |
field="name" | |
onChange={handleInputChange} | |
state={cart} | |
type="text" | |
required | |
/> | |
<FormGroup | |
group="billing" | |
field="addr1" | |
onChange={handleInputChange} | |
state={cart} | |
type="text" | |
required | |
/> | |
<FormGroup | |
group="billing" | |
field="addr2" | |
onChange={handleInputChange} | |
state={cart} | |
type="text" | |
optional | |
/> | |
<FormGroup | |
group="billing" | |
field="city" | |
onChange={handleInputChange} | |
state={cart} | |
type="text" | |
required | |
/> | |
<FormGroup | |
group="billing" | |
field="state" | |
onChange={handleInputChange} | |
state={cart} | |
type="text" | |
required | |
/> | |
<FormGroup | |
group="billing" | |
field="zip" | |
size="small" | |
onChange={handleInputChange} | |
state={cart} | |
type="number" | |
min={10000} | |
max={99999} | |
required | |
/> | |
</div> | |
</div> | |
<div className="form-area"> | |
<h4>Credit Card</h4> | |
<div className="creditCard-input"> | |
<FormGroup | |
group="creditCard" | |
field="name" | |
onChange={handleInputChange} | |
state={cart} | |
type="text" | |
required | |
/> | |
<FormGroup | |
group="creditCard" | |
field="number" | |
onChange={handleInputChange} | |
state={cart} | |
type="number" | |
min={1000000000000000} | |
max={9999999999999999} | |
required | |
/> | |
<SubFormWrap label="Expires"> | |
<FormGroup | |
group="creditCard" | |
field="month" | |
onChange={handleInputChange} | |
state={cart} | |
type="number" | |
size="small" | |
min={0} | |
max={12} | |
required | |
subFormGroup | |
/> | |
<FormGroup | |
group="creditCard" | |
field="year" | |
onChange={handleInputChange} | |
state={cart} | |
type="number" | |
size="small" | |
min={String(new Date().getFullYear()).split("20")[1]} | |
max={12} | |
required | |
subFormGroup | |
/> | |
</SubFormWrap> | |
<FormGroup | |
group="creditCard" | |
field="cvc" | |
onChange={handleInputChange} | |
state={cart} | |
type="number" | |
size="small" | |
allCapsLabel | |
min={0} | |
max={999} | |
required | |
/> | |
</div> | |
</div> | |
</div> | |
<div className="form-shipping"> | |
<h3>Shipping</h3> | |
<div className="form-area"> | |
<h4>Address</h4> | |
<div className="address-input"> | |
<FormGroup | |
group="shipping" | |
field="name" | |
onChange={handleInputChange} | |
state={cart} | |
type="text" | |
required | |
/> | |
<FormGroup | |
group="shipping" | |
field="addr1" | |
onChange={handleInputChange} | |
state={cart} | |
type="text" | |
required | |
/> | |
<FormGroup | |
group="shipping" | |
field="addr2" | |
onChange={handleInputChange} | |
state={cart} | |
type="text" | |
optional | |
/> | |
<FormGroup | |
group="shipping" | |
field="city" | |
onChange={handleInputChange} | |
state={cart} | |
type="text" | |
required | |
/> | |
<FormGroup | |
group="shipping" | |
field="state" | |
onChange={handleInputChange} | |
state={cart} | |
type="text" | |
required | |
/> | |
<FormGroup | |
group="shipping" | |
field="zip" | |
size="small" | |
onChange={handleInputChange} | |
state={cart} | |
type="number" | |
min={10000} | |
max={99999} | |
required | |
/> | |
</div> | |
</div> | |
<PurchaseBtn /> | |
</div> | |
<style jsx>{` | |
#OrderForm { | |
display: flex; | |
justify-content: space-between; | |
} | |
#OrderForm > * { | |
width: 48%; | |
margin-bottom: 4rem; | |
} | |
h3 { | |
margin-bottom: 0.5rem; | |
} | |
h4 { | |
margin-bottom: 0.5rem; | |
} | |
h4 + div { | |
margin-left: 0.5rem; | |
} | |
.form-area { | |
margin-bottom: 2rem; | |
margin-left: 0.5rem; | |
} | |
`}</style> | |
</form> | |
); | |
}; | |
export default withRouter(OrderForm); |
This file contains hidden or 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
import React from "react"; | |
import Global from "../constants/Global"; | |
const PurchaseBtn = props => { | |
return ( | |
<> | |
<input className="purchase-btn" type="submit" value="Purchase" /> | |
<style jsx>{` | |
.purchase-btn { | |
font-size: 1.5rem; | |
padding: 1rem; | |
background: ${Global.css.colors.theme[1].themec4}; | |
color: white; | |
} | |
.purchase-btn:hover { | |
background: ${Global.css.colors.theme[1].themec3}; | |
} | |
`}</style> | |
</> | |
); | |
}; | |
export default PurchaseBtn; |
This file contains hidden or 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
import React from "react"; | |
const SubFormWrap = props => { | |
const { allCapsLabel, children } = props; | |
return ( | |
<div className="sub-form-wrap"> | |
<label> | |
<span>{props.label}</span> | |
</label> | |
{children} | |
<style jsx>{` | |
.sub-form-wrap { | |
display: flex; | |
justify-content: flex-start; | |
margin-bottom: 1rem; | |
} | |
.sub-form-wrap * { | |
width: 100%; | |
} | |
.sub-form-wrap label { | |
max-width: 10rem; | |
margin-right: 0.5rem; | |
font-size: 1.3rem; | |
} | |
.sub-form-wrap label { | |
text-align: right; | |
display: flex; | |
line-height: 1; | |
align-items: center; | |
justify-content: flex-end; | |
text-transform: ${allCapsLabel ? "uppercase" : "none"}; | |
} | |
`} | |
</style> | |
</div> | |
); | |
}; | |
export default SubFormWrap; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment