我已经使用Remix一段时间了,我非常喜欢它。 这里是我在Remix应用中用于处理路由内提交的自定义Hook。
import { useActionData, useNavigation, useSubmit } from '@remix-run/react'
import { useEffect } from 'react'
import { parseSubmissionData } from '~/utils/object'
import { useIndexRouteDetector } from './use-index-route-detector'
import type { RouteSubmission, RouteSubmissionInput, SubmitData } from '~/types/hooks'
/**
* 从任何嵌套元素向当前路由提交数据。
* 相比`useSubmitFetcher`更推荐使用这个,因为它允许从`useActionData`钩子获取的`actionData`可以在路由中的任何嵌套组件中访问,无论嵌套多深。
*
* @param input - `object` - 用于路由提交的输入
* @param [input._action] - 用于路由提交的`_action`,它将被添加到提交数据中
* @param [input.onSubmitted] - 当路由提交完成时运行的回调函数
* @returns RouteSubmission
* @example
* ```tsx
* import { useRouteSubmission } from '~/hooks'
*
* export function MyComponent() {
* // 应该添加一个`_action`参数来区分多个提交
* let [submit, submitting, submitData] = useRouteSubmission({ _action: 'myAction' })
* // 或者 let {submit, submitting, submitData} = useRouteSubmission({ _action: 'myAction' })
*
* let handleClick = () => submit({ name: 'John Doe' })
* let loading = submitting
*
* return (
* <Button loading={loading} onClick={handleClick}>
* Submit
* </Button>
* )
* }
*```
*/
export function useRouteSubmission(input: RouteSubmissionInput): RouteSubmission {
let _submit = useSubmit()
let navigation = useNavigation()
let isIndexRoute = useIndexRouteDetector()
let actionData = useActionData()
let { _action, onSubmitted } = input || {}
let submitData = parseSubmissionData(navigation)
let isActionMatched = submitData?._action === _action
useEffect(() => {
if (isActionMatched && navigation.state === 'loading') {
onSubmitted?.(submitData, actionData)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [navigation.state])
let submit = (data: SubmitData = {}) => {
let actionURL = window.location.pathname
_submit(
{ data: JSON.stringify({ ...data, _action }) },
{
action: isIndexRoute ? `${actionURL}?index` : actionURL,
method: 'post',
replace: true,
}
)
}
let submitting = isActionMatched && navigation.state === 'submitting'
return Object.defineProperty({ submit, submitting, submitData }, Symbol.iterator, {
enumerable: false,
value: function* () {
yield submit
yield submitting
yield submitData
},
}) as RouteSubmission
}
Hook中使用的工具函数:
import type { useNavigation } from '@remix-run/react'
import type {SubmitDataWithAction} from '~/types/hooks'
/**
* 获取提交的数据
*
* @param navigation - 从`useNavigation`钩子返回的当前页面导航
* @example
* let data = parseSubmissionData(transition)
*/
export function parseSubmissionData(
navigation: ReturnType<typeof useNavigation>,
): SubmitDataWithAction {
let formData = navigation?.formData
if (!formData) return null
return JSON.parse((Object.fromEntries(formData) as any).data)
}
以及
import { useLocation, useMatches } from '@remix-run/react'
export function useIndexRouteDetector() {
let matches = useMatches()
let location = useLocation()
let match = matches.find(({ pathname }) => pathname === location.pathname)
if (match) {
return !!match.id.match(/\/index$/) || match.id === 'root'
}
return false
}
Hook中使用的类型定义:
export type RouteSubmissionInput = {
_action: string
onSubmitted?: (
submitData: SubmitDataWithAction,
actionData: { [key: string]: any },
) => void
}
export type SubmitData = {
[key: string]: any
}
export type SubmitDataWithAction = {
_action?: string
[key: string]: any
}
type SubmitFunction = (data?: SubmitData) => void
export type RouteSubmission = {
submit: SubmitFunction
submitting: boolean
submitData: SubmitDataWithAction
} & [SubmitFunction, boolean, SubmitDataWithAction]
使用示例:
import { Button } from '~/components/button'
import { useRouteSubmission } from '~/hooks/use-route-submission'
export function SaveProject() {
// 应该添加一个`_action`参数来区分多个提交
let [submit, submitting] = useRouteSubmission({ _action: 'SAVE_DATA' })
// 或者 let {submit, submitting} = useRouteSubmission({ _action: 'SAVE_DATA' })
function save() {
submit({ name: 'John Doe' })
}
return (
<Button loading={submitting} onClick={save}>
保存
</Button>
)
}
愉快地提交吧!