You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript React路由报错:缺少FormProps要求的toggleCreateForm属性

问题描述

将应用重构为TypeScript后,使用React Router时遇到报错:

Property 'toggleCreateForm' is missing in type '{}' but required in type 'FormProps'

toggleCreateForm是在其他组件中声明、由自定义hook管理的状态,此前非TypeScript环境可正常运行,疑惑为何需要在路由元素中传递该props。


相关代码

App.tsx

function App(): React.JSX.Element {
  const [isLoading, setIsLoading] = useState(false);
  const token = useAppSelector(selectToken);
  const currentUser = useAppSelector(selectCurrentUser);
  const isAuthenticated = useAppSelector(selectAuthenticated);
  
  const dispatch = useAppDispatch();
  
  const checkLoggedIn = useCallback(() => {
    setIsLoading(true)
    dispatch(userCheckLoggedIn());
    setIsLoading(false)
  }, [isAuthenticated]);

  useEffect(() => { 
    checkLoggedIn()
  }, [checkLoggedIn])
  
  console.debug(
    "App",
    "currentUser", currentUser,
    "token=", token
  )
    
  // Find current user using local storage "token"

  if (isLoading) {
    return <LoadingComponent/>
  } else {
    if (!(token && isAuthenticated)) {
      return (
        <div className="">
          <NavBar></NavBar>
          <Routes>
            <Route path="/register" element={<SignupForm/>}/>
            <Route path="/" element={<Homepage/>}/>
            <Route path="*" element={<Homepage/>}/>
          </Routes>
        </div>
      )
    } else {
      return (
        <div className="App">
          <NavBar></NavBar>
          <Routes>
            <Route path="/workouts" element={<WorkoutList/>}/>
            <Route path="/workouts/:workout_id" element={<WorkoutDetail/>}/>
            <Route path="/workouts/add" element={<NewWorkoutForm/>}/>
            <Route path="/register" element={<SignupForm/>}/>
            <Route path="/exercises" element={<ExerciseList/>}/>
            <Route path="/exercises/add" element={<NewExerciseForm/>}/>
            <Route path="/account" element={<Account/>}/>
            <Route path="/" element={<Homepage/>}/>
            <Route path="*" element={<NotFound/>}/>
          </Routes>
        </div>
      );
    }
  }
}

export default App;

NewWorkoutForm.tsx

interface FormValues {
  name: string,
  category: number
};

interface Category {
  id: number,
  user_id: number
  name: string,
  systemdefault: boolean
};

interface FormProps{
  toggleCreateForm(): boolean
}

const NewWorkoutForm = ({toggleCreateForm} : FormProps): React.JSX.Element => {
    const dispatch = useAppDispatch();
    const categories = useAppSelector(selectCategories);
    const currentUser = useAppSelector(selectCurrentUser);

    const categoryComponents : object[] = categories.map( (cat : Category) => (
        <option value={cat.id}>{cat.name}</option>
    ))

    return (
        <div className="d-flex justify-content-center">
            <Card className="w-25 d-flex flex-column align-items-center pb-3">
                <h3>New Workout Form</h3>
                <Formik
                    initialValues={{
                        name: '', 
                        category: 1,
                        }}
                    validate={(values: FormValues) => {
                        const errors: FormikErrors<FormValues> = {};
                        if (!values.name){ errors.name = 'Name Required'}
                        return errors
                    }}

                    onSubmit={(values : FormValues, { setSubmitting } : FormikHelpers<FormValues>) => {
                        setTimeout( async () => {
                            dispatch(addWorkout({
                                ...values,
                                user_id: currentUser.id,
                                favorited: false
                            }))
                            setSubmitting(false);
                            toggleCreateForm();
                        }, 400)
                    }}
                    >
                        {({isSubmitting}) => (
                            <div>
                            <Form>
                                <div className="d-flex flex-column align-items-center row-gap-3">    
                                    <div className="d-flex flex-column align-items-between row-gap-3">

                                    <div className="d-flex flex-row justify-content-between">
                                        <label htmlFor="name">Workout Name:</label>
                                        <Field type='name' name='name'/>
                                    </div>
                                    <div style={{color: "red"}}>
                                        <ErrorMessage name='name' component='div'/>
                                </div>
                                    <div className="d-flex flex-row justify-content-between">
                                        <label htmlFor="category">Workout Category:</label>
                                        <Field as='select' name='category'>
                                            {categoryComponents}
                                        </Field>
                                    </div>
                                    <div style={{color: "red"}}>
                                        <ErrorMessage name='category' component='div'/>
                                </div>
                                    </div>
                                    <div className="d-flex flex-row column-gap-5">
                                        <button className="btn btn-danger" onClick={toggleCreateForm}>Cancel</button>
                                        <button className="btn btn-success" type='submit' disabled={isSubmitting}>Create Workout</button>
                                    </div>
                                </div>
                            </Form>
                            </div>
                        )}
                </Formik>
            </Card>
        </div>
    )
}

export default NewWorkoutForm;

toggleCreateForm 相关代码

const [showCreateWorkoutForm, setShowCreateWorkoutForm] = useToggle();

const toggleCreateForm = () => { 
  setShowCreateWorkoutForm()
}

原因与解决方案

原因

TypeScript对组件Props做强类型校验:你给NewWorkoutForm定义了FormProps接口,其中toggleCreateForm是必填属性,但在App.tsx的路由中渲染<NewWorkoutForm/>时未传递该props,因此触发报错。

而JavaScript环境下无类型校验,即便缺传props也不会提前报错,但运行时若toggleCreateForm不存在,仍会抛出toggleCreateForm is not a function的错误,只是问题被延后暴露了。

解决方案

根据你的使用场景,可选择以下两种处理方式:

方式1:将toggleCreateForm设为可选属性

如果组件存在不需要该函数的使用场景,可修改接口标记属性为可选,同时修正返回值类型(原接口定义的boolean有误,setShowCreateWorkoutForm无返回值):

interface FormProps{
  toggleCreateForm?: () => void
}

使用时需做安全判断,避免运行时错误:

// 示例:点击事件中使用
onClick={() => toggleCreateForm?.()}

方式2:在路由渲染时传递toggleCreateForm

如果组件必须依赖该函数,需调整组件层级或状态管理方式,让路由所在的层级能获取到toggleCreateForm,再传递给组件:

  • 比如将路由嵌套在定义toggleCreateForm的组件内部(如WorkoutList)
  • 或者通过Context、Redux等状态管理方案共享该函数
  • 也可通过React Router的state传参(不推荐用于函数传递,仅作临时方案)

内容的提问来源于stack exchange,提问作者CamTom47

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 00:14:50