Skip to content

Instantly share code, notes, and snippets.

@Migweld
Last active January 10, 2023 20:41
Show Gist options
  • Save Migweld/9db0e82e90b263175178e8962f57d540 to your computer and use it in GitHub Desktop.
Save Migweld/9db0e82e90b263175178e8962f57d540 to your computer and use it in GitHub Desktop.
Inner page snippet & test file for a particular use-case from a NextJS app I created approximately 3 years ago. ES6 without Typescript. Demo can be found at https://babyfoodnpm.org
/* eslint-disable no-undef */
import React, { useReducer, useState, useEffect } from "react"
import PropTypes from "prop-types"
import Router from "next/router"
import { rules, marketingQuestions } from "../data/rules"
import { validateForm } from "../components/QuestionForm/validate"
import RulesForm from "../components/RulesForm/RulesForm"
import SectionTitle from "../components/SectionTitle/SectionTitle"
import {
ADVANCE_FORM,
UPDATE_VALUE,
UPDATE_MARKETING,
CALCULATE_RESULTS,
RETREAT_FORM,
} from "../components/actions"
import formReducer from "../data/formReducer"
import MarketingForm from "../components/MarketingForm/MarketingForm"
import ResultForm from "../components/ResultForm/ResultForm"
let flatRules = []
let filteredRules = []
let filteredMarketing = []
// ruleSet is a large JSON object, which corresponds to the various options chosen by the user and applies the correct logic
const Rules = ({ ruleSet, initialState }) => {
const [state, dispatch] = useReducer(formReducer, initialState)
const [errors, setErrors] = useState({})
const [isSubmitting, setIsSubmitting] = useState(false)
if (ruleSet.rules) {
flatRules = Object.values(ruleSet.rules)
.flat()
.map((rule) => ({
slug: rule.slug,
value: rule.value || null,
type: rule.type || null,
targets: rule.targets || null,
operator: rule.operator,
unit: rule.unit || null,
question: rule.question || null,
}))
filteredRules = Object.values(ruleSet.rules)
.flat()
.map((rule) => ({
id: rule.id,
slug: rule.slug,
question: rule.question || null,
resultQuestion: rule.resultQuestion || null,
helperText: rule.helperText || null,
}))
filteredMarketing = marketingQuestions.map((question) => ({
id: question.id,
slug: question.slug,
question: question.question,
resultQuestion: question.resultQuestion
}))
}
useEffect(() => {
if (
Object.values(errors).length === 0 &&
isSubmitting &&
state.showMarketing === true
) {
setIsSubmitting(false)
dispatch({
type: CALCULATE_RESULTS,
rules: flatRules,
marketingQuestions,
})
}
if (Object.values(errors).length === 0 && isSubmitting) {
window.scrollTo(0, 0)
setIsSubmitting(false)
dispatch({ type: ADVANCE_FORM })
}
}, [errors, isSubmitting])
const handleRuleChange = (event) => {
dispatch({
type: UPDATE_VALUE,
payload: { slug: event.target.name, value: event.target.value },
})
}
const handleQuestionChange = (event) => {
dispatch({
type: UPDATE_MARKETING,
payload: { slug: event.target.name, value: event.target.value },
})
}
const handleSubmit = () => {
if (state.showMarketing === false && state.showResults === false) {
setErrors(validateForm(state.values, Object.keys(state.values)))
} else if (state.showMarketing === true && state.showResults === false) {
setErrors(validateForm(state.marketing, Object.keys(state.marketing)))
}
setIsSubmitting(true)
}
const handleFormRetreat = () => {
if (state.showMarketing === false) {
Router.push("/?welcome=false")
} else {
window.scrollTo(0, 0)
dispatch({ type: RETREAT_FORM })
}
}
if (ruleSet.noMarketing) {
return (
<>
<SectionTitle title={ruleSet.name} description="" />
<p>
Products of this type should not be marketed to children under 36
months of age
</p>
</>
)
}
if (state.showMarketing === true && state.showResults === false) {
return (
<MarketingForm
handleQuestionChange={handleQuestionChange}
questions={marketingQuestions}
state={state}
errors={errors}
handleSubmit={handleSubmit}
handleFormRetreat={handleFormRetreat}
/>
)
}
if (state.showMarketing === false && state.showResults === true) {
return (
<ResultForm
results={state.results}
// eslint-disable-next-line no-undef
rules={filteredRules}
// eslint-disable-next-line no-undef
marketingQuestions={filteredMarketing}
category={ruleSet.slug}
categoryName={ruleSet.name}
/>
)
}
return (
<RulesForm
ruleSet={ruleSet}
handleRuleChange={handleRuleChange}
handleSubmit={handleSubmit}
errors={errors}
state={state}
handleFormRetreat={handleFormRetreat}
/>
)
}
Rules.getInitialProps = ({ query }) => {
const category = rules.find((x) => x.slug === query.slug)
const initialState = {
noMarketing: false,
showMarketing: false,
showResults: false,
results: {
marketing: {},
values: {},
},
values: {
totalEnergy: null,
saltOrSodium: null,
},
marketing: {},
category: { name: category.name, slug: category.slug },
}
if (category.noMarketing === true) {
initialState.noMarketing = true
return { ruleSet: category, initialState }
}
const flatCat = Object.values(category.rules).flat()
// Set initial values for our various rule criteria
flatCat.map((rule) => {
initialState.values[rule.slug] = null
return 0
})
// Set initial values for our marketing questions
marketingQuestions.map((question) => {
initialState.marketing[question.slug] = null
return 0
})
return { ruleSet: category, initialState }
}
Rules.propTypes = {
ruleSet: PropTypes.shape({
slug: PropTypes.string.isRequired,
noMarketing: PropTypes.bool,
name: PropTypes.string.isRequired,
rules: PropTypes.shape({
booleanRules: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
question: PropTypes.string.isRequired,
slug: PropTypes.string.isRequired,
helperText: PropTypes.string,
operator: PropTypes.boolean,
}),
),
percentageRules: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
question: PropTypes.string.isRequired,
slug: PropTypes.string.isRequired,
helperText: PropTypes.string,
operator: PropTypes.string,
value: PropTypes.number.isRequired,
}),
),
valueRules: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
question: PropTypes.string.isRequired,
slug: PropTypes.string.isRequired,
helperText: PropTypes.string,
operator: PropTypes.string.isRequired || PropTypes.bool,
unit: PropTypes.string.isRequired,
unitPer: PropTypes.string.isRequired,
value: PropTypes.number.isRequired,
}),
),
}),
}).isRequired,
initialState: PropTypes.shape({
showMarketing: PropTypes.bool.isRequired,
showResults: PropTypes.bool.isRequired,
results: PropTypes.shape({}),
values: PropTypes.shape({}),
}).isRequired,
}
export default Rules
import { compareValues } from "../lib"
describe("Sodium amendment tests", () => {
test("should calculate pass result for a passing value sodium based on per 100g and per 100Kcal", () => {
// given I have a sodium value and threshold
const sodiumValue = 5
const sodiumThreshold = 10
const totalEnergy = 60
// calculation against this threshold should account for per 100g and per 100Kcal test
const rules = [
{
slug: "sodium",
operator: "<=",
unit: "gram",
value: sodiumThreshold,
},
]
const result = compareValues({ sodium: sodiumValue, totalEnergy }, rules)
expect(result).toEqual({ sodium: true })
})
test("should calculate fail result for sodium based on a failing value for both per 100g and per 100Kcal", () => {
// given I have a sodium value and threshold
const sodiumValue = 6
const sodiumThreshold = 1
const totalEnergy = 60
// calculation against this threshold should account for per 100g and per 100Kcal test
const rules = [
{
slug: "sodium",
operator: "<=",
unit: "gram",
value: sodiumThreshold,
},
]
const result = compareValues({ sodium: sodiumValue, totalEnergy }, rules)
expect(result).toEqual({ sodium: false })
})
test("should calculate fail result for sodium based a passing value per 100g but a failing value per 100Kcal", () => {
// given I have a sodium value and threshold
const sodiumValue = 6
const sodiumThreshold = 9
const totalEnergy = 60
// calculation against this threshold should account for per 100g and per 100Kcal test
const rules = [
{
slug: "sodium",
operator: "<=",
unit: "gram",
value: sodiumThreshold,
},
]
const result = compareValues({ sodium: sodiumValue, totalEnergy }, rules)
expect(result).toEqual({ sodium: false })
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment