【问题标题】:Reset to Initial State with React Hooks使用 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 附带的方法或函数可以将状态重置回其初始值?

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案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();
              

              【讨论】:

                猜你喜欢
                • 2020-11-02
                • 1970-01-01
                • 2019-09-07
                • 1970-01-01
                • 2019-07-07
                • 2020-04-12
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多