checkboxreact-hook-form

How can I set checkbox values of dynamic array from server using React Hook Forms Field Array


My server returns an object that looks like this:

{
  "success": true,
  "user_groups": {
    "user_id": 6,
    "username": "sbosell",
    "first_name": "Steve",
    "last_name": "Bosell",
    "email": "sbosell@aol.com",
    "is_staff": false,
    "is_active": true,
    "is_superuser": false,
    "groups": [
      { "id": 1, "name": "Admin", "value": false },
      { "id": 3, "name": "Managers", "value": false },
      { "id": 4, "name": "Users", "value": true },
      { "id": 5, "name": "Stakeholders", "value": false }
    ]
  }
}

My needs are:

  1. show all the fields as editable in a form (all but the groups currently show up)
  2. show a row for each group with a checkbox indicating the current value
  3. it must work dynamically, as new groups may be created or existing ones destroyed.

Here is the code I have so far (which is incomplete, but I am focusing on the display part at the moment).. Note the commented out lines of thing's I tried in the past but did not work.

My current goals is to get the checkboxes showing the proper state (in this particular use case the first two and the last boxes should be not checked, but the User group one should be. What is actually happening however is that all the checkboxes are always checked no matter what I try.

Notes: I am not using any UI toolkit, just plain old HTML.

Advice?

import React, { useState, useEffect } from "react";
import { useForm, useFieldArray } from "react-hook-form";
import { ErrorMessage } from "@hookform/error-message";
import { RootStateOrAny, useSelector, useDispatch } from "react-redux";
import { Link, Outlet, useParams, useNavigate } from "react-router-dom";
import {
  userGetGroupsAction,
  userUpdateRequestAction,
} from "../../actions/adminActions";

interface FormInputs {
  email: string;
  firstName: string;
  lastName: string;
  username: string;
  isActive: boolean;
  isStaff: boolean;
  isSuperuser: boolean;
  group: {
    groupId: any;
    name: any;
    value: boolean;
  }[];
}

export default function User() {
  const {
    register,
    control,
    formState: { errors },
    handleSubmit,
    setValue,
  } = useForm<FormInputs>();
  const users = useSelector((state: RootStateOrAny) => state.admin.users);
  const subject = useSelector(
    (state: RootStateOrAny) => state.admin.user_groups,
  );

  const { mode, user_id } = useParams();

  const navigate = useNavigate();
  const dispatch = useDispatch();

  const exitModal = () => {
    navigate("/admin/user");
  };

  const { fields, append, remove } = useFieldArray({
    name: "group",
    control,
  });

  useEffect(() => {
    if (user_id) {
      dispatch(userGetGroupsAction(user_id));
    }
  }, [user_id]);

  useEffect(() => {
    if (subject) {
      setValue("username", subject.username);
      setValue("firstName", subject.first_name);
      setValue("lastName", subject.last_name);
      setValue("email", subject.email);
      setValue("isActive", subject.is_active);
      setValue("isStaff", subject.is_staff);
      setValue("isSuperuser", subject.is_superuser);

      remove();

      subject.groups.map((group: any, index: number) => {
        append({
          groupId: group.id,
          name: group.name,
          value: group.value,
        });
        //console.log('derp' + group.value);
        //setValue(`group.${index}.value`, group.value);

        /*
            setValue("group", [{
              groupId: group.id,
              name: group.name,
              value: group.value
            }]);
            */

        //setValue("group", [index][group.value]);
      });
    }
  }, [subject]);

  const onSubmit = handleSubmit(async (data: FormInputs) => {
    dispatch(userUpdateRequestAction(data));
  });
  const [updateCount, forceUpdate] = useState(0);
  const clearError = (field: string) => {
    subject.response[field] = false;
    forceUpdate(updateCount + 1);
  };

  const userList = users.map((user: any) => (
    <tr key={user.id}>
      <td>{user.username}</td>
      <td>{user.first_name}</td>
      <td>{user.last_name}</td>
      <td>{user.email}</td>
      <td>
        <Link to={"/admin/user/edit/" + user.id}>Edit</Link>
      </td>
    </tr>
  ));

  return (
    <div>
      <h4 className="pageTitle">User Maintenance</h4>
      <table>
        <thead>
          <tr>
            <th>Username</th>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Email</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          {users && userList}
          {users.length === 0 && (
            <tr>
              <td colSpan={5}>There are no Users set up yet</td>
            </tr>
          )}
        </tbody>
      </table>
      {mode === "edit" && subject && (
        <>
          <div className="screenBlocker" onClick={exitModal}></div>
          <div className="modalLarge">
            <div className="modalHeader">
              Edit User: {subject.first_name} {subject.last_name}
              <span onClick={exitModal}>x</span>
            </div>
            <div className="modalContent">
              <form onSubmit={onSubmit}>
                <div className="halfLeft">
                  <div>
                    <label>Username</label>
                    <input
                      {...register("username", {
                        required: "Username is required",
                      })}
                      onClick={() => clearError("username")}
                    />
                    <ErrorMessage errors={errors} name="username" />
                    {subject.response && subject.response.username && (
                      <span>{subject.response.username[0]}</span>
                    )}
                  </div>
                  <div>
                    <label>First Name</label>
                    <input {...register("firstName")} />
                  </div>
                  <div>
                    <label>Last Name</label>
                    <input {...register("lastName")} />
                  </div>
                  <div>
                    <label>Email</label>
                    <input
                      className="wideInput1"
                      {...register("email", { required: "Email is required" })}
                    />
                    <ErrorMessage errors={errors} name="email" />
                    {subject.response && subject.response.email && (
                      <span>{subject.response.email[0]}</span>
                    )}
                  </div>
                </div>
                <div className="halfRight">
                  <label>Global Settings</label>
                  <div>
                    <input
                      className="checkbox"
                      type="checkbox"
                      {...register("isActive")}
                    />
                    <label className="checkboxLabel" htmlFor="isActive">
                      Active
                    </label>
                  </div>
                  <div>
                    <input
                      className="checkbox"
                      type="checkbox"
                      {...register("isStaff")}
                    />
                    <label className="checkboxLabel" htmlFor="isStaff">
                      Staff
                    </label>
                  </div>
                  <div>
                    <input
                      className="checkbox"
                      type="checkbox"
                      {...register("isSuperuser")}
                    />
                    <label className="checkboxLabel" htmlFor="isSuperuser">
                      Superuser
                    </label>
                  </div>
                </div>
                <div className="bottomHalf">
                  <label>User's Groups</label>
                  {fields.map((item, index) => (
                    <div key={item.id}>
                      <input
                        className="checkbox"
                        type="checkbox"
                        {...register(`group.${index}.name`)}
                      />
                      <label
                        className="checkboxLabel"
                        htmlFor={`group.${index}.name`}
                      >
                        {item.name}
                      </label>
                    </div>
                  ))}
                </div>
                <input type="submit" />
              </form>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

Solution

  • {fields.map((item, index) => (
                      <div key={item.id}>
                        <input className="checkbox" type="checkbox" {...register(`group.${index}.value`)} />
                        <label className="checkboxLabel" htmlFor={`group.${index}.value`}>{item.name}</label>
                      </div>
                    ))}

    This is your group data:

    "groups": [
           {"id": 1, "name": "Admin", "value": false}, 
           {"id": 3, "name": "Managers", "value": false}, 
           {"id": 4, "name": "Users", "value": true}, 
           {"id": 5, "name": "Stakeholders", "value": false}
         ]

    And this is a form representing that data:

    "groups": [
           {"id": 1, "name": "Admin", "value": false}, 
           {"id": 3, "name": "Managers", "value": false}, 
           {"id": 4, "name": "Users", "value": true}, 
           {"id": 5, "name": "Stakeholders", "value": false}
         ]
         
         
         field.map((item, index) => {
            return <div>
               <input type='text' name={`group.${index}.name`}/>
               <input type='check' name={`group.${index}.value`}/>
            <div/>
         })

    So, the inputs will always be represented and associated to their value, do not mix them.