Skip to content

Instantly share code, notes, and snippets.

@jeffscottward
Created August 23, 2019 16:48
Show Gist options
  • Save jeffscottward/fa647b954a6df6eab6652729f950d069 to your computer and use it in GitHub Desktop.
Save jeffscottward/fa647b954a6df6eab6652729f950d069 to your computer and use it in GitHub Desktop.
OrderForm w/ State: Label + Inputs w/ onChange State sync + via HOC (useContext)
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;
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;
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;
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)
};
}
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);
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;
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