jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
34k stars 2.79k forks source link

Other components get formik value outside <Formik/> onchange #1375

Open aboutjquery opened 5 years ago

aboutjquery commented 5 years ago

❓Question

Hello, how can other components get formik value outside onchange?

Here is the code https://codesandbox.io/s/5wmrp396kp

I wanna show formik values on ComponentOne.js and ComponentTwo.js but i have no idea how to do that :(

please help, thanks so much.

osv commented 5 years ago

if you dont want to pass value from render function, you always can use formik's context

AyanGhatak commented 5 years ago

You can also keep a local state in the component, and update it before passing to Formik. Sort of this.

Edit Formik Example

const App = () => {
  let [formValues, setFormValues] = useState(initialValues);
  return (
    <div className="app">
      <Formik ...>
        {props => {
          const {
            ...,
            handleChange,
          } = props;
          const onChange = e => {
            const targetEl = e.target;
            const fieldName = targetEl.name;
            setFormValues({
              ...formValues,
              [fieldName]: targetEl.value
            });
            return handleChange(e);
          };

          return (
            <form onSubmit={handleSubmit}>
              ...
              <input
                name="email"
                ....
                onChange={onChange}
              />
             ....
              <input
                name="email2"
                ....
                onChange={onChange}
              />
              ....
            </form>
          );
        }}
      </Formik>
      <ComponentOne formValues={formValues} />
      <ComponentTwo formValues={formValues} />
    </div>
  );
};
otaviobonder-deel commented 4 years ago

Hi @osv, I'm trying to implement it using useFormikContext(). You said that:

if you dont want to pass value from render function, you always can use formik's context

What I did was create a function that will open a Dialog to validate a phone number. I need to access the updated phone.phoneNumber field from Formik, before submitting it.

I followed the useFormikContext() official documentation:


const SmsDialog = ({ open, handleClose, token, changeToken }) => {
    const { values } = useFormikContext();

    useEffect(() => {
        console.log(values.phone);
    }, [values]);

    return (
        <Dialog open={open} maxWidth="sm" onClose={handleClose}>
            <DialogContent>
                <Typography align="center">
                    A confirmation code was sent by SMS to your mobile. Please,
                    type the code below to confirm your number
                </Typography>
                <div
                    style={{
                        display: "flex",
                        alignItems: "center",
                        justifyContent: "center",
                        margin: 20
                    }}
                >
                    <TextField
                        value={token}
                        onChange={changeToken}
                        variant="outlined"
                    />
                </div>
            </DialogContent>
        </Dialog>
    );
};

export default function Phone(props) {
    const { formData, setFormData, nextStep, prevStep } = props;
    const [direction, setDirection] = useState("back");
    const classes = useStyles();

    // confirmation dialog state
    const [open, setOpen] = useState(false);
    const handleClose = () => setOpen(false);

    //sms state
    const [token, setToken] = useState("");
    const [valid, setValid] = useState(false);
    const changeToken = event => setToken(event.target.value);

    return (
        <>
            <Typography align="center">
                Let's validate your phone number
            </Typography>
            <Formik
                initialValues={formData}
                onSubmit={values => {
                    setFormData(values);
                    direction === "back" ? prevStep() : nextStep();
                }}
            >
                <Form style={{ width: "100%" }}>
                    <Container maxWidth="sm">
                        <div className="flex-div-column field-separation margin-top-bottom">
                            <div style={{ display: "flex" }}>
                                <Field
                                    name="phone.phoneNumber"
                                    label="Phone number"
                                    component={MaterialUiPhoneNumber}
                                    regions={"europe"}
                                    defaultCountry="es"
                                    countryCodeEditable={false}
                                    variant="outlined"
                                    InputProps={{ labelWidth: 110 }}
                                    InputLabelProps={{
                                        classes: { root: classes.label }
                                    }}
                                />
                                <Button
                                    variant="contained"
                                    color="primary"
                                    style={{ marginLeft: 10 }}
                                >
                                    Validate
                                </Button>
                            </div>
                            <div style={{ display: "flex" }}>
                                <ContainedButton
                                    type="submit"
                                    onClick={() => setDirection("back")}
                                >
                                    Previous step
                                </ContainedButton>
                                <ContainedButton
                                    type="submit"
                                    onClick={() => setDirection("forward")}
                                    //disabled={!valid}
                                >
                                    Next step
                                </ContainedButton>
                            </div>
                        </div>
                    </Container>
                    <SmsDialog
                        open={open}
                        handleClose={handleClose}
                        token={token}
                        changeToken={changeToken}
                    />
                </Form>
            </Formik>
        </>
    );
}

However the values.phone that comes from useFormikContext() it's the initialValue that I passed while creating the form, I can't get the updated field. Am I missing something?

Edit: Apparently it's something with my MaterialUiPhoneNumber component. Changing the component worked as expected

hyposlasher commented 4 years ago

So any ideas how to resolve this problem?

otaviobonder-deel commented 4 years ago

@hyposlasher just use the hook useFormikContext(); like this: const { values } = useFormikContext();. values will have the updated fields

resting commented 4 years ago

@otaviobps any idea if there's any substitute for useFormikContext() in v 1.5.7? Its on a react 16.3.1 prehooks era.

otaviobonder-deel commented 4 years ago

@otaviobps any idea if there's any substitute for useFormikContext() in v 1.5.7? Its on a react 16.3.1 prehooks era.

Not sure, sorry but I only use formik with hooks