Search code examples
javascriptreactjscheckboxreact-hooksuse-effect

React Jsx set checked state to false (reset button)


Here I'm trying to reset selected radio buttons on this list, however it doesn't work because I previously change input check from {checked} to {user.checked}. Refer from UserListElement.tsx below

Therefore, I tried the following two methods.

  1. in useEffect(), set user.userId = false

useEffect(() => {
    user.checked = false;
  }, [isReset, user]);

→ no change.

  1. setChecked to true when addedUserIds includes user.userId

 if (addedUserIds.includes(`${user.userId}`)) {
    setChecked(true);
  }

→ Unhandled Runtime Error Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

Any suggestion on how to make this this work?

UserListElement.tsx

export const UserListElement = ({
  user,
  handleOnMemberClicked,
  isReset,
}: {
  user: UserEntity;
  handleOnMemberClicked: (checked: boolean, userId: string | null) => void;
  isReset: boolean;
}) => {
  const [checked, setChecked] = useState(user.checked);
  const addedUserIds = addedUserList.map((item) => item.userId) || [];

  const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => {
    const checkedState = e.target.checked;
    setChecked(checkedState); //not called
    user.checked = checkedState;
    handleOnMemberClicked(checkedState, user.userId);
  };
  useEffect(() => {
    setChecked(false);
  }, [isReset, user]);

  if (addedUserIds.includes(`${user.userId}`)) {
    user.checked = true;
    // setChecked(true) cause runtime error (infinite loop)
  }

  return (
    <li>
      <label className={style.checkboxLabel}>
        <input
          type="checkbox"
          className={style.checkboxCircle}
          checked={user.checked}
          // checked={checked}
          onChange={(e) => handleOnChange(e)}
        />
        <span>{user.name}</span>
      </label>
    </li>
  );
};

UserList.tsx

export const UserList = (props: {
    showsUserList: boolean;handleClose: () => void;corporationId: string;currentUserId: string;samePerson: boolean;twj: string;
  }) => {
    const [isReset, setReset] = useState(false);
    .......
    const resetAll = () => {
      setReset(!isReset);
      setCount((addedUserList.length = 0));
      setAddedUserList([]);
      setUserName('');
    };
    ......
    return ( <
      > < div > xxxxx <
      ul className = {
        `option-module-list no-list option-module-list-member ${style.personListMember}`
      } > {searchedUserList.map((user, i) => ( 
      <UserListElement user = { user }
          handleOnMemberClicked = { handleOnMemberClicked }
          isReset = { isReset }
          key = {i} />
        )) }
        </ul> 
      /div>
    <a className="is-secondary reservation-popup-filter-reset" onClick={resetAll}>
      .....
    }

UseAddUserList.tsx

export class UserDetail {
  constructor(public userId: string | null, public name: string | null) {}
}

export let addedUserList: UserDetail[] = [];
export let setAddedUserList: Dispatch<SetStateAction<UserDetail[]>>;

export const useAddUserList = (idList: UserDetail[]) => {
  [addedUserList, setAddedUserList] = useState(idList);
};

Further Clarification:

Default view

enter image description here

Searched option (showed filtered list)

enter image description here

I use user.checked because when using only checked, the checked state does not carry on from filtered list view to the full view (ex. when I erase searched word or close the popup).


Solution

  • The real answer to this question is that the state should NOT be held within your component. The state of checkboxes should be held in UsersList and be passed in as a prop.

    export const UserListElement = ({
      user,
      handleOnMemberClicked,
      isChecked
    }: {
      user: UserEntity;
      handleOnMemberClicked: (checked: boolean, userId: string | null) => void;
      isChecked: boolean;
    }) => {
       // no complicated logic in here, just render the checkbox according to the `isChecked` prop, and call the handler when clicked
    }
    

    in users list

    
    return searchedUserList.map(user => (
       <UserListElement 
           user={user} 
           key={user.id} 
           isChecked={addedUserIds.includes(user.id)} <-- THIS LINE
           handleOnMemberClicked={handleOnMemberClicked}
        />
    )
    

    You can see that you almost had this figured out because you were doing this in the child:

      if (addedUserIds.includes(`${user.userId}`)) {
        user.checked = true;
        // setChecked(true) cause runtime error (infinite loop)
      }
    

    Which indicates to you that the checkdd value is entirely dependent on the state held in the parent, which means there is actually no state to be had in the child.

    Also, in React, NEVER mutate things (props or state) like - user.checked = true - that's a surefire way to leave you with a bug that will cost you a lot of time.

    Hopefully this sheds some light