React.js 状态不会改变

时间:2021-05-05 17:08:49

标签: javascript reactjs react-hooks state

我有一个简单的 React 应用程序,我将完全发布它(它不会那么长)。该应用程序不起作用,但也不会引发任何错误。我试图记录状态,结果它们永远不会改变。我正在使用大东西,比如自定义钩子和 useReducer,但我怀疑我缺乏理解 React 工作原理的基本原理。

以下是应用应该如何工作的简短摘要:
有一个 Form 组件返回一系列自定义 Input 元素(这里只有两个)。
Input 组件将验证逻辑外包给返回 [isTouched, isValid, dispatcherOfTheCustomHookReducer] 的自定义钩子。当事件发生时,Input 组件调用自定义钩子的调度器,然后根据自定义钩子中的reducer 返回的状态将样式应用到<input> 元素。
此外,由于 Form 组件需要知道整个表单是否有效,因此每个 Input 都有一个 onChangeValidity 属性用于提升 isValid 状态。 理论上,表单应该在开始时显得中性,然后,在您聚焦和模糊输入之后,它应该变为有效(蓝色背景)或无效(红色背景)。
我可能应该在提交后重置输入并添加其他内容,但现在我想让应用程序正常工作。目前状态永远不会改变,形式看起来总是中性(白色)。

您可能更喜欢查看 codesandbox 中的文件。

App.js

import Form from './components/Form';

function App() {
  return (
    <div className="app">
      <Form />
    </div>
  );
}

export default App;

Form.js

import { useReducer } from 'react';

import Input from './Input';

// put your inputs' ID here to generate the default state
const defaultState = (inputs = ['username', 'email']) => {
  let inputsState = {};
  for (const input of inputs) inputsState[input] = false;
  return { ...inputsState, isValid: false };
};

const formReducer = (state, action) => {

  let newInputsStateList = {...state, [action.id]: action.isValid};
  delete newInputsStateList.isValid;

  let isValid = true;
  for(const key in newInputsStateList) {
    if(!newInputsStateList[key]) isValid = false;
    break;
  }

  return { ...newInputsStateList, isValid};
}

const Form = props => {

  const [formState, dispatchFormState] = useReducer(formReducer, undefined, defaultState);

  const submitHandler = event => {
    event.preventDefault();
    console.log('You are logged in.');
  }

  return <form onSubmit={submitHandler}>

    <Input
      id='username'
      label='Username'
      type='text'
      test={username => username.trim().length > 6}
      onChangeValidity={validity => dispatchFormState({id: 'username', isValid: validity})}
    />

    <Input
      id='email'
      label='Email'
      type='email'
      test={email => email.includes('@')}
      onChangeValidity={validity => dispatchFormState({id: 'email', isValid: validity})}
    />

    <button type='submit' disabled={!formState.isValid} >Submit</button>
  </form>
};

export default Form;

输入.js

import { useEffect } from 'react';

import classes from './Input.module.css';
import useValidation from '../hooks/use-validation';

const Input = props => {

  const [isTouched, isValid, checkValidity] = useValidation();

  // eslint-disable-next-line
  useEffect(() => props.onChangeValidity(isValid), [isValid]);

  return <div className={classes.generic_input}>
    <label className={classes['generic_input-label']} htmlFor={props.id} >{props.label}</label>
    <input
      className={classes[`${isTouched ? 'generic_input-input--'+isValid ? 'valid' : 'invalid' : ''}`]}
      type={props.type}
      name={props.id}
      id={props.id}
      onChange={event => checkValidity({
        type: 'CHANGE',
        value: event.target.value,
        test: props.test
      })}
      onBlur={event => checkValidity({
        type: 'BLUR',
        value: event.target.value,
        test: props.test
      })}
    />
  </div>
};

export default Input;

使用-validation.js

import { useReducer } from 'react';

const validationReducer = (state, action) => {

  let isTouched = state.isTouched;
  let isValid = state.isValid;

  if(action.type === 'CHANGE') if (isTouched) isValid = action.test(action.value);

  else if(action.type === 'BLUR') {

    isValid = action.test(action.value);

    if (!isTouched) isTouched = true;
  }

  else isTouched = isValid = false;

  return {isTouched, isValid};
}

const useValidation = () => {

  const [validationState, dispatchValidation] = useReducer(validationReducer, {isTouched: false, isValid: false});

  return [validationState.isTouched, validationState.isValid, dispatchValidation];
};

export default useValidation;

Input.module.css

.generic_input {
  display: flex;
  flex-direction: column;
  padding: 1rem;
}

.generic_input-label {
  font-weight: bold;
}

.generic_input-input--valid {
  background-color: lightblue;
}

.generic_input-input--invalid {
  border-color: red;
  background-color: rgb(250, 195, 187);
}

.submit:disabled {
  background-color: #CCC;
  color: #292929;
  border-color: #CCC;
  cursor: not-allowed;
}

1 个答案:

答案 0 :(得分:2)

我认为您需要修复 isTouched 中的 validationReducer 逻辑。 isTouched 永远不会设置为 true

类似于:

const validationReducer = (state, action) => {
  let isTouched = state.isTouched;
  let isValid = state.isValid;

  if (action.type === "CHANGE") {
    isTouched = true;
    isValid = action.test(action.value)
  } else if (action.type === "BLUR") {
    isValid = action.test(action.value);
  } else {
    isTouched = isValid = false;
  }

  return { isTouched, isValid };
};

...虽然我不确定您何时希望将 isTouched 再次设置为 false,因此该逻辑需要一些工作...

此外,您输入的类不正确。

它应该看起来像:

  <input
     className={
       classes[
         isTouched
           ? `generic_input-input--${isValid ? "valid" : "invalid"}`
           : ""
       ]
     }
     ...
   >

看看this sandbox

相关问题