React - Complex state Management

React - Complex state Management



Article content


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:

  • React 18+
  • Tailwind 3+


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:

  1. The current state, which initially matches the initialState you provided.
  2. The set function, which allows you to update the state to a different value and trigger a re-render.

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

  • updateAverageAllSkills() when skill object in state is updated
  • updateGuidance() when calculate object in state is update

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

  • currently one module is implemented, designed code in such a way that it should work for all modules
  • no numeric validation for text box is added
  • can generalize the state update object which is called in all methods


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>
    </>

  );
};
        

To view or add a comment, sign in

More articles by Dilipkumar Somasundaram

  • Essential Data Science Metrics

    Z-score: Definition: The Z-score, or standard score, measures how many standard deviations an element is from the mean…

  • Unlocking Machine Learning: A Guide to Supervised and Unsupervised Learning

    Supervised Learning Definition: Supervised learning is a type of machine learning where the model is trained on a…

    2 Comments
  • Modernizing Student Assessment: The Dynamic Report Card Approach using ReactJS

    Objective: To develop a standardized mark card system that addresses the inconsistencies in grading scales across…

    2 Comments
  • Eigen value and Eigen vector

    What Are Eigenvalues and Eigenvectors? How can it be explained to 10 year old student. Imagine you have a magical…

    1 Comment
  • How I prepared for DP 100?

    Prerequisite Understanding of Machine Learning, Artificial Intelligence, and Data Science: Aspiring data scientists…

    1 Comment
  • Matrix Decomposition

    Matrix decomposition In the realm of linear algebra, a matrix decomposition (also known as matrix factorization) is the…

Explore content categories