Passing form input to Redux via using Hooks

Passing form input to Redux via using Hooks
0

I have converted class component to functional component and I am stuck. I tried everything to pass the value (user id and password) to Redux.

When I console log ‘inputs’, I see [object Object] but, login and password show exact name and value.

How can I pass name and value to inputs in this case?

function Login(props) {
  const classes = useStyles();
  const [inputs, setInputs] = useState({
    // you can login with email or username
    login: '',
    password: '',
    keepLogged: false
  });

  function handleChange(e) {
    e.preventDefault();
    const { name, value } = e.target;
    setInputs({ ...inputs, [name]: value });
    console.log(`${inputs}`)
  }

  const loginUser = () => {
    const { requestSignIn } = props;
    requestSignIn({ inputs });
    console.log(`inputs; ${inputs}`)
  };

  return (
    <Grid container component='main' className={classes.root}>
      <CssBaseline />
      <Grid item xs={false} sm={4} md={7} className={classes.image} />
      <Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
        <div className={classes.paper}>
          <div>
            <img src={Logo} style={{ width: 300 }} />
          </div>
          <Typography component='h1' variant='h5'>
            Sign in
          </Typography>
          <form className={classes.form} noValidate>
            <TextField
              variant='outlined'
              margin='normal'
              required
              fullWidth
              id='email'
              label='Email Address'
              value={inputs.login}
              onChange={e => handleChange(e)}
              name='login'
              autoComplete='email'
              autoFocus
            />
            <TextField
              variant='outlined'
              margin='normal'
              required
              fullWidth
              name='password'
              label='Password'
              value={inputs.password}
              onChange={e => handleChange(e)}
              type='password'
              id='password'
              autoComplete='current-password'
            />
            <FormControlLabel
              control={<Checkbox value='remember' color='primary' />}
              label='Remember me'
            />
            <Button
              type='submit'
              fullWidth
              variant='contained'
              color='primary'
              value='Log In'
              className={classes.submit}
              onClick={() => loginUser()}
            >
              Sign In 🙂
            </Button>
            <Grid container>
              <Grid item xs>
                <Link href='#' variant='body2'>
                  Forgot password?
                </Link>
              </Grid>
              <Grid item>
                <Link
                  variant='body2'
                  onClick={() => history.push('/registration')}
                >
                  {"Don't have an account? Sign Up"}
                </Link>
              </Grid>
            </Grid>
            <Box mt={5}>
              <Copyright />
            </Box>
          </form>
        </div>
      </Grid>
    </Grid>
  );
}

const mapDispatchToProps = dispatch => ({
  requestSignIn: data => dispatch(requestSignIn(data))
});

export default connect(null, mapDispatchToProps)(Login);

The dispatch is a side effect, so use React.useEffect.

useEffect runs every time the component rerenders (ie every time the state changes).


[Following is not essential, but makes things simpler]: for simplicity you ideally do not want the state to be a single object. So you define like

const [login, setLogin] = useState("");
const [password, setPassword] = useState("");
const [keepLoggedIn, setKeepLoggedIn] = useState(false);

Anyway, I would advise always doing that rather than using objects unless strictly necessary. Keep things as flat as possible, avoid nesting things inside objects if you can avoid it. If you do need complex state, think about using useReducer instead of individual useState values (though this will make you’re code a little more complex).


After that, you also need a value that you can set if the user has clicked submit so that the component rerenders if the user tries to log in. At that point, you can dispatch the other values from the state.

const [isLoggingIn, setIsLoggingIn] = useState(false);

You can write handlers for all of these, but as none of the logic is complex, you can just put them inline

 value={login}
onChange={e => setLogin(e.target.value)}
value={password}
onChange={e => setPassword(e.target.value)}
value={keepLoggedIn}
onChange={e => setKeepLoggedIn(!keepLoggedIn)}

Submit button doesn’t have a value – it’s triggering a side effect (dispatching to the store)

<button type="submit" onClick={() => setIsLoggingIn(true)}>

Now your state updates, but it doesn’t actually do anything. Make it do something:

useEffect(() => {
  console.log(`Login: ${login}, Password: ${password}, Keep logged in: ${keepLoggedIn}`);
  // Only dispatch is `isLoggingIn` is true
  if (isLoggingIn) {
    props.requestSignIn({ name: login, password });
    // Once we've dispatched, not logging in any more,
    // so set `isLoggingIn` back to false
    setIsLoggingIn(false);
  }
// Note the list of values that your useEffect hook should
// react to here -- this is essential to avoid bugs
}, [login, password, keepLoggedIn, isLoggingIn]);

Also, you don’t need that connect call. react-redux provides hooks, and they’re much easier and cleaner

import { useDispatch } from "react-redux";

// ... some code

function Login(props) {
  const dispatch = useDispatch();
  // ...some code

  useEffect(() => {
    console.log(`Login: ${login}, Password: ${password}, Keep logged in: ${keepLoggedIn}`);

  if (isLoggingIn) {
    dispatch(requestSignIn({ name: login, password }));
      // Once we've dispatched, not logging in any more,
      // so set `isLoggingIn` back to false
      setIsLoggingIn(false);
    }
  }, [login, password, keepLoggedIn, isLoggingIn]);

  // ...rest of code

Here is a full example – I don’t know the API for the UI library you’ve used, so I’ve just used plain HTML elements instead

Note that it clears the form after dispatching by accident – I haven’t build any logic to handle that, and it does need to be handled, the pretend reducer is the thing that’s doing it afaics, so it should work ok with your actual reducer. Anyway, you can look in the console and see the state updating every time you type something, then the pretend dispatch going to the store

1 Like

Hi! First of all, thank you for your answer. I am super lost. But, I will read carefully and follow the post.

I was using class component and switching to functional in order to use Material-UI theme (i dunno why it doesn’t work on class component) and also taking it as a learning experience.

Yep, sorry if it’s a little bit confusing, I must have written something basically the same as this code tens (if not hundreds) of times so I kinda went through it quickly

Yep, I get that – it’ll be a bit hard to grok at first, but it should be easier in the long run. useEffect is the important thing to understand when you’re moving to hooks (it’s what replaces basically all the lifecycle methods), and unfortunately it’s also the thing that’s kinda hardest to grok

1 Like

No! It is just me. I read that useEffect was the replacement for componentDidMount() and willMount() such thing. I did not know if it could have used this way.

There are always something to learn!

1 Like

I am sorry to bother ya again. But, this is the class component that I wanted to convert to functional. I tried to apply to your solution and again I am stuck. So, keepLogged does nothing. I think it was there for future reference.

I changed [inputs, setInputs] to 3 different inputs. However, it has not converted to requestSignIn :confused:

class Login extends Component {
  state = {
    login: '',
    password: '',
    keepLogged: false
  };

  changeValue = event => {
    event.preventDefault();
    const value = event.target.value;
    const name = event.target.name;

    this.setState({
      [name]: value
    });
  };

  checkedValue = event => {
    console.log(event.target.checked, event.target.name);
    const value = event.target.checked;
    const name = event.target.name;

    this.setState({
      [name]: value
    });
  };

  loginUser = () => {
    const { login, password } = this.state;
    const { requestSignIn } = this.props;

    requestSignIn({ login, password });
  };

  render() {
    const { login, password } = this.state;

    return (
      <LoginWrapper>
        <Container style={{ border: '1px solid #757575', padding: '5%' }}>
          <Row>
            <Col style={{ textAlign: 'right', marginRight: '25px' }}>
            </Col>
          </Row>
          <Row>
            <Col>
              <LoginHeader>Log In to Your Account</LoginHeader>
            </Col>
          </Row>
          <Row>
            <Col>
              <LoginForm>
                <FormControl
                  name='login'
                  type='text'
                  placeholder='Username/Email'
                  value={login}
                  onChange={this.changeValue}
                  style={{ marginBottom: '10px' }}
                />
                <FormControl
                  name='password'
                  type='password'
                  placeholder='Password'
                  value={password}
                  onChange={this.changeValue}
                  style={{ marginBottom: '10px' }}
                />
                <Button
                  variant='info'
                  value='Log In'
                  onClick={() => this.loginUser()}
                >
                  Log In
                </Button>
              </LoginForm>
            </Col>
          </Row>
          <Row>
            <Col>
              <LoginBottom
                onClick={() => history.push('/registration')}
                style={{ marginTop: '30px' }}
              >
                Need an account?{' '}
                <Link style={{ color: '#007bff' }}>Sign Up</Link>
              </LoginBottom>
            </Col>
          </Row>
        </Container>
      </LoginWrapper>
    );
  }
}

const mapDispatchToProps = dispatch => ({
  requestSignIn: data => dispatch(requestSignIn(data))
});

export default connect(null, mapDispatchToProps)(Login);
function Login() {

  const classes = useStyles();
  const [login, setLogin] = useState('')
  const [password, setPassword] = useState('')
  const [keepLogged, setKeepLogged] = useState(false)

  function handleChangeLogin(e) {
    e.preventDefault();
    const { name, value } = e.target;
    setLogin({ ...login, [name]: value });
    console.log(`${login}`)
  }

  function handleChangePass(e) {
    e.preventDefault();
    const { name, value } = e.target;
    setPassword({ ...password, [name]: value });
    console.log(`${password}`)
  }

  const loginUser = () => {
    const { login, password } = this.state;
    const { requestSignIn } = this.props;

    requestSignIn({ login, password });
    console.log(`inputs; ${password}`)
  };

  return (
    <Grid container component='main' className={classes.root}>
      <CssBaseline />
      <Grid item xs={false} sm={4} md={7} className={classes.image} />
      <Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
        <div className={classes.paper}>
          <div>
            <img src={Logo} style={{ width: 300 }} />
          </div>
          <Typography component='h1' variant='h5'>
            Sign in
          </Typography>
          <form className={classes.form} noValidate>
            <TextField
              variant='outlined'
              margin='normal'
              required
              fullWidth
              id='email'
              label='Email Address'
              value={login}
              onChange={e => handleChangeLogin(e)}
              name='login'
              autoComplete='email'
              autoFocus
            />
            <TextField
              variant='outlined'
              margin='normal'
              required
              fullWidth
              name='password'
              label='Password'
              value={password}
              onChange={e => handleChangePass(e)}
              type='password'
              id='password'
              autoComplete='current-password'
            />
            <FormControlLabel
              control={<Checkbox value='remember' color='primary' />}
              label='Remember me'
            />
            <Button
              type='submit'
              fullWidth
              variant='contained'
              color='primary'
              value='Log In'
              className={classes.submit}
              onClick={() => loginUser()}
            >
              Sign In 🙂
            </Button>
            <Grid container>
              <Grid item xs>
                <Link href='#' variant='body2'>
                  Forgot password?
                </Link>
              </Grid>
              <Grid item>
                <Link
                  variant='body2'
                  onClick={() => history.push('/registration')}
                >
                  {"Don't have an account? Sign Up"}
                </Link>
              </Grid>
            </Grid>
            <Box mt={5}>
              <Copyright />
            </Box>
          </form>
        </div>
      </Grid>
    </Grid>
  );
}

const mapDispatchToProps = dispatch => ({
  requestSignIn: data => dispatch(requestSignIn(data))
});

export default connect(null, mapDispatchToProps)(Login);