import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react' import { useTranslation } from 'next-i18next' import { isDegreeMeasurement } from '../../../config/measurements' import measurementAsMm from 'pkgs/utils/src/measurementAsMm' /* * This is a single input for a measurements * Note that it keeps local state with whatever the user types * but will only trigger a gist update if the input is valid. * * m holds the measurement name. It's just so long to type * measurement and I always have some typo in it because dyslexia. */ const MeasurementInput = ({ m, gist, app, updateMeasurements, focus }) => { const { t } = useTranslation(['app', 'measurements']) const prefix = (app.site === 'org') ? '' : 'https://freesewing.org' const title = t(`measurements:${m}`) const isDegree = isDegreeMeasurement(m); const factor = useMemo(() => (isDegree ? 1 : (gist.units == 'imperial' ? 25.4 : 10)), [gist.units]) const isValValid = val => (typeof val === 'undefined' || val === '') ? null : val != false && !isNaN(val) const isValid = (newVal) => (typeof newVal === 'undefined') ? isValValid(val) : isValValid(newVal) const [val, setVal] = useState(gist.measurements?.[m] / factor || '') // keep a single reference to a debounce timer const debounceTimeout = useRef(null); const input = useRef(null); // onChange const update = useCallback((evt) => { evt.stopPropagation(); let evtVal = evt.target.value; // set Val immediately so that the input reflects it setVal(evtVal) let useVal = isDegree ? evtVal : measurementAsMm(evtVal, gist.units); const ok = isValid(useVal) // only set to the gist if it's valid if (ok) { // debounce in case it's still changing if (debounceTimeout.current !== null) { clearTimeout(debounceTimeout.current); } debounceTimeout.current = setTimeout(() => { // clear the timeout reference debounceTimeout.current = null; updateMeasurements(useVal, m) }, 500); } }, [gist.units]) // use this for better update efficiency // FIXME: This breaks gist updates. // See: https://github.com/freesewing/freesewing/issues/2281 const memoVal = useMemo(() => gist.measurements?.[m], [gist]) // track validity against the value and the units const valid = useMemo(() => isValid(isDegree ? val : measurementAsMm(val, gist.units)), [val, gist.units]) // hook to update the value or format when the gist changes useEffect(() => { // set the value to the proper value and format if (memoVal) { let gistVal = +(memoVal / factor).toFixed(2); setVal(gistVal) } }, [memoVal, factor]) // focus when prompted by parent useEffect(() => { if (focus) { input.current.focus(); } }, [focus]) // cleanup useEffect(() => { clearTimeout(debounceTimeout.current) }, []) if (!m) return null return (
) } export default MeasurementInput