使用 React Hooks 重置为初始状态

Posted

技术标签:

【中文标题】使用 React Hooks 重置为初始状态【英文标题】:Reset to Initial State with React Hooks 【发布时间】:2019-07-20 14:36:51 【问题描述】:

我目前正在制作一个注册表单,以下是我的代码的 sn-p:

const Signup = () => 
    const [username, setUsername] = useState('')
    const [email, setEmail] = useState('')
    const [password, setPassword] = useState('')
    const [passwordConfirmation, setPasswordConfirmation] = useState('')

    const clearState = () => 
        setUsername('')
        setEmail('')
        setPassword('')
        setPasswordConfirmation('')
    

    const handleSubmit = signupUser => e => 
        e.preventDefault()
        signupUser().then(data => 
            console.log(data)
            clearState() // <-----------
        )
    

    return <JSX />


export default Signup

每个状态都用于表单的受控输入。

基本上我想做的是在用户成功注册后,我希望状态恢复到清除字段的初始状态。

clearState 中手动将每个状态设置回空字符串是非常必要的我想知道是否有 React 附带的方法或函数可以将状态重置回其初始值?

【问题讨论】:

【参考方案1】:

遗憾的是,没有内置方法可以将状态设置为其初始值。

您的代码看起来不错,但如果您想减少所需的功能,您可以将整个表单状态放在一个状态变量对象中并重置为初始对象。

示例

const  useState  = React;

function signupUser() 
  return new Promise(resolve => 
    setTimeout(resolve, 1000);
  );


const initialState = 
  username: "",
  email: "",
  password: "",
  passwordConfirmation: ""
;

const Signup = () => 
  const [
     username, email, password, passwordConfirmation ,
    setState
  ] = useState(initialState);

  const clearState = () => 
    setState( ...initialState );
  ;

  const onChange = e => 
    const  name, value  = e.target;
    setState(prevState => ( ...prevState, [name]: value ));
  ;

  const handleSubmit = e => 
    e.preventDefault();
    signupUser().then(clearState);
  ;

  return (
    <form onSubmit=handleSubmit>
      <div>
        <label>
          Username:
          <input value=username name="username" onChange=onChange />
        </label>
      </div>
      <div>
        <label>
          Email:
          <input value=email name="email" onChange=onChange />
        </label>
      </div>
      <div>
        <label>
          Password:
          <input
            value=password
            name="password"
            type="password"
            onChange=onChange
          />
        </label>
      </div>
      <div>
        <label>
          Confirm Password:
          <input
            value=passwordConfirmation
            name="passwordConfirmation"
            type="password"
            onChange=onChange
          />
        </label>
      </div>
      <button>Submit</button>
    </form>
  );
;

ReactDOM.render(<Signup />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

【讨论】:

【参考方案2】:

我认为投票的答案仍然是正确的,但最近 React 发布了新的内置 useReducer,用他们自己的话来说是

便于稍后在响应操作时重置状态

https://reactjs.org/docs/hooks-reference.html#usereducer

它还指出,当您具有涉及多个子值的复杂状态逻辑或下一个状态取决于前一个状态时,通常更可取的是 useReducer。

在投票答案上使用相同的样本,您可以像这样使用 useReducer:

javascript

import React,  useReducer  from "react";

const initialState = 
    username: "",
    email: "",
    password: "",
    passwordConfirmation: "",
;

const reducer = (state, action) => 
    if (action.type === "reset") 
        return initialState;
    

    const result =  ...state ;
    result[action.type] = action.value;
    return result;
;

const Signup = () => 
    const [state, dispatch] = useReducer(reducer, initialState);
    const  username, email, password, passwordConfirmation  = state;

    const handleSubmit = e => 
        e.preventDefault();

        /* fetch api */

        /* clear state */
        dispatch( type: "reset" );
    ;

    const onChange = e => 
        const  name, value  = e.target;
        dispatch( type: name, value );
    ;

    return (
        <form onSubmit=handleSubmit>
            <div>
                <label>
                    Username:
                    <input value=username name="username" onChange=onChange />
                </label>
            </div>
            <div>
                <label>
                    Email:
                    <input value=email name="email" onChange=onChange />
                </label>
            </div>
            <div>
                <label>
                    Password:
                    <input
                        value=password
                        name="password"
                        type="password"
                        onChange=onChange
                    />
                </label>
            </div>
            <div>
                <label>
                    Confirm Password:
                    <input
                        value=passwordConfirmation
                        name="passwordConfirmation"
                        type="password"
                        onChange=onChange
                    />
                </label>
            </div>
            <button>Submit</button>
        </form>
    );
;

export default Signup;

打字稿

import React,  FC, Reducer, useReducer  from "react";

interface IState 
    email: string;
    password: string;
    passwordConfirmation: string;
    username: string;


interface IAction 
    type: string;
    value?: string;


const initialState: IState = 
    email: "",
    password: "",
    passwordConfirmation: "",
    username: "",
;

const reducer = (state: IState, action: IAction) => 
    if (action.type === "reset") 
        return initialState;
    

    const result: IState =  ...state ;
    result[action.type] = action.value;
    return result;
;

export const Signup: FC = props => 
    const [state, dispatch] = useReducer<Reducer<IState, IAction>, IState>(reducer, initialState, () => initialState);
    const  username, email, password, passwordConfirmation  = state;

    const handleSubmit = (e: React.FormEvent) => 
        e.preventDefault();

        /* fetch api */

        /* clear state */
        dispatch( type: "reset" );
    ;

    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => 
        const  name, value  = e.target;
        dispatch( type: name, value );
    ;

    return (
        <form onSubmit=handleSubmit>
            <div>
                <label>
                    Username:
                    <input value=username name="username" onChange=onChange />
                </label>
            </div>
            <div>
                <label>
                    Email:
                    <input value=email name="email" onChange=onChange />
                </label>
            </div>
            <div>
                <label>
                    Password:
                    <input
                        value=password
                        name="password"
                        type="password"
                        onChange=onChange
                    />
                </label>
            </div>
            <div>
                <label>
                    Confirm Password:
                    <input
                        value=passwordConfirmation
                        name="passwordConfirmation"
                        type="password"
                        onChange=onChange
                    />
                </label>
            </div>
            <button>Submit</button>
        </form>
    );
;

请注意,我创建此reducer 函数 const 是为了尽可能通用,但您可以完全更改它并测试不同的操作类型(而不是简单的状态属性名称)并在返回修改后的状态之前执行复杂的计算。上面提供的链接中有一些示例。

【讨论】:

我一直在寻找通用 handleChange 方法的 Typescript 版本,这非常适合。很好的例子@Guilherme【参考方案3】:

这有一个非常简单的解决方案。您可以更改渲染组件的关键道具。 例如,当我们有一个要编辑的组件时,我们可以传递一个不同的键来清除以前的状态。

return <Component key=<different key> />

【讨论】:

感谢@Masih,快速解决方案,完美运行。 当心:如果你依赖 &lt;Component /&gt; 的所有用法来传递 key 属性作为重置内部状态的手段,当你或其他人使用该组件并忘记时,你可能会感到惊讶包括key。我知道这是 react docs 的官方策略,但是这里很容易出错。【参考方案4】:

如果你想要一个快速的脏方法,你可以尝试改变组件的键,这将导致 React 卸载你的旧组件实例并安装一个新的。

我在这里使用 Lodash 来生成一个唯一的一次性 ID,但假设所需的时间分辨率高于 1 毫秒,您也可以使用 Date.now() 或类似的 ID。

我以debugKey 的身份再次传递密钥,以便更轻松地查看发生了什么,但这不是必需的。

const StatefulComponent = ( doReset, debugKey ) => 
  const [counter, setCounter] = React.useState(0);
  const increment = () => setCounter(prev => prev + 1); 
  return (
    <React.Fragment>
      <p>`Counter: $counter`</p>
      <p>`key=$debugKey`</p>
      <button onClick=increment>Increment counter</button>
      <button onClick=doReset>Reset component</button>
    </React.Fragment>
  );
;

const generateUniqueKey = () => `child_$_.uniqueId()`;

const App = () => 
  const [childKey, setChildKey] = React.useState(generateUniqueKey());
  const doReset = () => setChildKey(generateUniqueKey());
  return (
    <div className="App">
      <StatefulComponent key=childKey debugKey=childKey doReset=doReset />
    </div>
  );


const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  rootElement
);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>


<div id="root"></div>

【讨论】:

这绝对是非常肮脏的艾略特,只要有办法,我不会推荐任何人使用这种方法。只是我的 2 美分。 有一个时间和地点可以让你快速变脏。这种方法在过去派上用场,我想确保它在此处得到完整记录,因为任何方法都有利有弊。这个很简单,没有外部依赖,并且可以与基于函数和类的组件一起使用,即使感觉有点 hacky。【参考方案5】:

您可以使用常见问题解答中所述的一个状态变量:https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables

这当然取决于您的用例。

从父容器中重新设置组件的密钥当然也会自动重置它。

【讨论】:

您好,谢谢您的回答,我想知道您所说的“重新输入密钥”是什么意思? @avatarhzh 如果您更改组件上的密钥,react 将卸载它并将其安装为新组件。不确定这是否是这种情况下的最佳方法,因为您可能会失去焦点等。【参考方案6】:

除了其他答案之外,我建议您选择一个帮助程序库 like this,或者在钩子上进行自己的抽象,如果这是您经常做的事情。

useState 和朋友实际上只是低级原语,供您(用户)在其上构建更有用的钩子。我有一些项目,其中原始的useState 调用实际上相当罕见。

【讨论】:

【参考方案7】:

你可以在类似这样的钩子中使用 useRef

 const myForm = useRef(null)

 const submit = () => 

   myForm.current.reset(); // will reset the entire form :)

   

  <form ref=myForm onSubmit=submit>

   <input type="text" name="name" placeholder="John Doe">

     <input type="email" name="name" placeholder="usman@gmail.com">

     <button type="submit">Submit</button>

 </form>

【讨论】:

有趣的答案。提交是否执行完整的默认页面重新提交,或者 myForm.current.reset() 是否会使用 event.preventDefault 标志来更新 DOM 的适当部分? (即用户会看到屏幕“闪烁”和完整的页面重新加载吗?) 一直在玩这个......效果很好。我确认,只是更新了 DOM 的关键元素。见codesandbox here.【参考方案8】:

我刚刚编写了一个返回实际钩子的自定义钩子,以及一个resetState 函数。

用法:

const [
    foo: [foo, setFoo],
    bar: [bar, setBar],
  ,
  resetState,
] = useStateWithReset(
  foo: null,
  bar: [],
)

// - OR -

const [
    [foo, setFoo],
    [bar, setBar],
  ],
  resetState,
] = useStateWithReset([
  null,
  [],
])

后者可读性较差,但前者重复键,因此没有完美的解决方案。

代码:

const useStateWithReset = initialState => 
  const hooksArray = Object.fromEntries(
    Object.entries(initialState).map(([k, v]) => 
      return [k, useState(v)]
    )
  );
  const resetState = () =>
    Object.entries(initialState).map(
      ([k, v]) => hooksArray[k][1](v)
    );
  return [hooksArray, resetState];
;

【讨论】:

【参考方案9】:

我有一个类似的用例。完全与登录、注册机制无关,但我将其更改为与您的用例相关。

在我看来,解决这个问题的一个简单方法是使用父组件。

const initUser = 
  name: '',
  email: '',
  password: '',
  passwordConfirmation: ''      


const LoginManager = () => 
  const [user, setUser] = useState(initUser)

  return <Signup user=user resetUser=setUser />


const Signup = (user, resetUser) => 
    const [username, setUsername] = useState(user.name)
    const [email, setEmail] = useState(user.email)
    const [password, setPassword] = useState(user.password)
    const [passwordConfirmation, setPasswordConfirmation] = useState(user.passwordConfirmation)


    const handleSubmit = signupUser => e => 
        e.preventDefault()
        signupUser().then(data => 
            console.log(data)
            resetUser(initUser) // <-----------
        )
    

    return <JSX />


export default Signup

【讨论】:

如果您不赞成至少在您看来改进此答案,请添加评论【参考方案10】:

据我所知(通过阅读 react 文档)- 目前还没有办法。

【讨论】:

【参考方案11】:

这是提交表单后如何在钩子中重置输入值(来自对象)

您可以在同一个 useState 中定义多个输入值,例如 firstNamelastNameetc...

const [state, setState] = React.useState( firstName: "", lastName: "" );

示例代码。

export default function App() 
  const [state, setState] = React.useState( firstName: "", lastName: "" );
  const handleSubmit = e => 
    e.preventDefault();
    setState(firstName:'',lastName:'')
  ;
  const handleChange = e => 
    const  name, value  = e.target;
    setState( ...state, [name]: value );
  ;
  console.log(state)
  return (
    <form onSubmit=handleSubmit>
      <input
        type="text"
        name="firstName"
        placeholder="Enter first name"
        value=state.firstName
        onChange=handleChange
      />
      <input
        type="text"
        name="lastName"
        placeholder="Enter last name"
        value=state.lastName
        onChange=handleChange
      />
      <input type="submit" value="Submit" />
    </form>
  );

如果您希望在对象中定义多个输入而不是单独声明。

【讨论】:

【参考方案12】:

我完全同意@Tholle 的回答。

如果需要在状态清除后运行一些函数

const clearState = () => 
  setState(...initialState);
  return 
    foo,
    bar,
    // ...
  ;
;

// when component is unmounted

() => clearState().foo();
() => clearState().bar();

【讨论】:

以上是关于使用 React Hooks 重置为初始状态的主要内容,如果未能解决你的问题,请参考以下文章

useEffect 中的 state 总是使用 React Hooks 引用初始状态

React:使用 Hooks 为深度嵌套对象设置状态

React Hooks 实现计数器

在状态对象中使用具有多个键/值对的 React Hooks

React Hooks useState+useEffect+event 给出过时的状态

react-hooks