I crafted a CV builder App with React.js

I crafted a CV builder App with React.js

Embark on a thrilling journey with me as we delve into the development of a resume builder. I use React's dynamic components to build this app. Uncovering the secrets of efficient state management, component hierarchy, and interactivity. Join me in unlocking the potential of structuredClone for immutability, harnessing custom data attributes, useRef and hook. Get ready for an exciting and insightful read that will elevate your skills and may even inspire your next coding project!

alt text

alt text

Find the deployed app here

The source code

Find my X

The component hierarchy

The very first thing that comes to mind when you are making a react application is COMPONENTS, and how you structure them. What's the hierarchy of parent and child components? While pondering on these I stumbled on this piece of gold:

Key Takeaways:

"Build user interfaces by breaking them down into reusable components. Start with a mock-up and identify the components hierarchy, define the application's state, and pass data through components using props for interactivity."

So to conceptualize my application as a hierarchy of reusable components, I mirrored real UI elements.

I used the single responsibility model that means, a component should ideally be supposed to do one thing only.


So how does one say that a component is a parent to another child component?

  • A parent component renders one or more other components within its code. These rendered components become its child components.

  • The parent component controls the existence and props passed down to its children.

  • Data Flow: Data typically flows downward in the component hierarchy. Parent components pass data (through props) to their child components.

    Child Component Inheritance:

    In React, components don't inherit properties or states from their parents in the traditional sense like in object-oriented programming languages or the Prototypal Inheritance in JavaScript.

    Child components can access and utilize props that are explicitly passed down to them from their parent. These are a way for a child component to receive data and functionality from their parent components and customize their behaviour accordingly.

Hierarchy of the CV components


Following are the images where I have coloured boxes to represent the components.

  1. As visible in this picture, I have on the right side the Resume component, outlined in red which is responsible for displaying the data that the user has supplied.
// resume.jsx
const Resume = React.forwardRef(({ personalInfo, sections }, ref) => {
  return (
    <div className="resume-container" ref={ref}>
        <div id="resume">

          <PersonalInfoSection
              fullName={personalInfo.fullName}
              email={personalInfo.email}
              phoneNumber={personalInfo.phoneNumber}
          />
          <div>
            <EducationInfoSection
                educations={sections.educations}
            />
            <ExperienceInfoSection
                experiences={sections.experiences}
            />
          </div>
        </div>
    </div>
  )
});

The component receives the personalInfo and sectionsstate objects from the parent App.jsx

// app.jsx
const [personalInfo, setPersonalInfo] = useState({
    fullName: "",
    email: "",
    phoneNumber: "",
  });
  const [sections, setSections] = useState({
    educations: [
      // array of education form objects
      {
        degree: "",
        schoolName: "",
        startDate: "",
        endDate: "",
        id: uniqid(),
      },
    ],

    experiences: [
      // array of experience form objects
      {
        companyName: "",
        positionTitle: "",
        description: "",
        startDate: "",
        endDate: "",
        id: uniqid(),
      },
    ],
  });

Why ref is used

const Resume = React.forwardRef: This line defines a React component using the forwardRef higher-order component. forwardRef allows parent components to move down (or “forward”) refs to their children. Essentially I attached a ref to the entire resume container which makes it available for usage by the parent component (PdfSave). Refs are commonly used to access child components and perform other direct DOM manipulations that are outside the scope of React's declarative approach (without using state). It gives the resume component a reference to the DOM entity created by PdfSave.

Accessing DOM Elements: ref is used to get a reference to a DOM element or a class component instance. In the context of ReactToPrint (refer to PdfSave code in children), the ref is necessary to access the content of the Resume component for printing.

  • ({ personalInfo, sections }, ref): The component accepts two props:

    • personalInfo: An object containing the user's personal information (full name, email, phone number).

    • sections: An object containing the resume sections data properties educations and experiences (arrays of objects representing education and experience entries).

    • ref: This prop allows attaching a ref to the entire resume container.

Children of Resume, are:

// app.jsx
        <Resume personalInfo={personalInfo} sections={sections} />
  • PersonalInfoSection: To display the user's name as an <h1> and other contact details that the user enters in the Personal details form on the left half. So in the case where data to be entered might be optional for example the phone number, I have used conditional rendering to prevent the rendering of empty values and unnecessarily populating the DOM.

      const PersonalInfoSection = ({ email, phoneNumber, fullName }) => {
        return (
          <div className="personal-info">
            <h1 className="resume-name">{fullName}</h1>
            <div className="contact-info">
              {email && (  // conditional rendering
                <div>
                  <FaEnvelope />
                  <span>{email}</span>
                </div>
              )}
              {phoneNumber && (
                <div>
                  <FaPhone />
                  <span>{phoneNumber}</span>
                </div>
              )}
            </div>
          </div>
        );
      };
      export default PersonalInfoSection;
    
  • EducationInfoSection: To render the education section of the resume. If the user enters multiple educational institutions, it must have an array to store them in an object array, so the EducationInfoSection receives the educations array by the grandparent App.jsx and then it passes this prop to the DisplaySection child component. Also, it imports EducationInfo as it is expected by the DisplaySection as the InfoComponent prop. So in this sense the EducationInfo becomes a child of DisplaySection

    • It has a reusable sub-component DisplaySection responsible for rendering multiple detail-sets mapping the educations array that the user enters through the Education form on the left half. It leverages the EducationInfo component which is in turn responsible for a single entry of education data.

        const EducationInfoSection = ({ educations}) => {
          return (
            <div className="education-info-section resume-section">
                <DisplaySection
                    array={educations}
                    InfoComponent={EducationInfo}
                    title='Education'
                />
            </div>
          )
        }
      
        const DisplaySection = ({ array, InfoComponent, title }) => {
          const hasNonEmptyValues = (obj) => {
            // do any of the objects in the array contain non-empty values for their properties
            return Object.entries(obj).some(([key, value]) => key!=='id' && value !== '');
          }
      
          const hasNonEmptyItems = array.some(obj => hasNonEmptyValues(obj));
      
          return (
            <>
              {array && array.length > 0 && hasNonEmptyItems &&(
                <>
                  <h3 className='header-text'>{title}</h3>
                  {array.map(
                      (info) =>
                          <InfoComponent
                              info={info}
                              key={info.id}
                          />
                  )}
                </>
              )}
            </>
          )
        }
      
        export default DisplaySection;
      

If we think of all the information we will have for an education entry; degree, dates, institution name. We can store this as an object called 'info', from which all these can be destructured. Each of these objects gets added to the educations array and the DisplaySection can iterate over them rendering one by one (only if they have non-empty values).

const EducationInfo = ({info}) => {
    var {schoolName, degree, startDate, endDate} = info;
    const today = new Date();

    // parse the date string into a Date object
    const parseDate = (dateString) => {
        if(!dateString) return null;
        const [year, month, day] = dateString.split('-');
        return new Date(year, month-1, day);
    }

    const parsedEndDate = parseDate(endDate);

    // if the end date is in the future, set the end date to today
    if(parsedEndDate && parsedEndDate > today) {
        endDate = 'Present';
    }

  return (
    <div className="education-info">
        <div className="education-info-group">
            <p className="dates">
                {startDate}
                {startDate && endDate && ' to '}
                {endDate}

            </p>
        </div>
        <div className="education-info-group">
            <p className="education-info-schoolName">{schoolName}</p>
            <p className="education-info-degree">{degree}</p>
        </div>
    </div>
  )
}

export default EducationInfo
  • ExperienceInfoSection: To render the experience section of the resume.

    • Similar to EducationInfoSection, it uses DisplaySection component to render multiple detail-sets mapping the experiences array that the user enters through the Experience form on the left half. It leverages the ExperienceInfo Component which in turn is responsible for a single entry of experience data.

    • The info object in experiences becomes (startDate, endDate, companyName, positionTitle, description)

  1. In the left half, you can see 2 reusable rows representing the collapsed rows of the education entry sets, belonging to CollapsedForm component, I will 'expand' on this component in the upcoming part.

  2. On the upper left, we have the PdfSave component displaying the download button. What does it do? It converts the resume into a PDF and downloads the resume once clicked. I have used ref to enable the ReactToPrint library component to reference the Resume component so it can generate print content from it.

import Resume from './Resume.jsx';
import ReactToPrint from "react-to-print";


const PdfSave = ( {personalInfo, sections} ) => {

  const resumeRef = useRef(); // Creating the ref

  return (
    <div className="cv-download">
      <ReactToPrint
        trigger={() => (
          <button className="download-button">
            <div className="inner">
              <p>Download CV</p>
              <FaDownload />
            </div>
          </button>
        )}
        content={() => resumeRef.current} 
          // Pass the Resume component reference to ReactToPrint
      />
      {/* Hide the Resume component from view, 
       but render it for printing */}
      <div style={{ display: 'none' }}>
        <Resume ref={resumeRef} personalInfo={personalInfo} 
            sections={sections}/>
      </div>
    </div>
  );
};
  • useRef is a React hook that creates a ref object. Here I use it for resumeRef.

  • resumeRef will be used to refer to the Resume component instance.

  • The content prop of ReactToPrint expects a function that returns the DOM node or component instance to print.

  • resumeRef.current is the current value of the resumeRef which will be set to the Resume component instance.

    • The .current property is directly mutable, meaning I can read or write to resumeRef without triggering a re-render of the component.

    • When we attach a ref to a DOM element or a component, React automatically updates the .current property of the ref object to point to the corresponding DOM node or component instance. Unlike state, which triggers a re-render when it changes, modifying the .current property of a ref does not cause a re-render.

    • refs are used for scenarios where we need direct access to a DOM element or an instance of a component for purposes such as managing focus, text selection, or integrating with third-party libraries.

  • The Resume component is then rendered with the ref attribute set to resumeRef.

    this associates the resumeRef with the Resume component instance, allowing ReactToPrint to access it.

FeatureRefsState
Triggers Re-rendersNoYes
MutabilityMutableImmutable
UsageUsed for direct DOM manipulations and persisting values across renders.Used for managing dynamic data that affects the component's rendering.
WhyMutable because the .current property can be directly modified without re-creating the ref object or triggering re-renders.Immutable because state updates create a new state object, ensuring changes are detected and a re-render is triggered to update the UI.

Let's look at the forms now.

The whole thing is under the "grandparent" component, the App.jsx

Now moving on to the left half, the edit-side, we have:

  1. PersonalDetails

    • It fulfils the purpose of allowing a user to input their name and other contact details.

    • In this case, PersonalDetails renders multiple instances of InputField component within its form element.

    • The PersonalDetails component receives props (fullName, email, phoneNumber, and onChange) from its parent component App.js. So it passes these props indirectly down to each InputField component.

    • The InputField component is reused for the contact and name details.

const InputField = ({ id, value, labelText, placeholder, type, onChange, "data-key": dataKey, }) => {
  return (
    <div className="input-field">
      <label htmlFor={id}>
        <span className="label-text">{labelText}</span>
      </label>

      <input
        type={type}
        id={id}
        placeholder={placeholder}
        value={value}
        onChange={onChange}
        data-key={dataKey} // pass a data-key prop which is then set as a data-key attribute on the input element. 
                     // This custom data attribute is used to store the key that identifies which part of the state the input field corresponds to.
      />
    </div>
  );
};

Custom data-key attribute is here for one purpose, and accompanying other. In simple words, every <input> in PersonalDetails will receive the same onChange event handler function, and on that input change, the function should update only the state of the field property that changes, rather than updating the whole state object, that's where the data-key attribute comes into play. It serves the purpose to only update the key that changes instead of updating the state of the whole state object, i.e. only that state property that should be updated, in this case, either the fullName, the phoneNumber or the email.

import InputField from "../InputField";

const PersonalDetails = ({ fullName, email, phoneNumber, onChange }) => {
  return (
    <>
      <form className="personal-details">
        <h2>Personal Details</h2>
        <InputField
          type="text"
          id="full-name"
          labelText="Full Name"
          placeholder="Enter your full name"
          value={fullName} // whatever the user enters
          onChange={onChange} //  defined in app.js
          data-key="fullName" // set the data-key prop to the key that should be updated in the state when the input field's value changes.
        />


        <InputField
          type="email"
          id="email"
          labelText="Email"
          placeholder="Enter your email address"
          value={email} //whatever the user enters
          onChange={onChange} //  defined in app.js
          data-key="email"
        />

        <InputField
          type="tel"
          id="phone-number"
          labelText="Phone Number"
          placeholder="Enter phone number"
          value={phoneNumber} //whatever the user enters
          onChange={onChange} // defined in app.js
          data-key="phoneNumber"
        />
      </form>
    </>
  );
};

export default PersonalDetails;
// app.jsx 
 const [personalInfo, setPersonalInfo] = useState({
    fullName: "",
    email: "",
    phoneNumber: "",
  });

function handlePersonalInfoChange(e) {
    const { key } = e.target.dataset; // extract the data-key property 
// from the dataset object of the target element 
// (the input field being edited (e.g., "fullName", "email", etc.).
    setPersonalInfo({ ...personalInfo, [key]: e.target.value });
  }

return (
    <>
        ...
        <PersonalDetails
              onChange={handlePersonalInfoChange}
              fullName={personalInfo.fullName}
              email={personalInfo.email}
              phoneNumber={personalInfo.phoneNumber}
            />
        ...
    </>
)
  1. AddEdcuationSection: This component is responsible for allowing the user to add in their schooling-related information, it has more UI features like collapsing or displaying the whole fields of the form, and a button to add a new entry to the educational information.

    • ExpandSection: This is a button that enables the user to open or close the section-content div, essentially the content that DisplayForms and the CreateForm render. The hiding and displaying along with transitions are taken care of in the CSS.

    • It would use a boolean to track if the section is open or closed and a helper function to toggle the open/close status. The onClick event handler is logically checking if isOpen is true for the section and if it is it replaces the sectionName with an empty string else if it is false the sectionName gets added ('Education' or 'Experience').

        // app.jsx
        const [sectionOpen, setSectionOpen] = useState(null);
        const setOpen = (sectionName) => setSectionOpen(sectionName);
      
        <AddEducationSection
                      educations={sections.educations}
                      isOpen={sectionOpen === "Education"}
                      setOpen={setOpen}
            ...
                 />
      
    • sectionName prop for keeping track of which section is open (educations or experiences). In this scenario since we are looking at AddEducationSection , we can safely give this component the prop value of 'Education'

    • In the App.jsx I have a state function setSectionOpen for changing the state of sectionOpen state variable.


    const ExpandSection = ({ isOpen, setOpen, sectionName, IconName}) => {
      return (
        <button className="expand-section" onClick={() => setOpen(isOpen ? "" : sectionName)}>
          <h2 className="expand-section-header">
            <span style={{fontSize: "1.5em"}}>
              <IconName className={IconName} />
            </span>
            {sectionName}
          </h2>

          <FaChevronUp className={`chevron ${isOpen ? "open" : ""}`}/>
        </button>
      );
    };

    export default ExpandSection;
  1. DisplayForms: This is the component responsible for displaying a saved entry as either a single row in the AddEducationSection (not the whole form) or as the whole form to make edits to pre-filled data, based on theisCollapsedproperty of the form object. It has two child components which get toggled into view (if isCollapsed is true for a form object in the array storing several forms then CollapsedForm renders else the EducationForm renders, not both at the same time)
  • forms prop: Array of experience or education objects.

  • onChange prop: handle the changes to form objects to render the change in real-time

      function handleSectionChange(e) {
          const { key } = e.target.dataset; 
          const inputValue = e.target.value; 
          const form = e.target.closest(".section-form"); 
          const { id } = form; // extracts the id property
          const { arrayName } = form.dataset; // extract the data-array-name property from the dataset object of the form element, holds the name of array being updated (educations or experiences)
    
          const section = sections[arrayName]; // which section is being edited, extracts the specific section array educations or experiences from sections state object
          setSections({  
            ...sections, // shallow copy the sections state object using spread operator To ensure immutability 
            [arrayName]: section.map((obj) => {
              // Update the specific array (educations or experiences)
              if (obj.id === id) obj[key] = inputValue; // check if the id of the object matches the id of the form being edited.
              return obj; // Return the updated object to include that modified object in the new state array
            }),
          });
        }
    
  • toggleCollapsed: a prop that the DisplayForms component receives. It is used by the EducationForm component to toggle the 'isCollapsed' property when user hits 'save' button. It is also used when as an 'onClick' I hit the 'collapsedForm' button.

      // app.jsx
      // Store prevState to revert changes when user clicks "cancel"
        const [prevState, setPrevState] = useState(null);
        function toggleValue(e, key) { // on event of change, key would be isCollapsed property
          const sectionForm = e.target.closest(".section-form"); // find the form object being interacted with
          const { id } = sectionForm; // destructure the id property from the form object
          const { arrayName } = sectionForm.dataset;
          const section = sections[arrayName];
    
          setSections({
            ...sections, // spread operator to make a copy of sections state object
            [arrayName]: section.map((form) => {
              if (form.id === id) {
                // copy the values of all enumerable properties from one or more source objects to a target object
                // create a shallow copy of current original form object and set it as prevstate
                setPrevState(Object.assign({}, form)); // set the current state as previous state in case the form gets cancelled
                form[key] = !form[key]; // toggling the isCollapsed
              }
              return form;
            }),
          });
        }
    
        const toggleCollapsed = (e) => toggleValue(e, "isCollapsed");
    
  • onRemove: to delete a form entry from the education or experience section.

      const removeForm = (e) => {
        const form = e.target.closest(".section-form"); // find out the form which had its delete button clicked
        const { id } = form; // get its id
        const { arrayName } = form.dataset;

        const section = sections[arrayName];

        setSections({
          ...sections,
          [arrayName]: section.filter((item) => item.id !== id),
        });  //  creates a new array without the unwanted form
      };
  • onCancel: if the user clicks on cancel while editing a form, remove it from DOM or revert to the previous state for that form object by matching id.

    
      const cancelForm = (e) => {
          // dont update the form state
          // and if the form has no state already remove it from DOM
          if (prevState == null) {
            removeForm(e);
            return;
          }
    
          const sectionForm = e.target.closest(".section-form");
          const { id } = sectionForm;
          const { arrayName } = sectionForm.dataset;
          const section = sections[arrayName];
    
          setSections({
            ...sections,
            [arrayName]: section.map((form) => {
              if (form.id === id) {
                form = prevState;
              }
              return form; // ensure the modified object with reverted state is included in the new state array.
            }),
          });
        };
    
  • titleKey: for CollapsedForm to identify the title to display.

  • arrayName: to identify which state object to update (educations or experiences). The arrayName data attribute is used to dynamically identify which array within the sections state object is being interacted with. Since I have multiple sections (educations, experiences) that follow the same structure and logic but need to be handled separately. This attribute helps in identifying which section of the state (e.g., educations, experiences) the current form belongs to.

      import CollapsedForm from "./CollapsedForm"
    
      const DisplayForms = ({ forms, onChange, onCancel, onRemove, FormComponent, arrayName, titleKey, toggleCollapsed }) => {
        return (
          <div className="forms-container">
              {forms.map((form) =>
    
                form.isCollapsed ? (
                  <CollapsedForm
                      form={form}
                      key={form.id}
                      onClick={toggleCollapsed}
                      arrayName={arrayName}
                      title={form[titleKey]}
                  />
                ) : (
                  <FormComponent 
                      form={form} // form object example, EducationForm
                      key={form.id} 
                      onChange={onChange}
                      cancel={onCancel}
                      remove={onRemove}
                      arrayName={arrayName}
                      save={toggleCollapsed}
                      title={form[titleKey]} // iterates over the
                      // forms array, value of the title prop is set to form[titleKey], title will be dynamically retrieved from the form object based on the property specified by the titleKey prop
                  />
                )
              )}
          </div>
        )
      }
    
      export default DisplayForms
    
    • CollapsedForm: Is the component that renders a buttoned row which displays only the title of the education entry. If clicked, the EducationForm takes space all this is accompanied by toggling the isCollapsed property of the form.

      • The CollapsedForm needs the title only to be displayed when the pre-filled form is collapsed. For that, the AddEducationSection component passes the titleKey="schoolName" as a prop to the DisplayForms component which in turn passes that down to the CollapsedForm component.
            const CollapsedForm = (props) => {
                const {title, arrayName, onClick} = props;
                const {id} = props.form;
              return (
                <button
                    className='collapsed-form section-form'
                    id={id}
                    onClick={onClick}
                    data-array-name={arrayName}
                >
                    <p className='collapsed-form-title'>{title}</p>
                </button>
              )
            }
  • EducationForm: Gets passed as a prop to the DisplayForms' FormComponent prop to render either the experienceForm or the EducationForm based on the prop value (here, EducationForm)

      import InputField from '../InputField';
      import Buttons from '../Buttons';
    
      const EducationForm = (props) => { // props object
          const {degree='', schoolName='', startDate='', endDate='', id=''} = props.form;
          const {onChange, cancel, save, remove} = props; // destructuring top level props from props object for buttons
          // onChange prop is used as the event handler for the input fields, which will be the handleSectionChange function
        return (
          <form className="education-form section-form"
              id={id}
              data-array-name="educations"
            onSubmit={(e) => e.preventDefault()}
          >
            <InputField
              type="text"
              id="school-name" 
              labelText="School"
              placeholder="Enter your school / university"
              onChange={onChange}
              value={schoolName} 
              data-key="schoolName" 
              required
            />
    
            <InputField
              type="text"
              id="degree"
              labelText="Degree"
              placeholder="Enter your study title"
              value={degree}
              onChange={onChange}
              data-key="degree"
              required
            />
    
            <div className="dates-group">
              <InputField
                type="date"
                id="date"
                labelText="Start Date"
                placeholder="Enter start date"
                value={startDate}
                onChange={onChange}
                data-key="startDate"
                required
              />
              <InputField
                type="date"
                id="date"
                labelText="End Date"
                placeholder="Enter end date"
                value={endDate}
                onChange={onChange}
                data-key="endDate"
                required
              />
            </div>
    
          <Buttons save={save} remove={remove} cancel={cancel}></Buttons>
          </form>
        );
      };
      export default EducationForm;
    
    • CreateForm: The component showcases a button that will create a new form object and append it to the relevant object array (either education or experiences)
    // app.jsx
    // add a new form object to appropriate section
    function createForm(arrayName, object) {
      const section = structuredClone(sections[arrayName]);
      setPrevState(null); // start as new form with no previous state
    
      section.push(object);
      setSections({ ...sections, [arrayName]: section });
    }
    
    const createEducationForm = () => {
      createForm("educations", {
        degree: "",
        schoolName: "",
        startDate: "",
        endDate: "",
        id: uniqid(),
        isCollapsed: false,
      });
    };
    
    const createExperienceForm = () => {
      createForm("experiences", {
        companyName: "",
        positionTitle: "",
        startDate: "",
        endDate: "",
        description: "",
        id: uniqid(),
        isCollapsed: false,
      });
    };
    
const CreateForm = ({ onClick, buttonText }) => {
  return (
    <button className="create-form" onClick={onClick}>
      <h4 className="button-content">
        <FaPlus className="fa-plus"></FaPlus>
        {buttonText}
        </h4>
    </button>
  );
};

export default CreateForm;
// This component manages the list of education forms
import CreateForm from "../CreateForm"
import DisplayForms from "../DisplayForms"
import EducationForm from "./EducationForm"
import ExpandSection from "../ExpandSection"
import { FaGraduationCap } from "react-icons/fa"; // Ensure correct import

const AddEducationSection = ({
    educations,
    isOpen,
    onChange,
    createForm,
    setOpen,
    onCancel,
    onRemove,
    toggleCollapsed
}) => {
  return (
    <div className="add-education-section section">
        <ExpandSection 
            isOpen={isOpen}
            setOpen={setOpen}
            sectionName='Education'
            IconName={FaGraduationCap}
        />

        <div className={`section-content ${isOpen ? "open" : ""}`}>
            <DisplayForms    //to render a list of EducationForm components. ???
                forms={educations}
                onChange={onChange}
                // onChange prop is passed down to each EducationForm, which is the handleSectionChange function in App.jsx
                onCancel={onCancel}
                onRemove={onRemove}
                FormComponent={EducationForm} // this will provide the input fields for the form
                arrayName="educations"
                titleKey="schoolName"
                toggleCollapsed={toggleCollapsed}
            />


        <CreateForm 
            onClick={createForm}
            buttonText='Education'
        />
        </div>
    </div>
  )
}

export default AddEducationSection
// app.jsx
<AddEducationSection
              educations={sections.educations}
              isOpen={sectionOpen === "Education"}
              onChange={handleSectionChange}
              createForm={createEducationForm}
              setOpen={setOpen}
              onCancel={cancelForm}
              onRemove={removeForm}
              toggleCollapsed={toggleCollapsed}
            />
  1. AddExperienceSection: Similar to AddEducationSection, this component is responsible for allowing the user to add in the work experience information, and the same collapsing or displaying the whole form, and a new entry button.

    • ExpandSection

    • DisplayForms

    • CreateForm

structuredClone is used to create a deep copy of an object or array. In the context of the createForm function, it ensures that the array being modified is a completely new instance, not just a reference to the original array. This is important for maintaining immutability and preventing unintended side effects in state management.

Here's why structuredClone is used in createForm:

Immutability

React's state management relies on immutability. When we update the state, we should create a new version of the state rather than modifying the existing one directly. This allows React to correctly detect changes and trigger re-renders.

Shallow Copy vs. Deep Copy

  • Shallow Copy: A shallow copy creates a new object, but it copies the references to the nested objects. If the nested objects are modified, those changes will reflect in both the original and the copied objects.

React uses a concept called reconciliation to update the UI. When a component's state changes, React compares the new state with the previous state to determine what parts of the DOM need to be updated. This comparison is more efficient when the data is immutable because React can simply check if the reference to the state object has changed. This comparison is more efficient because checking if a reference to an object has changed is a constant-time operation (O(1)), meaning it takes the same amount of time regardless of the size of the object. In contrast, comparing every property within an object (deep comparison) to see if any values have changed would take linear time (O(n)), where n is the number of properties in the object, making it slower and less efficient as the object grows larger.


When is state not required?

Use State When:

  • Data Changes Over Time: If the data displayed or manipulated by a component needs to update dynamically based on user interactions, events, or external data fetching, use state e.g., toggle visibility of a modal, editing a form field, displaying real-time data

  • Data Drives UI Changes: If the value of the data directly affects how the component is rendered visually or functionally, state management is essential. React will re-render the component whenever the state changes, ensuring the UI reflects the updated data. (e.g., displaying a loading indicator while fetching data, conditionally rendering elements based on a state value).

Alternatives to State:

  • Props: If the data is passed down from a parent component and doesn't need to be modified within the child component, use props. Props provide a one-way data flow from parent to child and are suitable for static or controlled data. (e.g., displaying user information from a parent component)

  • Refs: Refs allow enable direct access to DOM elements or store mutable values without triggering re-renders. Use refs for situations where one needs to interact with the DOM directly (e.g., focusing on an input field, or printing a resume that is generated in this project).

Tools Used:

  • React-sight (browser extension)

  • Codeium (vs code)

  • texttografo (web app)

  • sapling (vscode extension)

  • gifgit (webapp)