如何在功能组件之间传递状态? [英] How do I pass state between functional components?

查看:55
本文介绍了如何在功能组件之间传递状态?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在使用 react-js 和 react-hooks 编写一个注册页面,我仍在学习,所以如果这是一个非常简单的问题,请原谅我.

我有一个用带有钩子的功能组件编写的 signup.js.signup.js 导入 'EmailTextField', 'PasswordTextField', 'NameTextField', 'CellPhoneTextField' ... 其组件也用钩子编写在功能组件中.

我将所有这些文本字段作为单独的组件来简化代码,因为我需要对每个文本字段进行许多不同的检查.(并且在 signup.js 页面中包含所有这些字段会产生很长的代码)

在 signup.js 的过程结束时,我想获得它所有子组件(所有这些文本字段)的状态(用户是否适合登录.)但我不确定如何将状态(或变量)从这些文本字段传递到 signup.js.

我知道 redux 可以管理状态,但有没有办法在没有 redux 的情况下实现这一点?

谢谢.

我创建了一个 CodeSandbox 示例可使用的最少示例代码.

在这里,我使用 apptest.js 中的 EmailTextfield 组件.我想从 apptest.js 获取 EmailTextfield 上的 isValid 状态,以便我可以确保在用户注册之前验证所有字段.

'./components/UI/Textfield/EmailTextField.js'

import React, { useState } from "react";从@material-ui/core/TextField"导入TextField;从@material-ui/core/Grid"导入网格;export const EmailTextField = props =>{const [value, setValue] = useState("");const [helperText, setHelperText] = useState(电子邮件地址将用作您的用户名.");const [isValid, setIsValid] = useState("true");const handleOnChangeEmailAddress = 事件 =>{//电子邮件验证逻辑如果属实) {setIsValid(true);} 别的 {setIsValid(false);}};返回 (<网格项 xs={12}><文本字段变体=概述"必需的全屏宽度id="电子邮件"标签=电子邮件地址"错误={!isValid}helperText={helperText}名称=电子邮件"自动完成=电子邮件"边距=密集"onBlur={handleOnChangeEmailAddress}/></网格>);};导出默认的 EmailTextField;

'aptest.js'

从react"导入React;从@material-ui/core/CssBaseline"导入CssBaseline;从@material-ui/core/Grid"导入网格;import { makeStyles } from "@material-ui/core/styles";从@material-ui/core/Container"导入容器;import { EmailTextField } from "./components/UI/Textfield/EmailTextField";const useStyles = makeStyles(theme => ({@全球的": {身体: {背景颜色:theme.palette.common.white}},纸: {marginTop: 主题.spacing(8),显示:弹性",flexDirection: "列",alignItems:中心"},主框:{//边距:'200px',宽度:550px",文本对齐:左",boxShadow: "0 2px 3px #ccc",边框:1px 实心#eee",padding: "40px 70px 50px 70px",boxSizing:边框框"},形式: {width: "100%",//修复 IE 11 问题.marginTop:theme.spacing(3)}}));const Apptest = props =>{const 类 = useStyles();返回 (<容器组件="main" maxWidth="xs"><CssBaseline/><div className={classes.paper}>

<form className={classes.form} noValidate><网格容器间距={2}><EmailTextField/></网格></表单>

</容器>);};导出默认的Apptest;

解决方案

我有一个非常粗略的实现.

输入字段周围应该有一致的数据模型.该数据模型应该是该特定输入字段的单一事实来源.它应该能够判断该特定字段是否被触及、是否有错误、是否原始、它的价值是什么等等.

假设你有这样的情况:

错误:[],onChange:假,原始:真实,感动:假的,价值,

我们称之为StateChangeEvent.

现在每个输入字段都将有一个处理诸如更改和模糊之类的事件.在这里,单个组件将更新 StateChangeEvent.而这些方法最终会调用一个以StateChangeEvent为参数的回调函数.

这样,父级就会知道其中一个字段发生了变化,并且可以做出相应的响应.

在父组件中,为了启用表单上的提交按钮,我们还可以有一个副作用,即更新表单的整体状态.像这样:

useEffect(() => {const isValid = !fieldOne.onChange &&fieldOne.errors.length === 0 &&fieldOne.value.length !== 0 &&!fieldTwo.onChange &&fieldTwo.errors.length === 0 &&fieldTwo.value.length !== 0 &&...;setIsFormValid(isValid);}, [fieldOne, fieldTwo, ...]);

我确定这不是一个完整的解决方案.但我相信它会让你开始.

更新:

根据您提供的 CodeSandbox,您可以采取以下措施来完成这项工作:

导入...const useStyles = makeStyles(theme => ({ ... }));const Apptest = props =>{const 类 = useStyles();const [isInvalid, setIsInvalid] = useState(true);const handleStateChange = updatedState =>{console.log("updatedState:", updatedState);updatedState.errors.length === 0 ?setIsInvalid(false) : setIsInvalid(true);};返回 (<容器组件="main" maxWidth="xs"><CssBaseline/><div className={classes.paper}>

<form className={classes.form} noValidate><网格容器间距={2}><EmailTextField onStateChange={handleStateChange}/></网格><按钮变体=包含"颜色=主要"禁用={isInvalid}className={classes.button}>提交</按钮></表单>

</容器>);};导出默认的Apptest;

EmailTextField 组件中:

import React, { useState } from "react";从@material-ui/core/TextField"导入TextField;从@material-ui/core/Grid"导入网格;export const EmailTextField = props =>{const { onStateChange } = props;const [状态,设置状态] = useState({错误:[],onChange:假,原始:真实,感动:假的,值:空});const helperText = "电子邮件地址将用作您的用户名.";const handleBlur = 事件 =>{//电子邮件验证逻辑const 匹配 = event.target.value.match(`[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,3}`);如果(匹配){常量更新状态 = {...状态,感动:真实,值:事件.目标.值,错误:[]};设置状态(更新状态);onStateChange(updatedState);} 别的 {常量更新状态 = {...状态,感动:真实,值:事件.目标.值,错误:[请输入有效的电子邮件"]};设置状态(更新状态);onStateChange(updatedState);}};返回 (<网格项 xs={12}><文本字段变体=概述"必需的全屏宽度id="电子邮件"标签=电子邮件地址"错误={state.errors.length >0}helperText={state.errors.length >0 ?state.errors[0] : helperText}名称=电子邮件"自动完成=电子邮件"边距=密集"onBlur={handleBlur}/></网格>);};导出默认的 EmailTextField;

<小时><块引用>

这是一个 工作 CodeSandbox 示例 供您参考.

I am currently writing a sign-up page with react-js with react-hooks and I am still learning so please excuse me if this is a very simple question.

I have a signup.js written in functional component with hooks. signup.js imports 'EmailTextField', 'PasswordTextField', 'NameTextField', 'CellPhoneTextField' ... components of which are also written in functional components with hooks.

I made all these textfields as separate components to simplify the code as I have a requirement to have many different checks on each text fields. (and having all these fields in signup.js page makes very long code)

At the end of the process in signup.js, I would like to get state of all it's sub-components (all those textfields) status (whether the user is good to sign in or not.) but I am not sure how to pass a state (or variable) from these textfields up to signup.js.

I know redux can manage state but is there anyway to achieve this without redux?

Thank you.

I've created a CodeSandbox Sample with a minimal sample code to work with.

In here, I use EmailTextfield component in apptest.js. I would like to get isValid state on EmailTextfield from apptest.js so that I can make sure all fields are validated before the user signs up.

'./components/UI/Textfield/EmailTextField.js'

import React, { useState } from "react";
import TextField from "@material-ui/core/TextField";
import Grid from "@material-ui/core/Grid";

export const EmailTextField = props => {
  const [value, setValue] = useState("");
  const [helperText, setHelperText] = useState(
    "Email address will be used as your username."
  );
  const [isValid, setIsValid] = useState("true");

  const handleOnChangeEmailAddress = event => {
    // Email Validation logic
    if (true) {
      setIsValid(true);
    } else {
      setIsValid(false);
    }
  };

  return (
    <Grid item xs={12}>
      <TextField
        variant="outlined"
        required
        fullWidth
        id="email"
        label="email address"
        error={!isValid}
        helperText={helperText}
        name="email"
        autoComplete="email"
        margin="dense"
        onBlur={handleOnChangeEmailAddress}
      />
    </Grid>
  );
};

export default EmailTextField;

'aptest.js'

import React from "react";
import CssBaseline from "@material-ui/core/CssBaseline";
import Grid from "@material-ui/core/Grid";
import { makeStyles } from "@material-ui/core/styles";
import Container from "@material-ui/core/Container";
import { EmailTextField } from "./components/UI/Textfield/EmailTextField";

const useStyles = makeStyles(theme => ({
  "@global": {
    body: {
      backgroundColor: theme.palette.common.white
    }
  },
  paper: {
    marginTop: theme.spacing(8),
    display: "flex",
    flexDirection: "column",
    alignItems: "center"
  },
  mainBox: {
    // margin: '200px',
    width: "550px",
    textAlign: "left",
    boxShadow: "0 2px 3px #ccc",
    border: "1px solid #eee",
    padding: "40px 70px 50px 70px",
    boxSizing: "border-box"
  },
  form: {
    width: "100%", // Fix IE 11 issue.
    marginTop: theme.spacing(3)
  }
}));

const Apptest = props => {
  const classes = useStyles();
  return (
    <Container component="main" maxWidth="xs">
      <CssBaseline />
      <div className={classes.paper}>
        <div className={classes.mainBox}>
          <form className={classes.form} noValidate>
            <Grid container spacing={2}>
              <EmailTextField />
            </Grid>
          </form>
        </div>
      </div>
    </Container>
  );
};

export default Apptest;

解决方案

I have a very crude implementation in mind.

There should be a consistent data-model around the Input fields. This data model should be a single source of truth for the that particular Input field. It should be able to tell whether that particular field is touched, has errors, is pristine, what is it's value and all that stuff.

So let's say you have it like this:

errors: [],
onChange: false,
pristine: true,
touched: false,
value,

Let's call it a StateChangeEvent.

Now each Input field will have a handler for events like change and blur. Here that individual component will update the StateChangeEvent. And these methods will eventually call a callback function with StateChangeEvent as an argument.

That way, the parent will know that there was a change in one of the fields and it can respond accordingly.

In the parent component, to make the Submit Button on the form enabled, we can also have a side effect that will update the overall state of the form. Something like this:

useEffect(() => {
  const isValid = !fieldOne.onChange &&
    fieldOne.errors.length === 0 &&
    fieldOne.value.length !== 0 &&
    !fieldTwo.onChange &&
    fieldTwo.errors.length === 0 &&
    fieldTwo.value.length !== 0 &&
    ...;
  setIsFormValid(isValid);
}, [fieldOne, fieldTwo, ...]);

I'm sure this isn't a complete solution. But I'm sure it would get you started.

UPDATE:

Based on the CodeSandbox that you provided, here's what you can do to make this work:

import ...

const useStyles = makeStyles(theme => ({ ... }));

const Apptest = props => {

  const classes = useStyles();
  const [isInvalid, setIsInvalid] = useState(true);

  const handleStateChange = updatedState => {
    console.log("updatedState: ", updatedState);
    updatedState.errors.length === 0 ? setIsInvalid(false) : setIsInvalid(true);
  };

  return (
    <Container component="main" maxWidth="xs">
      <CssBaseline />
      <div className={classes.paper}>
        <div className={classes.mainBox}>
          <form className={classes.form} noValidate>
            <Grid container spacing={2}>
              <EmailTextField onStateChange={handleStateChange} />
            </Grid>
            <Button
              variant="contained"
              color="primary"
              disabled={isInvalid}
              className={classes.button}
            >
              Submit
            </Button>
          </form>
        </div>
      </div>
    </Container>
  );
};

export default Apptest;

And in the EmailTextField component:

import React, { useState } from "react";
import TextField from "@material-ui/core/TextField";
import Grid from "@material-ui/core/Grid";

export const EmailTextField = props => {
  const { onStateChange } = props;
  const [state, setState] = useState({
    errors: [],
    onChange: false,
    pristine: true,
    touched: false,
    value: null
  });
  const helperText = "Email address will be used as your username.";

  const handleBlur = event => {
    // Email Validation logic
    const matches = event.target.value.match(
      `[a-z0-9._%+-]+@[a-z0-9.-]+.[a-z]{2,3}`
    );
    if (matches) {
      const updatedState = {
        ...state,
        touched: true,
        value: event.target.value,
        errors: []
      };
      setState(updatedState);
      onStateChange(updatedState);
    } else {
      const updatedState = {
        ...state,
        touched: true,
        value: event.target.value,
        errors: ["Please enter a valid email"]
      };
      setState(updatedState);
      onStateChange(updatedState);
    }
  };

  return (
    <Grid item xs={12}>
      <TextField
        variant="outlined"
        required
        fullWidth
        id="email"
        label="email address"
        error={state.errors.length > 0}
        helperText={state.errors.length > 0 ? state.errors[0] : helperText}
        name="email"
        autoComplete="email"
        margin="dense"
        onBlur={handleBlur}
      />
    </Grid>
  );
};

export default EmailTextField;


Here's a Working CodeSandbox Sample for your ref.

这篇关于如何在功能组件之间传递状态?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆