简体   繁体   English

使用 React Hooks 重置为初始 State

[英]Reset to Initial State with React Hooks

I'm currently working on a signup form and the following is a snippet of my code:我目前正在处理注册表单,以下是我的代码片段:

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

Each piece of state is used for a controlled input for the form. state 的每一块都用于表单的受控输入。

Essentially what I want to do is after the user has successfully signed up, I want the state to go back to the initial state with the fields cleared.基本上我想要做的是在用户成功注册后,我希望 state 到 go 回到最初的 state 并清除字段。

It's quite imperative to manually set each piece of state back to empty strings in clearState I was wondering if there is a method or function that comes with React that resets the state back to its initial values?必须在clearState中手动将 state 的每一段设置回空字符串我想知道是否有 React 附带的方法或 function 将 state 重置回其初始值?

There is no built-in way to set the state to its initial value, sadly.遗憾的是,没有内置的方法可以将状态设置为其初始值。

Your code looks good, but if you want to decrease the functions needed you can put your entire form state in a single state variable object and reset to the initial object.您的代码看起来不错,但是如果您想减少所需的功能,您可以将整个表单状态放在单个状态变量对象中并重置为初始对象。

Example例子

 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>

I think the voted answer is still correct, but recently React released the new built-in useReducer which, in their own words, is我认为投票的答案仍然是正确的,但最近 React 发布了新的内置useReducer ,用他们自己的话来说,是

handy for resetting the state later in response to an action便于稍后响应操作重置状态

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

Also it states that it's usually preferable useReducer when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.它还指出,当您具有涉及多个子值的复杂状态逻辑或下一个状态取决于前一个状态时,通常最好使用 useReducer。

Using the same sample on the voted answer, you could use useReducer like this:在投票答案上使用相同的示例,您可以像这样使用 useReducer:

Javascript 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;

Typescript打字稿

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>
    );
};

Notice that I created this reducer function const to be as generic as possible, but you can completely change it and test different action types (other than simply state property names) and perform complex calculations before returning the state modified.请注意,我创建了这个reducer函数 const 以使其尽可能通用,但是您可以完全更改它并测试不同的操作类型(除了简单的状态属性名称)并在返回修改后的状态之前执行复杂的计算。 There are some examples in the link provided above.上面提供的链接中有一些示例。

This has a very simple solution.这有一个非常简单的解决方案。 You can change the key prop where rendering component.您可以更改渲染组件所在的关键道具。 eg when we have a component for editing we can pass a different key to clear previous states.例如,当我们有一个要编辑的组件时,我们可以传递一个不同的键来清除以前的状态。

return <Component key={<different key>} />

If you want a quick n' dirty method you could try just changing the component's key which will cause React to unmount your old component instance and mount a fresh one.如果你想要一个快速不脏的方法,你可以尝试改变组件的键,这将导致 React 卸载旧组件实例并安装一个新的。

I am using Lodash here to generate a unique throwaway ID but you could also probably get away with Date.now() or similar, assuming the time resolution needed is above 1 millisecond.我在这里使用 Lodash 来生成一个唯一的一次性 ID,但假设所需的时间分辨率超过 1 毫秒,您也可以使用Date.now()或类似方法。

I am passing the key a second time as debugKey to make it easier to see what's going on but this is not neccessary.我第二次将密钥作为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>

You could use one state variable as described in the FAQ here: https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables您可以使用此处的常见问题解答中所述的一个状态变量: https : //reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables

It depends on your use case of course.当然,这取决于您的用例。

Rekeying the component from the parent container would also reset it automatically of course.从父容器重新生成组件的密钥当然也会自动重置它。

Alongside the other answers, I'd recommend picking up a helper library like this , or making your own abstraction on top of hooks, if this is something you'll be doing often.除了其他的答案,我建议你拿起一个辅助库这样的,或者制作在钩子上的顶部自己的抽象,如果这是你会经常做的事情。

useState and friends are really just low-level primitives for you, the user, to build more useful hooks on top of it. useState和朋友实际上只是您(用户)的低级原语,可以在其上构建更多有用的钩子。 I have projects where raw useState calls are actually fairly uncommon.我有一些项目,其中原始useState调用实际上相当不常见。

You could have used useRef in hooks something like this你可以在类似这样的钩子中使用 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>

I just wrote a custom hook that returns the actual hooks, along with a resetState function.我刚刚编写了一个返回实际钩子的自定义钩子,以及一个resetState函数。

Usage:用法:

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

// - OR -

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

The latter is less readable but the former duplicates the keys, so there isn't a perfect solution.后者可读性较差,但前者会复制密钥,因此没有完美的解决方案。

The code:编码:

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];
};

One way to achieve this "reset states to initial" is by using the use-state-with-deps package.实现此“将状态重置为初始状态”的一种方法是使用use-state-with-deps package。

Example:例子:

import {useStateWithDeps} from "use-state-with-deps";

const Signup = () => {
    const [generation, setGeneration] = useState(0);

    const [username, setUsername] = useStateWithDeps("", [generation])
    const [email, setEmail] = useStateWithDeps("", [generation])
    const [password, setPassword] = useStateWithDeps("", [generation])
    const [passwordConfirmation, setPasswordConfirmation] = useStateWithDeps("", [generation])

    const clearState = () => {
        setGeneration(generation + 1);
    }

    const handleSubmit = signupUser => e => {
        e.preventDefault()
        signupUser().then(data => {
            console.log(data)
            clearState()
        })
    }

    return <JSX />
}

export default Signup

If you don't want to pull in a new dependency, you can find other solutions in this thread , which are short enough to just include directly in your project (eg. in a "utils" file).如果您不想引入新的依赖项,您可以在此线程中找到其他解决方案,这些解决方案足够短,可以直接包含在您的项目中(例如,在“utils”文件中)。 For example, this solution is only 20 lines long.例如, 这个解决方案只有 20 行长。

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

I had a similar use case.我有一个类似的用例。 Completelty unrelated from a Login, Signup mechanism but I changed it to be related to your use case.与登录、注册机制完全无关,但我将其更改为与您的用例相关。

An easy way to solve this is with a parent component in my opinion.在我看来,解决这个问题的一个简单方法是使用父组件。

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

You can 'wrap' your useState in another use[Whatever name you want] and include a reset function - ie like a custom hook as suggested by Augustin in his answer.您可以在另一个用途中“包装”您的 useState [无论您想要什么名称] 并包含一个重置功能 - 即就像 Augustin 在他的回答中建议的自定义挂钩。

Taking the example of an input form, as there is a good real example you can use and view the source of as noted below, you would use the custom hook similar to this:以输入表单为例,因为您可以使用一个很好的真实示例并查看其源代码,如下所述,您将使用与此类似的自定义钩子:

function ContactForm(props) {
  const [state, handleSubmit, reset] = useForm("contactForm");

  const clearForm = e => {
    e.preventDefault();
    reset();  // <---- the extra reset function
    // Any other code you want like hiding 
    // or removing the form div from the 
    // DOM etc.
  }

  if (state.succeeded) {
      return (
        <React.Fragment>
          <p>Thanks fro your input!</p>
          <button className="default-button" onClick={clearForm}>Ok</button>
        </React.Fragment>
      );
  }
  return (
      <form onSubmit={handleSubmit}> // <-- the standard setSate type function
      <label htmlFor="email" className="form-element">
        Email Address
      </label>
      <input
        id="email"
        type="email"
        name="email"
        className="form-element"
      />
      // etc - Your form code...
      <button className="default-button" type="submit" disabled={state.submitting}>
        Submit
      </button>
    </form>
  );
}

You can see this in action in the fomrspree git respoitory react examples (at the time of writing) - the function is defined in the useForm source and there is an example of its use in 'useForm.test.js':您可以在 fomrspree git respoitory react 示例中看到这一点(在撰写本文时) - 该函数在 useForm 源代码中定义,并且在“useForm.test.js”中有一个使用示例:

There is a lot of hooks on GitHub and NPM, here's some of them: GitHub 和 NPM 上有很多钩子,这里是其中的一些:

I just listed a lot of hooks collections and still cannot find one with "is changed" flag.我刚刚列出了很多钩子 collections,但仍然找不到带有“已更改”标志的钩子。

This is how you can reset input values(from object) in hooks after form submission.这是提交表单后如何在钩子中重置输入值(来自对象)

You can define multiple input values in same useState like firstName , lastName , etc ...您可以在同一个useState定义多个输入值,例如firstNamelastName...

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

Sample code.示例代码。

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>
  );
}

If you want multiple input to define in object instead of declaring seperately.如果您希望在对象中定义多个输入而不是单独声明。

I agreed @Tholle's answer totally.我完全同意@Tholle 的回答。

If you need to run some functions after state is cleared如果需要在清除状态后运行某些功能

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

// when component is unmounted

() => clearState().foo();
() => clearState().bar();
const handleSubmit = e => {
    e.preventDefault();
    reset();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM