React Form Validation Issue: Form State Not Reflecting Errors Set in Child Component within Parent Component

51 Views Asked by At

setting error at child Component Code Preview I'm currently facing an issue in a React project where I have a form implemented in a parent component and validation errors being handled in a child component. Despite setting errors in the child component, the form state in the parent component does not seem to reflect these errors, resulting in the form not being marked as invalid.

Here's a brief overview of the setup:

The form is implemented in a parent component. Validation errors are being handled and set in a child component. Even though errors are present in the child component, the form state in the parent component does not show it as invalid. I've double-checked the communication between the parent and child components, and it seems like the errors are being correctly passed from the child to the parent. However, the form state doesn't seem to update accordingly.

I would appreciate any insights, suggestions, or examples of how to ensure that the form state in the parent component accurately reflects the validation errors set in the child component.

Thank you in advance for your help!

What I've Tried:

Verified that the errors are correctly set in the child component by console logging them. Confirmed that the error values are being passed from the child to the parent component through props. Checked that the parent component state is being updated with the correct error values. Examined the form validation logic in the parent component to ensure it's correctly utilizing the error values. Expected Outcome: I expected that when errors are present in the child component and passed to the parent component, the form state in the parent component should recognize these errors and mark the form as invalid. However, despite these efforts, the form state remains unaffected.

By providing this additional information, you give those helping you a clearer picture of your troubleshooting process and enable them to offer more targeted assistance.

import React, { useEffect } from 'react'
import { Form, Col } from 'react-bootstrap'
import { FormProvider, Controller, useFormContext } from 'react-hook-form'
import Attachments from '../../../../../components/Attachments'
import { DOE_SOURCE, BCM_REPORT_TYPE, COMMON_ROLES, TASK_NAME } from '../../../../../constants/appConstants'
import { IUiConfigurations } from '../../../../../constants/commonInterface'
import { IBcmKpi, getColorForCompliance } from '../KpiReport.dto'
import { ATTACHMENT_CONFIG } from '../../../../../constants/appAttachments'
import { restrictToTwoDecimals } from '../../../../../helpers/api/helper'
import { get } from 'lodash'
import { useSelector } from 'react-redux'
interface IbcmKpiProps {
    taskName?:string | null,
    bcmKpi: IBcmKpi,
    kpiIndex: number,
    registerLocation: any,
    useFormMethods: any,
    uiConfiguration: IUiConfigurations
    jsonLocation: any,
    calculateKpiResultScoreCompliance: any
    calculateApplicableWeight: any
    disableFields: boolean
    setIsUploadedRequiredAttachments: any,
}
export const Kpi = ({ taskName,bcmKpi, kpiIndex, registerLocation, useFormMethods, uiConfiguration, jsonLocation, calculateKpiResultScoreCompliance, calculateApplicableWeight, disableFields, setIsUploadedRequiredAttachments }: IbcmKpiProps) => {
    const watchNotApplicableValue = useFormMethods.watch(`${registerLocation}.notApplicable`);
    const userInfo: any = useSelector((state: any) => state.userDetails);
    const calculateApplicantScore = (action?: string) => {
        // getting values outside performanceIndicatorValue object to get  
        const modifiedString = registerLocation.replace(/\.performanceIndicatorValue$/, '');
        const deducatableKpiWeight = parseFloat(useFormMethods.getValues(`${modifiedString}.kpiWeight`))
        calculateApplicableWeight(deducatableKpiWeight, action)
    }
    useEffect(() => {
        if (watchNotApplicableValue) {
            useFormMethods.setValue(`${registerLocation}.numerator`, '')
            useFormMethods.setValue(`${registerLocation}.denominator`, '')
            useFormMethods.setValue(`${registerLocation}.compliance`, '')
            useFormMethods.trigger(`${registerLocation}.compliance`)
            calculateApplicantScore('sub')
        }
        else if (watchNotApplicableValue == null) {
            calculateApplicantScore()
        }
        else if (watchNotApplicableValue == false) {
            calculateApplicantScore('add')
        }
    }, [watchNotApplicableValue]);
    return (
        <React.Fragment key={kpiIndex + (bcmKpi.kpiName || "")}>
            {/* Row for BCM KPI */}
            <tr >
                <td colSpan={2} className="text-center align-middle"> {bcmKpi?.kpiId}</td>
                <td className="text-center align-middle"> {bcmKpi?.kpiName}</td>
                <td className="text-center align-middle"> {bcmKpi?.kpiWeight}</td>
                <td className='align-middle text-center'>
                    <Form.Check
                        type="checkbox"
                        id="checkboxId"
                        {...useFormMethods.register(`${registerLocation}.notApplicable`)}
                        disabled={disableFields || (!useFormMethods.getValues(`${registerLocation}.comments`) && taskName !== TASK_NAME.SUBMIT_KPI)}
                    />
                </td>
                <td className='align-middle text-center w-20'>
                    <FormProvider {...useFormMethods}>
                        <Col md={12}>
                            <NumeratorInput disableFields={disableFields || (!useFormMethods.getValues(`${registerLocation}.comments`) && taskName !== TASK_NAME.SUBMIT_KPI)} calculateApplicableWeight={calculateApplicableWeight} calculateKpiResultScoreCompliance={calculateKpiResultScoreCompliance} registerLocation={registerLocation} jsonLocation={jsonLocation} uiConfiguration={uiConfiguration} />
                        </Col>
                        <Col md={12}>
                            <DenominatorInput disableFields={disableFields || (!useFormMethods.getValues(`${registerLocation}.comments`) && taskName !== TASK_NAME.SUBMIT_KPI)} registerLocation={registerLocation} jsonLocation={jsonLocation} uiConfiguration={uiConfiguration} />
                        </Col>
                    </FormProvider>
                </td>
                <td className='align-middle'>
                    <Col md={12}>
                        <p>{bcmKpi?.numeratorDescription}</p>
                    </Col>
                    <Col md={12}>
                        <p>{bcmKpi?.denominatorDescription}</p>
                    </Col>
                </td>
                <td className='align-middle text-center'>
                    {useFormMethods.getValues(`${registerLocation}.kpiResult`)}
                </td>
                <td className='align-middle text-center'>
                    {bcmKpi?.performanceIndicatorValue?.score}
                </td>

                <td className='align-middle text-center' style={{ backgroundColor: getColorForCompliance(bcmKpi?.performanceIndicatorValue?.compliance as string) }}>
                    {bcmKpi?.performanceIndicatorValue?.compliance}
                </td>
                <td className='align-middle text-center max-w-25 min-w-25' >
                    <Attachments
                        setIsUploadedRequiredAttachments={setIsUploadedRequiredAttachments}
                        isFieldAttachments={true}
                        source={DOE_SOURCE}
                        type={BCM_REPORT_TYPE}
                        attachmentID={ATTACHMENT_CONFIG.KPI_REPORT}
                        files={useFormMethods.getValues(`${registerLocation}.attachments`) || []}
                        requestId={useFormMethods.getValues(`${registerLocation}.attachments`)[0]?.requestId}
                        readOnly={disableFields || (!useFormMethods.getValues(`${registerLocation}.comments`) && taskName !== TASK_NAME.SUBMIT_KPI)}
                    />
                    <small>
                        <ErrorMessage
                            className="text-danger error-message"
                            errors={useFormMethods.formState.errors}
                            name={`attachments`}
                            as="p"
                        />
                    </small>
                </td>
                <td className='align-middle text-center w-25'>
                    <Controller
                        name={`${registerLocation}.justification`}
                        control={useFormMethods.control}
                        render={({ field: { onChange, value } }) => (
                            <>

                                <Form.Control id={(`${registerLocation}`).toString()} disabled={disableFields || (!useFormMethods.getValues(`${registerLocation}.comments`) && taskName !== TASK_NAME.SUBMIT_KPI)} value={value || null} type="text" as="textarea" rows={3} className={`form-control .${useFormMethods.getFieldState(`${registerLocation}.justification`).error ? 'is-invalid' : ''}`} placeholder={uiConfiguration?.UI_LABELS?.JUSTIFICATION || 'Justification'} onChange={onChange} />
                                <small>
                                    <ErrorMessage
                                        className="text-danger error-message"
                                        errors={useFormMethods.formState.errors}
                                        name={`${registerLocation}.justification`}
                                        as="p"
                                    />
                                </small>
                            </>
                        )} />
                </td>
               { (userInfo?.role?.split(",")?.includes(COMMON_ROLES.BCM_REVIEWER) || taskName === TASK_NAME.BCM_MORE_INFO_BY_REVIEWER )&& <td className='align-middle text-center w-25'>
                    <Controller
                        name={`${registerLocation}.comments`}
                        control={useFormMethods.control}
                        render={({ field: { onChange, value } }) => (
                            <>

                                <Form.Control id={(`${registerLocation}`).toString()} disabled={!userInfo?.role?.split(",")?.includes(COMMON_ROLES.BCM_REVIEWER)} value={value || null} type="text" as="textarea" rows={3} className={`form-control .${useFormMethods.getFieldState(`${registerLocation}.comments`).error ? 'is-invalid' : ''}`} placeholder={uiConfiguration?.UI_LABELS?.COMMENTS || 'Comments'} onChange={onChange} />
                                <small>
                                    <ErrorMessage
                                        className="text-danger error-message"
                                        errors={useFormMethods.formState.errors}
                                        name={`${registerLocation}.comments`}
                                        as="p"
                                    />
                                </small>
                            </>
                        )} />
                </td>}
                <td colSpan={12} ></td>
            </tr>
        </React.Fragment>
    )
}
// NumeratorAndDenominator Related Code Start
interface INumeratorAndDenominatorInput {
    calculateKpiResultScoreCompliance?: any,
    registerLocation: any,
    jsonLocation: any,
    uiConfiguration: IUiConfigurations,
    calculateApplicableWeight?: any,
    disableFields: boolean
}
const NumeratorInput = ({ calculateKpiResultScoreCompliance, registerLocation, jsonLocation, uiConfiguration, calculateApplicableWeight, disableFields }: INumeratorAndDenominatorInput) => {
    const useFormMethods = useFormContext();
    const watchNumerator = useFormMethods.watch(`${registerLocation}.numerator`);
    const watchDenominator = useFormMethods.watch(`${registerLocation}.denominator`);
    const watchNotApplicableValue = useFormMethods.watch(`${registerLocation}.notApplicable`);
    // Calculating KpiResult Score Compliance and Total
    useEffect(() => {
        checkNumeratorandDenominatorValidation(registerLocation)
        calculateKpiResultScoreCompliance(`${jsonLocation}`);
    }, [watchNumerator, watchDenominator]);
    const checkNumeratorandDenominatorValidation = (registerLocation: any) => {
        const numerator = parseFloat(watchNumerator); // Parse as float if needed
        const denominator = parseFloat(watchDenominator); // Parse as float if needed
        if (denominator == 0) {
            // Set an error message for the Denominator field
            useFormMethods.setError(`${registerLocation}.denominator`, {
                type: 'manual',
                message: 'Denominator cannot be zero',
            });
        } else if (numerator > denominator) {
            // Set an error message for the Numerator field
            useFormMethods.setError(`${registerLocation}.numerator`, {
                type: 'manual',
                message: 'Numerator cannot be greater than Denominator',
            });
        }
        else {
            // Clear errors if conditions are met
            useFormMethods.clearErrors(`${registerLocation}.denominator`);
            useFormMethods.clearErrors(`${registerLocation}.numerator`);
        }
        useFormMethods.trigger(`${registerLocation}.denominator`);
        useFormMethods.trigger(`${registerLocation}.numerator`);
        console.log(useFormMethods.formState.errors, useFormMethods.getFieldState(`${registerLocation}.numerator`).error, useFormMethods.formState.isValid)
    }
    return (
        <>
            <Controller key={`${registerLocation}.numerator`}
                name={`${registerLocation}.numerator`}
                control={useFormMethods.control}
                render={({ field: { onChange, value } }) => (
                    <>

                        <Form.Control id={(`${registerLocation}`).toString()} value={value || ''} disabled={watchNotApplicableValue || disableFields} type="number" className={`form-control .${useFormMethods.getFieldState(`${registerLocation}.numerator`).error ? 'is-invalid' : ''}`} placeholder={uiConfiguration?.UI_LABELS?.NUMERATOR || 'Numerator'} onChange={(e) => { e.target.value = restrictToTwoDecimals(e.target.value); onChange(e.target.value); }} />
                        <small>
                            <ErrorMessage
                                className="text-danger error-message"
                                errors={useFormMethods.formState.errors}
                                name={`${registerLocation}.numerator`}
                                as="p"
                            />
                        </small>
                    </>
                )} />
        </>
    )
}
const DenominatorInput = ({ registerLocation, uiConfiguration, disableFields }: INumeratorAndDenominatorInput) => {
    const useFormMethods = useFormContext();
    const watchNotApplicableValue = useFormMethods.watch(`${registerLocation}.notApplicable`);
    return (
        <>
            <Controller key={`${registerLocation}.denominator`}
                name={`${registerLocation}.denominator`}
                control={useFormMethods.control}
                render={({ field: { onChange, value } }) => (
                    <>
                        <Form.Control id={(`${registerLocation}`).toString()} value={value || ''} disabled={watchNotApplicableValue || disableFields} type="number" className={`form-control .${useFormMethods.getFieldState(`${registerLocation}.denominator`).error ? 'is-invalid' : ''}`} placeholder={uiConfiguration?.UI_LABELS?.DENOMINATOR || 'Denominator'} onChange={(e) => { e.target.value = restrictToTwoDecimals(e.target.value); onChange(e.target.value); }} />
                        <small>
                            <ErrorMessage
                                className="text-danger error-message"
                                errors={useFormMethods.formState.errors}
                                name={`${registerLocation}.denominator`}
                                as="p"
                            />
                        </small>
                    </>
                )} />
        </>
    )
}
// NumeratorAndDenominator Related Code End
0

There are 0 best solutions below