React znikające wartości w kontrolowanych komponentach formularza

0

Sprawa wygląda w ten sposób: kontrolowane elementy formularza używają callbacków do updateu sateów, Gdy chcę odczytać ich wartość z innego callbacku, zawsze widnieje w nich wartość początkowa/domyślna. Kilka commitów wcześniej działało i nie zauważyłem żadnych znaczących zmian przy kontrolowanych elementach :d

Mniej więcej tak to wygląda:

    const [textAreaValue, setTextAreaValue] = useState<string>("")

    const handleTextFormValueChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
        const newValue = e.target.value //tu jest widoczna prawidłowa wartość podczas wywołania
        setTextAreaValue(newValue)
    }

    const submitContactFormCallback = () => { // <----------------------------------- odczytywanie z kontrolowanego komponentu
        if(contactFormStatus !== ContactFormStatus.IDLE)
            return

        console.log(`pre send area: ${textAreaValue}`) // <--------------------------- zawsze ma wartość domyślną

        dispatch(submitContactForm(textAreaValue, titleValue))
    }

     <textarea 
          className="mb-10 py-4 px-2 bg-background-main font-content text-xl rounded-lg border-4 border-gray-400" 
          placeholder=""
          onChange={handleTextFormValueChange}
          value={textAreaValue}
          rows={12}
     />

        <button 
            disabled={!canSend}
            onClick={submitContactFormCallback}
        >
            Send
        </button>

Cały komponent:

export const Contact = () => {
    const dispatch = useDispatch()

    const contactFormStatus = useSelector(contactFormStatusSelector) 
    const contactFormResponse = useSelector(contactFormResponseSelector)

    const [textAreaValue, setTextAreaValue] = useState<string>("")
    const [titleValue, setTitleValue] = useState<string>("Hello")

    const clearFormStateCallback = useCallback(() => { 
        dispatch(clearContactFormState())
    }, [,])

    const handleTitleValueChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
        const newValue = e.target.value
        setTitleValue(newValue)
    }
    
    const handleTextFormValueChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
        const newValue = e.target.value
        setTextAreaValue(newValue)
    }

    const submitContactFormCallback = () => {
        if(contactFormStatus !== ContactFormStatus.IDLE)
            return

        console.log(`pre send area: ${textAreaValue}`) // <--------------------------- pusto tu
        console.log(`pre send title: ${titleValue}`) // <--------------------------- zawsze ma wartosc domyslna "Hello"

        dispatch(submitContactForm(textAreaValue, titleValue))
    }

    const ConnectedDialog = useMemo(() => {
        const isError = contactFormStatus === ContactFormStatus.FAILED

        //show when sending failed or succeeded
        const showDialog = contactFormStatus === ContactFormStatus.FAILED || contactFormStatus === ContactFormStatus.SENT

        return <Dialog 
            text={contactFormResponse}
            className={BindClasses({
                "bg-red-800 text-rawSalmon": isError,
                "bg-deadSalmon text-black": !isError
            }, " z-60 fixed flex flex-row flex-wrap items-center justify-between bottom-6 md:bottom-8 lg:bottom-12 left-1/2 m-1 w-11/12 lg:w-9/12 text-lg rounded-md shadow-2xl transform -translate-x-1/2")}
            show={showDialog}
            dialogButtons={[
                //close button
                {
                    callback: clearFormStateCallback,
                    // ! refactor, move outside :d
                    buttonContent: (
                    <>
                        {/* @ts-ignore "alt" here is fine */}
                        <svg alt="Close message" className="w-24 h-24" viewBox="0 0 24 24">
                            <path fill="currentColor" d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
                        </svg>
                    </>),
                    className: "bg-rawSalmon rounded-md transition duration-200 text-purpleAcid hover:text-white hover:bg-purpleAcid"
                }
            ]} 
        />
    }, [contactFormStatus, contactFormResponse]) //update dialog only when contactFormStatus or contactFormResponse changes state

    const ConnectedSubmitButton = useMemo(() => {
        const canSend = contactFormStatus === ContactFormStatus.IDLE

        return <button 
            className={BindClasses({
                "opacity-20 cursor-not-allowed hover:bg-purple-600 hover:no-underline": !canSend,
                "hover:underline hover:bg-purple-700": canSend
            }, "mx-4 mt-6 px-8 lg:px-12 py-4 w-auto h-auto rounded-2xl font-content self-center text-xl bg-purple-600 transition duration-200")}
            
            disabled={!canSend}
            
            onClick={submitContactFormCallback}
            // ! for dev only
            // onClick={e => { dispatch(dummySubmitContactFormWithSuccess(textAreaValue, titleValue)) }}
            // onClick={e => { dispatch(dummySubmitContactFormWithError(textAreaValue, titleValue)) }}
        >
            Send
        </button>
    }, [contactFormStatus])

    return (
        // contact wrapper
        <div id="contact" className="flex py-20 ml-2 lg:ml-12">
            {/* dialog */}
            {
                ConnectedDialog
            }

            {/* contact form */}
            <div className="flex flex-col m-auto w-10/12 lg:w-9/12 ">

                <div className="font-display text-5xl">Contact form</div>
                
                <select 
                    className="my-6 px-4 py-2 h-16 bg-background-main font-content text-2xl rounded-lg border-4 border-gray-400  " 
                    value={titleValue}
                    onChange={handleTitleValueChange}
                >
                    <option value="first">Hello</option>
                    <option value="second">Other</option>
                </select>

                <textarea 
                    className="mb-10 py-4 px-2 bg-background-main font-content text-xl rounded-lg border-4 border-gray-400" 
                    name="textarea"
                    placeholder=""
                    onChange={handleTextFormValueChange}
                    value={textAreaValue}
                    rows={12}
                />

                {
                    ConnectedSubmitButton
                }
            </div>
        </div>
    )
}
2

Problem jest raczej przez useMemo, ponieważ te komponenty, które memoizujesz nie odświeżają nowego state'u i powinieneś im podać odpowiednie zależności.

const ConnectedDialog = useMemo(() => {
  // ...
}, [contactFormStatus, contactFormResponse])

const ConnectedSubmitButton = useMemo(() => {
  // ...
}, [contactFormStatus])

1 użytkowników online, w tym zalogowanych: 0, gości: 1