首页 > 解决方案 > 可重用的表单状态机

问题描述

在尝试围绕 xState 和一般状态机进行研究时,我想知道您将如何向表单状态机提供 API URL 以使其可重用。我目前的解决方案是通过withContext来提供,但是感觉不对。

import { Machine, assign } from 'xstate';

const submitForm = async ({ formData, apiURL }) => {
    const res = await fetch(apiURL, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(formData),
    });

    const message = await res.text();

    return { status: res.status, message };
};

const formMachine = Machine({
    id: 'form',
    initial: 'idle',
    context: {
        formData: {},
        apiURL: '',
    },
    states: {
        idle: {
            on: {
                SEND: 'submitted',
                INPUT: {
                    actions: assign({
                        formData: (ctx, { data }) => ({ ...ctx.formData, ...data }),
                    }),
                },
            },
        },
        submitted: {
            id: 'form-submitted',
            initial: 'pending',
            states: {
                pending: {
                    invoke: {
                        id: 'submitForm',
                        src: submitForm,
                        onDone: {
                            target: 'success',
                            actions: assign({
                                result: (ctx, event) => event.data,
                            }),
                        },
                        onError: {
                            target: 'failure',
                            actions: assign({
                                errorMessage: (ctx, event) => event.data,
                            }),
                        },
                    },
                },
                success: {},
                failure: {
                    on: {
                        RETRY: 'pending',
                        SEND: 'pending',
                    },
                },
            },
        },
    },
});

export default formMachine;
import React from 'react';
import { useMachine } from '@xstate/react';
import formMachine from '../data/machines/form';

const ContactForm = () => {
    const contactFormMachine = formMachine.withContext({
        formData: {
            name: '',
            email: '',
            message: '',
        },
        apiURL: '/api/contact',
    });

    const [current, send] = useMachine(contactFormMachine);

    return (
        <>
            {
                current.matches('submitted.success') ? (
                    <div>Message succesfully sent</div>
                ) : (
                    <form onSubmit={
                        (e) => {
                            e.preventDefault();
                            send('SEND');
                        }
                    }>
                        ...
                    </form>
                )
            }
        </>
    );
};

export default ContactForm;

标签: xstate

解决方案


我认为您有一个很好的解决方案可以使该机器按原样重复使用。这是来自 xstate 可视化工具库的示例,它可以让您对您的解决方案感到更舒服:

const invokeSaveGist = (ctx: AppMachineContext, e: EventObject) => {
  return fetch(`https://api.github.com/gists/` + ctx.gist!.id, {
    method: 'post',
    body: JSON.stringify({
      description: 'Generated by XState Viz: https://xstate.js.org/viz',
      public: true,
      files: {
        'machine.js': { content: e.code }
      }
    }),
    headers: {
      Authorization: `token ${ctx.token}`
    }
  }).then(async response => {
    if (!response.ok) {
      throw new Error((await response.json()).message);
    }

    return response.json();
  });
};

正如你所看到的,url 的“动态”部分也是从机器上下文派生的,当然,它是 gist id,但它也可以是 url 的任何其他部分。

您可以考虑的另一种解决方案,尽管我不会以任何方式认为它是“更好”的解决方案(可能少 3 行代码),但apiUrl在提交表单时传递 as 数据send('SEND');。所以而不是:

<form onSubmit={
    (e) => {
        e.preventDefault();
        send('SEND');
    }
}>

你可以试试:

<form onSubmit={
    (e) => {
        e.preventDefault();
        send('SEND', { apiUrl: 'api/contact'});
    }
}>

推荐阅读