我有一个简单的 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;
}
答案 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"}`
: ""
]
}
...
>