1
\$\begingroup\$

I have an input hook that is reusable in other input fields. These input fields have onChange functions that are dispatched by redux.

My question is, is the following code considered a react hook?

handleHook.tsx

import React, { useState } from "react";

export const InputHook = (props) => {
    const handleInputChange = (event) => {
        if (event.target.name === "title") {
            props.addTitle(event.target.value);
        }
        if (event.target.name === "postContent") {
            props.addContent(event.target.value);
        }
        if (event.target.name === "username") {
            props.addUsername(event.target.value);
        }
        if (event.target.name === "email") {
            props.addEmail(event.target.value);
        }
        if (event.target.name === "password") {
            props.addPassword(event.target.value);
        }
        if (event.target.name === "passwordConf") {
            props.addPasswordConf(event.target.value);
        } else {
            return null;
        }
    };
    return {
        handleInputChange,
    };
};

And it is being used here, for example:

register.tsx

import Typography from "@material-ui/core/Typography";
import React, { Component, Fragment } from "react";
import SignUpForm from "../forms/signUp/signUp";
import GridHoc from "../hoc/grid";
import IsAuth from "../hoc/isAuthenticated";
import { InputHook } from "./../common/handleHook";
export interface registerProps {
    onChange: (event: any) => void;
    signUpInit: (event: object, history: object) => void;
    addUsername: (event: object) => void;
    addEmail: (event: object) => void;
    addPassword: (event: object) => void;
    addPasswordConf: (event: object) => void;
    user?: any;
    history?: any;
}
export interface registerState {
    passwordConf: string;
    passErr: string;
}
function Register(registerProps: any) {
    const { handleInputChange } = InputHook(registerProps);
    const handleSubmit = (e: any) => {
        e.preventDefault();
        const { username, email, password, passwordConf } = registerProps.user;
        const creds = {
            username,
            email,
            password,
        };
        console.log(creds);
        registerProps.signUpInit(creds, registerProps.history);
    };
    const { username, email, password, passwordConf, passwordConfError, usernameError, passwordError, emailError } = registerProps.user;

    const isEnabled = passwordConfError === true && emailError === true && passwordError === true && usernameError === true ? false : true;
    return (
        <Fragment>
            <Typography variant="h4" style={{ letterSpacing: "2px" }}>
                Register
            </Typography>
            {registerProps.user.error && <div>{registerProps.user.error}</div>}
            <SignUpForm
                submit={handleSubmit}
                usernameChange={handleInputChange}
                emailChange={handleInputChange}
                passwordChange={handleInputChange}
                passwordConfChange={handleInputChange}
                username={username}
                password={password}
                passwordConf={passwordConf}
                email={email}
                usernameError={usernameError}
                passwordError={passwordError}
                passwordConfError={passwordConfError}
                emailError={emailError}
                disButton={isEnabled}
            />
        </Fragment>
    );
}

export default GridHoc(IsAuth(Register));
\$\endgroup\$
2
\$\begingroup\$

I agree with the other answer from @jkettmann that you should abstract this. Generally speaking having a long list of if-else conditions usually has a better alternative.

That could be a reducer like mentioned, or could just be an object map like:

const handlers = {
  title: addTitle,
  content: addContent,
  username: addUsername,
  email: addEmail,
  password: addPassword,
  passwordConf: addPasswordConf
}
const { name } = event.target;
if (!handlers.hasOwnProperty(name)) {
  return null;
}
return handlers[name](event.target.value);

I find it strange to be using the same handler to be calling different update methods (eg: addTitle). Forms are the ideal use case for a managing the state together in a reducer.

You're not even using it properly inside register.tsx anyway:

usernameChange={handleInputChange}
emailChange={handleInputChange}
passwordChange={handleInputChange}
passwordConfChange={handleInputChange}

You could use a form management hook/reducer library, but using custom reducers is extremely easy with React hooks:

const reducer = (state, event) => {
  return {
    ...state,
    [event.target.name]: event.target.value,
  }
};
const [state, dispatch] = React.useReducer(reducer, {
  title: '',
  content: '',
  username: '',
  email: '',
  password: '',
  passwordConf: ''
});
...
<Register
  handleInputChange={dispatch}
  user={state}
...
...
<SignUpForm
  submit={handleSubmit}
  usernameChange={handleInputChange}

I'll try and answer your actual question as well:

is the following code considered a react hook ?

Your InputHook is structured like a hook but there's a few details that make it not 100% so.

The React docs say hooks are defined by this definition:

A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks. Source

Your hook both does not have use in front and doesn't call any other hooks. So really it's just a function imitating a hook.

For example, this would become a hook if you used the useCallback hook inside your custom hook: (and made sure to name it with use)

export default function useInputChange(props) {
  const {
    addTitle,
    addContent,
    addUsername,
    addEmail,
    addPassword,
    addPasswordConf
  } = props;
  return React.useCallback((event) => {
    if (event.target.name === "title") {
      addTitle(event.target.value);
    }
    if (event.target.name === "postContent") {
      addContent(event.target.value);
    }
    if (event.target.name === "username") {
      addUsername(event.target.value);
    }
    if (event.target.name === "email") {
      addEmail(event.target.value);
    }
    if (event.target.name === "password") {
      addPassword(event.target.value);
    }
    if (event.target.name === "passwordConf") {
      addPasswordConf(event.target.value);
    } else {
      return null;
    }
  }, [addTitle, addContent, addUsername, addEmail, addPassword, addPasswordConf]);
}
\$\endgroup\$
1
  • \$\begingroup\$ thank you for this, appreciate the explanation, and refactor. \$\endgroup\$
    – BARNOWL
    Apr 5 '20 at 4:00
2
\$\begingroup\$

It's a good idea to abstract this away. Otherwise you would end up with a lot of different callbacks. But I think your approach might be improved.

First: why do you use different actions instead of simply handling the different events and input names inside a reducer? Now you have conditionals inside the hook and probably inside the reducer for the different actions as well, right? Moving everything to the reducer might simplify things.

Having form state in the global redux state is btw not so great. It means that all your connected components might re-render on every input change. There are great solutions for handling forms in React like formik or react-form-hook. Latter gets a lot of traction currently

\$\endgroup\$
1
  • \$\begingroup\$ will look into your recommendations, but will leave this unanswered as this doesn't provide a code refactor. \$\endgroup\$
    – BARNOWL
    Mar 12 '20 at 16:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.