react-component / field-form

⚡️ React Performance First Form Component
https://field-form.react-component.now.sh/
MIT License
982 stars 266 forks source link

form.validateFields return error with `outOfDate:=true` in test #196

Open samkahchiin opened 4 years ago

samkahchiin commented 4 years ago

The code works perfectly fine in the apps. However, when I tested it using Jest, it will always return error upon calling form.validateFields(). The sample error returned is

   {
      values: { value: 'Updated' },
      errorFields: [],
      outOfDate: true
    }

I try to look at the source code and it said outOfDate means change when validating.

Does anybody knows how to avoid this error?

Here is my sample test codes

    test('should be able to update the values', async () => {
      renderComponent()
      const valueElem = await screen.findByText('value 1')
      userEvent.click(valueElem)

      const valueInput = await screen.findByTestId('editable-cell-input')
      userEvent.clear(valueInput)
      userEvent.type(valueInput, 'abc {enter}', { allAtOnce: false })

      await waitFor(() => {
        expect(screen.queryByTestId('editable-cell-input')).toBeNull()
        expect(screen.getByText('value 1 Updated')).toBeDefined()
        expect(updateInventoryValue).toHaveBeenCalledWith(1, {
          value: 'value 1 Updated',
        })
      })
    })

In apps

  const save = async () => {
    try {
      const params = await form.validateFields()
      toggleEdit()
      handleSave(record.id, params)
    } catch (errInfo) {
      console.log(errInfo)
      notification.error({
        message: 'Something went wrong!',
        description: 'Please try again.',
      })
    }
  }

      <Form form={form}>
        <Form.Item
          style={{
            margin: 0,
          }}
          name={title}
        >
          <Input
            ref={inputRef}
            onPressEnter={save}
            onBlur={save}
            data-testid="editable-cell-input"
          />
        </Form.Item>
      </Form>
yosang003 commented 3 years ago

Have you found a solution ?

sjoerdsmink commented 2 years ago

For those reading this thread later, a possible workaround is to use a setTimeout around the form.validateFields(). For some reason, it will validate without the outOfDate error. As a helper method, you can use something like:

const formValidateFields = (form: FormInstance): Promise<any> => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      form
        .validateFields()
        .then((values) => resolve(values))
        .catch((e) => reject(e))
    })
  })
}
legend80s commented 4 months ago

I got a same outOfDate = true problem but cannot be solved by setTimeout:

I just want to implement a num1 always less than num2 joint form validation .

import React from 'react';
import './index.css';
import { Button, Form, InputNumber } from 'antd';

const alert = (msg, data) => {
  window.alert(msg + ': ' + JSON.stringify(data, null, 2));
}

const App = () => {
  const [form] = Form.useForm();
  const onFinish = async() => {
    try {
      await form.validateFields()
    } catch (err) {
      alert('validateFields error', err)
      return;
    }
    const values = form.getFieldsValue()
    alert('form values', values)
  };
  return (
    <Form
      form={form}
      labelCol={{
        span: 8,
      }}
      wrapperCol={{
        span: 16,
      }}
    >
      <Form.Item
        label="num1"
        name="num1"
        rules={[
          ({ validateFields }) => ({
            validator() {
              return new Promise((resolve, reject) => {
                validateFields(['num2']).then(
                  (value) => {
                    console.log(value);
                    return resolve();
                  },
                  (reason) => {
                    console.log(reason);
                    return resolve();
                  }
                );
              });
            },
          }),
        ]}
      >
        <InputNumber />
      </Form.Item>

      <Form.Item
        label="num2"
        name="num2"
        rules={[
          {
            validator: (_, value) => {
              if (value > form.getFieldValue('num1')) {
                return Promise.reject("num2 can't more than num1");
              } else {
                return Promise.resolve();
              }
            },
          },
        ]}
      >
        <InputNumber />
      </Form.Item>

      <Button type="primary" htmlType="submit" onClick={onFinish}>
         OK
      </Button>
    </Form>
  );
};

export default App;
legend80s commented 4 months ago

I fixed my problem with setTimeout and callback. the point here is move the validation to next turn instead of waiting for the promise. So the "setTimeout-with-promise" walkaround not work for me. My "setTimout-with-callback" solution:

rules={[
  {
    validator: (rule, value, callback) => {
      setTimeout(() => {
        form.validateFields(['num2']);
      });
      callback();
    },
  },
]}