Last active
January 10, 2023 20:41
-
-
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
This file contains 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
/* 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 |
This file contains 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 { 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