отрисовка компонента react после изменения состояния, работающего с задержкой

#reactjs

#reactjs

Вопрос:

У меня есть эти 2 компонента

FormComponent

 const FormComponent = (props) => {
//keep the current step in the component state
const [currentStep, setCurrentStep] = useState(props.step);
const [componentData, setComponentData] = useState(null);
const [componentQuestions, setQuestions] = useState(null);
const [errors, setErrors] = useState();

const updateUserResponse = props.updateUserResponse;
const renderStepsTracker = props.renderStepsTracker;

/**
 * Get the component content after mount
 */
useEffect(() => {
    if (componentData) {
        setQustionsData(componentData);
    }
    //fire if componentData has changed or error object is changed
}, [errors,componentData]);

/**
 * Get the component content after mount
 */
useEffect(() => {
    if (currentStep amp;amp; !componentData) {
        let stepData = currentStep.stepdata;

        stepData.update = getUpdateTime();

        setComponentData(stepData);
    }
});

/**
 * Get the current time in milliseconds
 */
const getUpdateTime = () => {
    var d = new Date();
    var n = d.getTime();

    return n
}

/**
 * Extract the questions from the step object and 
 * set the state accordingly
 */
const setQustionsData = (stepData) => {
    let questions = stepData.questions;

    //assign a react component to each question
    questions.map((question, key) => {
        const component = getComponent(question);
        const question_id = question.question_id;

        questions[key].component = component;
        questions[key].error = (null != errors amp;amp; 'undefined' !== typeof errors[question_id]) ? errors[question_id] : '';

    })

    setQuestions(questions);
}

/**
 * a callback function to validate the current form
 * @param {object} step 
 */
const validateForm = (step) => {
    const formErrors = props.validateForm(step);

    setErrors(formErrors)
}

/**
 * Render the form
 */
const renderForm = () => {
    return null !== componentQuestions ? (
        <>
            <div className="row full-height">
                <div className="column">
                    <div className="form-wrapper">
                        <div className="questions">
                            {componentQuestions.map(question => {
                                return (
                                    <>
                                        {question.component}
                                    </>
                                )
                            })}
                        </div>
                        <NextStepButton
                            step={currentStep}
                            nextStepCallback={props.jumpToStep}
                            validateForm={validateForm}
                        />
                    </div>
                </div>
            </div>
        </>
    ) : ''
}

/**
 * Render the image in case it exists
 */
const renderImage = () => {
    return (
        <div className="full-height">
            <img className="full-height-image" src={componentData.image} alt="" />
        </div>
    )
}

/**
 * Get the question component
 */
const getComponent = (question) => {
    switch (question.question_type) {
        case "text":
            return (
                <div className="field-wrap">
                    <label>
                        <span className="question-label">{question.question_label}</span>
                        <span className="question-title">{question.question_title}</span>

                        <input type="text"
                            name={question.question_id}
                            onChange={updateUserResponse}
                            required={question.required}
                        />
                        {question.error ?
                            <span className={"error-message "   question.error.reason}>{question.error_message}</span>
                            : ''}

                    </label>
                </div>
            )
    }
}

return (
    componentData ?
        <>
            <div className="row full-height expanded">
                {componentData.image ?
                    <div className="column full-height col-collapse image-wrap">
                        {renderImage()}
                    </div>
                    : ''}
                <div className="column full-height questions-container">
                    {renderStepsTracker()}
                    {renderForm()}
                </div>
            </div>
        </>
        : ''
)} enter code hereexport default FormComponent
  

**

  • NextStepButton

**

     const NextStepButton = (props) => {

    const step = props.step
    const validateForm = props.validateForm

    return props.step.nextButtonText ? (
        <span className="next-step-button" onClick={() => { 
            validateForm( step )
         }}>
            {step.nextButtonText}
        </span>
    ) : ''
}

export default NextStepButton
  

Когда пользователь нажимает на следующий шаг, форма проверяется и возвращает массив ошибок,
Массив ошибок устанавливается в состояние компонента, а затем должен отображать ошибку

По какой-то причине ошибка появляется только после того, как пользователь нажимает 3 раза на кнопку следующего шага

Почему это так?

РЕДАКТИРОВАТЬ это основной компонент, который содержит глобальную проверку формы

 const MainContainer = () => {

    //set state variables
    const [nextButtonText, setNextButtonText] = useState()
    const [previousButtonText, setPreviousButtonText] = useState()
    const [backButtonCls, setBackButtonCls] = useState()
    const [currentStep, setCurrentStep] = useState()
    const [stepsData, setStepsData] = useState()
    const [currentStepNum, setCurrentStepNum] = useState(null)
    const [classNames, setclassNames] = useState(null)
    const [userData, setUserData] = useState({})

    //set refrence for callbacks that use this component state
    const ref = useRef({
        stepsData: stepsData,
        currentStep: currentStep
    })

    /**
     * On Initial state
     */
    useEffect(() => {
        if (!stepsData) {
            getStepsData().then((response) => {
                initProcess(response)
            })
        }
    })

    /**
     * Init the process after getting data from the server
     */
    const initProcess = (response) => {

        const steps = setStepsComponents(response.data.steps)
        const initialStep = steps[0];
        ref.current.stepsData = steps

        setStepsData(steps)
        setStep(initialStep, 1)
    }

    /**
     * Form submission callback
     * Loop over step questions and check for the appropriate userdata
     */
    const validateForm = (submittedStep) => {
        let errors = {}; 
        if ('null' !== typeof submittedStep.stepdata.questions) {
            const questions = submittedStep.stepdata.questions

            questions.map((question, key) => {
                const question_name = question.question_id

                //if there is an error add it to the questions collection
                if (question.required amp;amp; !userData[question_name]) {
                    errors[question.question_id] = {
                        reason : 'required'
                    }
                }
            })
        }

        if (errors) {
            return errors
        }

        return true;
    }

    /**
     * Capture user settings on change
     */
    const updateUserResponse = (event) => {
        userData[event.target.name] = event.target.value;

        setUserData(userData)
    }

    /**
     * Set the appropriate step component
     * @param {OBJECT} steps 
     */
    const setStepsComponents = (steps) => {
        steps.map((step, key) => {
            switch (steps[key]['component']) {
                case "onboarding":
                    steps[key]['component'] = <Onboarding step={step} setUserData />
                    break;
                case "questions":
                    steps[key]['component'] = <FormComponent
                        step={step}
                        renderStepsTracker={renderStepsTracker}
                        updateUserResponse={updateUserResponse}
                        validateForm={validateForm}
                    />
                    break;
            }
        })

        return steps
    }

    /**
     * Callback function that creates the steps tracker on each component
     */
    const renderStepsTracker = () => {

        return ref.current.stepsData ? (
            <>
                <div className="stepsTracker">
                    <div className="row">
                        <div className="column">
                            <div className="stepsTrackerWrap">
                                {ref.current.stepsData.map((step, key) => {
                                    const activeClass = ref.current.currentStep === key ? "active" : ""
                                    return key > 0 ? (
                                        <>
                                            <div className={"stepTracker "   activeClass}>
                                                <div className="stepTrackerImage">
                                                    <img src={step.icon} alt="" />
                                                </div>
                                                <label>{step.stepdata.title}</label>
                                            </div>
                                            <div className="stepSpacer"></div>
                                        </>
                                    ) : ''
                                })}
                            </div>
                        </div>
                    </div>
                </div>
            </>
        ) : ''
    }
    /**
     * Render the steps component if data is available
     */
    const renderSteps = () => {
        return stepsData ? (
            <StepZilla
                steps={stepsData}
                onStepChange={onstepChange}
                nextButtonText={nextButtonText}
                showSteps={false}
                backButtonText={previousButtonText}
                backButtonCls={backButtonCls}
            />
        ) : ''
    }

    /**
     * Set the data for the current step
     * @param {object} step 
     */
    const setStep = (step, stepNum) => {
        setNextButtonText(step.showNextButton ? step.nextButtonText : true)
        setPreviousButtonText(step.previousButtonText)
        setBackButtonCls(step.backButtonCls)
        setCurrentStep(step)

        setCurrentStepNum(stepNum)

        ref.current.currentStep = stepNum

        //add bottom padding in case the next button appears
        if (step.showNextButton) {
            setclassNames('padding')
        } else {
            setclassNames(' ')
        }
    }

    /**
     * Perform actions when step is changed
     * @param {step} step 
     */
    const onstepChange = (stepNum) => {
        if ('undefined' === typeof stepNum) {
            stepNum = '0';
        }

        setCurrentStepNum(stepNum)

        const nextStep = stepsData[stepNum];
        setStep(nextStep, stepNum)
    }

    return (
        <>
            <div className={'step-progress '   classNames}>
                {renderSteps()}
            </div>
        </>
    )
}

export default MainContainer
  

Комментарии:

1. Что такое props.validateForm ? Куда validateForm передается в качестве реквизита FormComponent ? Можете ли вы обновить свой вопрос, чтобы включить весь соответствующий код?

2. Обновлен основной компонент контейнера

3. Можете ли вы воссоздать его в песочнице?

4. Согласен, слишком много кода, который нужно просеивать и отслеживать, не понимая, как все это должно сочетаться и работать. Работающий codesandbox был бы отличным. Мне удалось вручную выполнить пошаговый код до submittedStep.stepdata.questions validateForm ввода. Если 'null' !== typeof submittedStep.stepdata.questions условие всегда выполняется false , вы всегда возвращаете пустой {} объект ошибки () обратно в форму. (На самом деле вы всегда возвращаете объект ошибки независимо от того, на самом деле ) Возможно, вы хотели проверить if (Object.keys(errors).length) return errors .

5. Ваш код кажется излишне запутанным, и, честно говоря, это анти-шаблон для хранения «компонентов» / «JSX» в состоянии реакции, т.Е. componentQuestions . Я протестировал это, непосредственно выполнив рендеринг showError(question.question_id) renderForm вместо question.component , и ошибка «test» отображается после первого нажатия кнопки «Нажмите здесь, чтобы проверить» (FormComponent:90). Кажется, что когда вы задаете данные вопроса и получаете компонент, ошибка компонента — это цикл рендеринга (вероятно, из-за чрезмерно сложных обновлений состояния.