简体   繁体   中英

React.js states won't change

I have a simple React app that I am going to post entirely (it is not that long). The app doesn't work but doesn't throw any error either. I tried to log the states and it turns out they never change. I am using big things like custom hook and useReducer, but I suspect I lack on understanding basic principles of how react works.

Here's a short summary of how the app should work:
There is a Form component which returns a series of custom Input elements (here only two).
The Input component outsources the validation logic to a custom hook which returns [isTouched, isValid, dispatcherOfTheCustomHookReducer] . When an event occurs, the Input component calls the dispatcher of the custom hook and then styles should be applied to the <input> element based on the state returned by the reducer in the custom hook.
Also since the Form component needs to know if the form as a whole is valid, each Input has an onChangeValidity property used to lift up the isValid state. In theory the form should appear neutral at the beginning and then, after you focus and blur an input this should become either valid (blue background) or invalid (red background).
I should probably reset the inputs after submission and add something else, but for now I want to make the app work. At the moment the states never changes and the forms appears always neutral (white).

You may prefer look at the files in 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;

Input.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;

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

I think you need to fix the isTouched logic in your validationReducer . isTouched never gets set to true :

Something like:

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

... though I'm not sure when you'd want isTouched to be set to false again, so that logic needs some work...

Also, the class on your input is not correct.

Its should look like:

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

Take a look at this sandbox

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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