React - Complex state Management
Objective:
To give the learners a visual on how the 14 skills, modules & methodologies come together. To help learners decide on the next steps based on the skills check in scores.
Package used:
Requirement:
Users are permitted to input numeric scores for each module in ‘MyScore’ against specific skills. These scores will be used to calculate and update critical skill averages and provide learning guidance.
Critical score column values are calculate and displayed as follows
Module 1 – Skill1_1
Module2 – skill2_1
Module3- (Skill3_2+Skill3_3)/2
Module4-Skill4_1
Average all value column are calculate and displayed as follows
If Skill1_1 = 2 and Skill1_2=3 then Average all value = average (skill1_1+skill1_2) = (2+3)/2 = 2.5
If Skill1_1 = 2, Skill1_2=3 and Skill1_3 = 4 then Average all value = average (skill1_1+skill1_2+skill1_3) = (2+3+4)/3 = 3
If Skill1_1 = 2, Skill1_2=3, Skill1_3 = 4 and skill1_4=7 then Average all value = average (skill1_1+skill1_2+skill1_3+skill1_4) = (2+3+4+7)/4 = 4
Similarly calculate for all modules, excluding zero
The Learning Guidance column displays guidance calculated from the average of the averageOfSkill and CriticalSkill column values
=IF(Skill1_1 = 0,
"Please enter your score",
IF(AND(averageOfSkill for Module < 6 and CriticalSkill of Module < 6),
"Do Value Foundation Interactive & Team Challenge",
IF(CriticalSkill for Module < 6,
"Do Value Foundation Interactive Session only",
IF(averageOfSkill for Module < 6,
"Do Value Foundation Team Challenge only",
"You are doing good, continue to sharpen the skills to get an 8 !"
)
)
)
)
Implementation:
InitialState:
The initialState represents the desired initial state value for a component. It can be of any type, but when passed as a function, it serves as an initializer function. React calls this function during component initialization and stores its return value as the initial state
InitalState object contains Skill, Calculate and selectedModule
Skill:
object is updated when values are entered by user
Calculate:
object is Calculated and updated critical skill, all skills, and guidance after the user enters a value.
SelectedModule:
string is to refer which module is updated recently/lastly
const initialValue = {
skill: {
module1: {
skill1_1: 0,
skill1_2: 0,
skill1_3: 0,
skill1_4: 0,
},
module2: {
skill2_1: 0,
skill2_2: 0,
skill2_3: 0,
},
module3: {
skill3_1: 0,
skill3_2: 0,
skill3_3: 0,
skill3_4: 0,
skill3_5: 0,
},
module4: {
skill4_1: 0,
skill4_2: 0,
},
},
calculate: {
module1: { criticalSkill: 0, allSkills: 0, guidance: "" },
},
selectedModule: "",
};
GUIDANCE constant string values after calculating critical skills, all skills.
const SKILL_CHECK_CONSTANTS = {
centricity: "Please enter your score",
skillBothLess: "Do Value Foundation Interactive & Team Challenge",
criticalSkill: "Do Value Foundation Interactive Session only",
allSkills: "Do Value Foundation Team Challenge only",
skillBothGreater:
"You are doing good, continue to sharpen the skills to get an 8 !",
};
Check for empty object
const isEmpty = (obj) => {
return Object.values(obj).every((x) => x === "" || x >= 0);
};
useState is a React Hook that returns an array containing two values:
Remember to use useState at the top level of your component to declare one or more state variables
const [skillScore, setSkillScore] = useState(initialValue);
useEffect is a React Hook that lets you synchronize a component with an external system.
Two useEffect are used namely to update methods
useEffect(() => {
updateAverageAllSkills();
}, [skillScore.skill[skillScore.selectedModule]]);
useEffect(() => {
updateGuidance();
}, [skillScore.calculate[skillScore.selectedModule]?.allSkills]);
when user update numberic doUpdate method is called
- When user enter numeric value in textbox then state value is updated
- Critical skill value is selected and update in state
const doUpdate = (module, e) => {
//updating the state
setSkillScore((prevState) => ({
skill: {
...prevState.skill,
[module]: {
...prevState.skill[module],
[e.target.name]: e.target.value,
},
},
calculate: {
...prevState.calculate,
},
selectedModule: module,
}));
//call criticalskill and update skill values in state
updateCriticalSkill(module, e.target.name, e.target.value);
};
updateCriticalSkill method will does the following
- update skill for selected module and criticalSkill inside state value is updated, using spread operator to update an object property
const updateCriticalSkill = (module, chkSkill, score) => {
//uupdate skill for selected module
if (chkSkill === "skill1_1") {
setSkillScore((prevState) => ({
skill: {
...prevState.skill,
},
calculate: {
...prevState.calculate,
[module]: {
...prevState.calculate[module],
criticalSkill: score,
},
},
selectedModule: prevState.selectedModule,
}));
}
};
In updateAverageAllSkills method the following steps are namely
- declare module – assign and find module
- clone module- copy an Object, where not to update the same object
- cleanup data - remove zero, string and null, which should contain only numeric value
- calculate the average
- finally update state
const updateAverageAllSkills = () => {
// declare module
const module = skillScore.selectedModule;
if (module == "") return;
//clone the module
let cloneObj = { ...skillScore.skill[module] };
if (!isEmpty(cloneObj)) return;
//get all its value
Object.keys(cloneObj).forEach((key) => {
if (cloneObj[key] <= 0) {
delete cloneObj[key];
}
});
Calculate the average
const values = Object.values(cloneObj);
const sum = values.reduce((a, b) => Number(a) + Number(b), 0);
const average = sum / values.length;
//updating the state
setSkillScore((prevState) => ({
skill: {
...prevState.skill,
},
calculate: {
...prevState.calculate,
[module]: {
...prevState.calculate[module],
allSkills: average,
},
},
selectedModule: prevState.selectedModule,
}));
};
updateGuidance method
const updateGuidance = () => {
let module = skillScore.selectedModule;
const skill = skillScore.calculate[module];
//define variable
let guidance_local = "";
if (!skill) return;
//find guidance for selected skill
if (skill.criticalSkill <= 0) {
guidance_local = SKILL_CHECK_CONSTANTS.centricity;
} else if (skill.criticalSkill < 6 && skill.allSkills < 6) {
guidance_local = SKILL_CHECK_CONSTANTS.skillBothLess;
} else if (skill.criticalSkill < 6) {
guidance_local = SKILL_CHECK_CONSTANTS.criticalSkill;
} else if (skill.allSkills < 6) {
guidance_local = SKILL_CHECK_CONSTANTS.allSkills;
} else {
guidance_local = SKILL_CHECK_CONSTANTS.skillBothGreater;
}
//update guidance value in state
setSkillScore((prevState) => ({
skill: {
...prevState.skill,
},
calculate: {
...prevState.calculate,
[module]: {
...prevState.calculate[module],
guidance: guidance_local,
},
},
selectedModule: prevState.selectedModule,
}));
};
Limitation
FullCode
import React, { useEffect, useState } from "react";
const initialValue = {
skill: {
module1: {
skill1_1: 0,
skill1_2: 0,
skill1_3: 0,
skill1_4: 0,
},
module2: {
skill2_1: 0,
skill2_2: 0,
skill2_3: 0,
},
module3: {
skill3_1: 0,
skill3_2: 0,
skill3_3: 0,
skill3_4: 0,
skill3_5: 0,
},
module4: {
skill4_1: 0,
skill4_2: 0,
},
},
calculate: {
module1: { criticalSkill: 0, allSkills: 0, guidance: "" },
},
selectedModule: "",
};
const SKILL_CHECK_CONSTANTS = {
centricity: "Please enter your score",
skillBothLess: "Do Value Foundation Interactive & Team Challenge",
criticalSkill: "Do Value Foundation Interactive Session only",
allSkills: "Do Value Foundation Team Challenge only",
skillBothGreater:
"You are doing good, continue to sharpen the skills to get an 8 !",
};
export const TTiVSkillCheck = () => {
const [skillScore, setSkillScore] = useState(initialValue);
useEffect(() => {
updateAverageAllSkills();
}, [skillScore.skill[skillScore.selectedModule]]);
useEffect(() => {
updateGuidance();
}, [skillScore.calculate[skillScore.selectedModule]?.allSkills]);
const isEmpty = (obj) => {
return Object.values(obj).every((x) => x === "" || x >= 0);
};
const doUpdate = (module, e) => {
setSkillScore((prevState) => ({
skill: {
...prevState.skill,
[module]: {
...prevState.skill[module],
[e.target.name]: e.target.value,
},
},
calculate: {
...prevState.calculate,
},
selectedModule: module,
}));
updateCriticalSkill(module, e.target.name, e.target.value);
};
const updateCriticalSkill = (module, chkSkill, score) => {
if (chkSkill === "vfDecisionMaking") {
setSkillScore((prevState) => ({
skill: {
...prevState.skill,
},
calculate: {
...prevState.calculate,
[module]: {
...prevState.calculate[module],
criticalSkill: score,
},
},
selectedModule: prevState.selectedModule,
}));
}
};
const updateAverageAllSkills = () => {
const module = skillScore.selectedModule;
if (module == "") return;
let cloneObj = { ...skillScore.skill[module] };
if (!isEmpty(cloneObj)) return;
Object.keys(cloneObj).forEach((key) => {
if (cloneObj[key] <= 0) {
delete cloneObj[key];
}
});
const values = Object.values(cloneObj);
const sum = values.reduce((a, b) => Number(a) + Number(b), 0);
const average = sum / values.length;
setSkillScore((prevState) => ({
skill: {
...prevState.skill,
},
calculate: {
...prevState.calculate,
[module]: {
...prevState.calculate[module],
allSkills: average,
},
},
selectedModule: prevState.selectedModule,
}));
};
const updateGuidance = () => {
let module = skillScore.selectedModule;
const skill = skillScore.calculate[module];
let guidance_local = "";
if (!skill) return;
if (skill.criticalSkill <= 0) {
console.log("critical less than 0", skill.criticalSkill);
guidance_local = SKILL_CHECK_CONSTANTS.centricity;
} else if (skill.criticalSkill < 6 && skill.allSkills < 6) {
console.log(
"critical and all less than 6",
skill.criticalSkill,
skill.allSkills
);
guidance_local = SKILL_CHECK_CONSTANTS.skillBothLess;
} else if (skill.criticalSkill < 6) {
console.log("critical less than 6", skill.criticalSkill, skill.allSkills);
guidance_local = SKILL_CHECK_CONSTANTS.criticalSkill;
} else if (skill.allSkills < 6) {
console.log(" all less than 6", skill.criticalSkill, skill.allSkills);
guidance_local = SKILL_CHECK_CONSTANTS.allSkills;
} else {
console.log(" else ", skill.criticalSkill, skill.allSkills);
guidance_local = SKILL_CHECK_CONSTANTS.skillBothGreater;
}
setSkillScore((prevState) => ({
skill: {
...prevState.skill,
},
calculate: {
...prevState.calculate,
[module]: {
...prevState.calculate[module],
guidance: guidance_local,
},
},
selectedModule: prevState.selectedModule,
}));
};
// console.log(skillScore);
return (
<>
<div
className="grid grid-cols-1 mt-6 px-8 min-h-[800px]"
id="ttiv-container"
>
<div className="max-w-full bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<form className="">
<div className="grid grid-cols-[20%_20%_10%_10%_10%_30%] gap-1 text-white">
<div className="flex justify-center items-center py-2 bg-[#21A5B7]">
Module
</div>
<div className="flex justify-center items-center py-2 bg-[#21A5B7]">
Skill
</div>
<div className="flex justify-center items-center py-2 bg-[#21A5B7]">
My Score
</div>
<div className="flex justify-center items-center py-2 bg-gray-400">
critical skill
</div>
<div className="flex justify-center items-center py-2 bg-gray-400">
Average, all skills
</div>
<div className="flex justify-center items-center py-2 bg-[#21A5B7]">
You TTiV Learning Guidance
</div>
<div className="flex justify-center items-center py-2 bg-[#21A5B7]">
Value Foundation
</div>
<div className=" bg-[#21A5B7]">
<div className="py-2 border-b-[3px] border-[#FFC600]">
Value based Decision Making
</div>
<div className="py-2">Collaboration</div>
<div className="py-2">Systems Thinking</div>
<div className="py-2">Customer Centricity</div>
</div>
<div className="">
<input
className="shadow border py-2 rounded text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
name="skill1_1"
type="text"
onChange={(e) => doUpdate("module1", e)}
/>
<input
className="shadow border py-2 rounded text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
name="skill1_2"
type="text"
onChange={(e) => doUpdate("module1", e)}
/>
<input
className="shadow border py-2 rounded text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
name="skill1_3"
type="text"
onChange={(e) => doUpdate("module1", e)}
/>
<input
className="shadow border py-2 rounded text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
name="skill1_4"
type="text"
onChange={(e) => doUpdate("module1", e)}
/>
</div>
<div className="flex justify-center items-center py-2 bg-gray-400">
critical skill
</div>
<div className="flex justify-center items-center py-2 bg-gray-400">
Average, all skills
</div>
<div className="flex justify-center items-center py-2 bg-[#21A5B7]">
{skillScore.calculate[skillScore.selectedModule]?.guidance}
</div>
</div>
</form>
</div>
</div>
</>
);
};