Skip to content
Advertisement

Can’t persist checkstate in reactjs and nodejs

This would be my first time of doing this and I can’t seem to find a way around it. I would like to persist check box state in react.js and would like this to be done without my MongoDB database. Here are my codes so far: I am fetching list of subscribers from my MongoDB database like this:

   const [allSubscribers, setAllSubscribers] = useState([]);

const response = await axiosPrivate.get(`${BASE_URL}/emailsub/subscribers?page=${pageNumber})
 setAllSubscribers(response.data)

This displays 9 subscribers per page. On next page, a new API call is made and another 9 subscribers listed, until the last set of items. That is how I handled the pagination via query.

To create the input checkbox, I had to create another array based on the size of the subscribers fetched from the database.

 const [checkedState, setCheckedState] = useState();
 const totalPosts = allSubscribers.length // to get the length of the items fetched from database
 const fillArray = new Array(totalPosts).fill( false)//created new array and fill it with initial value of `false`

//useEffecct to set the check state whenever the all subscriber state changes
useEffect(()=>{
setCheckedState(fillArray)
}, [allSubscribers])

When the checkbox is clicked it returns the opposite of the value of the matched item. And the subscriber Id is passed into an array which is a state called const [selectedSubscriberId, setSelectedSubscriberId] = useState([]);

 const arrayOfSelectedPostId = (subscriberId, indexPosition) =>{

   setSelectedSubscriberId(prevArray => [...prevArray, subscriberId]);
   const updatedCheckedState = checkedState.map((item, index) =>
      index == indexPosition ? !item : item
   )
    setCheckedState(updatedCheckedState);
 }

When unchecked, I removed the matched subscriberId from the selectedSubscriberId array.

   //handle deselecting of a selected postid
    const handleChangeState = (subscriberId)=>{
     selectedSubscriberId.map((item)=>{
       console.log(item === subscriberId)
       if(item === subscriberId){
           const newArray = selectedSubscriberId.filter((item) => item !==subscriberId)
         
         
           setSelectedSubscriberId(newArray);
           
       }
   })
   };

This is the checkbox input:

 <input type="checkbox"  id={index} checked={checkedState[index]} onChange={()=>{arrayOfSelectedPostId(subscriberId, index); handleChangeState(subscriberId)}}/>

On page load or refresh, I want to check the selectedSubscriberId array and any subscriber id found there should remain checked. Is there a way I can handle this? I don’t mind reworking the code if possible.

Advertisement

Answer

I think you overcomplicated your state a bit. You could achieve checkboxes state handling using 1 array that holds only ids (for example) of checked items. And 2 utility functions, 1 to check if item is checked and 1 to actually add or remove items from this array of ids.

Next – you would need 2 useEffects. One useEffect will load checked ids array from localstorage on first component render, and 2nd one will update localstorage when array of ids is updated.

import { useState, useEffect, useCallback } from "react";

const DATA = [
  { id: 1, name: "sub1" },
  { id: 2, name: "sub2" },
  { id: 3, name: "sub3" },
  { id: 4, name: "sub4" },
  { id: 5, name: "sub5" }
];

const CHECKED_SUBSCRIBERS_IDS_KEY = "checked_subscribers_ids";
const ITEMS_PER_PAGE = 2;

export default function App() {
  const [allSubscribers, setAllSubscribers] = useState([]);
  const [page, setPage] = useState(0);
  // initial state for it is "undefined"!
  const [checkedSubscribersIds, setCheckedSubscribersIds] = useState();

  // initial load of checked items from localstorage
  useEffect(() => {
    const json = localStorage.getItem(CHECKED_SUBSCRIBERS_IDS_KEY);
    setCheckedSubscribersIds(json ? JSON.parse(json) : []);
  }, []);

  // update localstorage on checked items changed.
  useEffect(() => {
    // initial load needs to be ignored
    if (!checkedSubscribersIds) return;
    const json = JSON.stringify(checkedSubscribersIds);
    localStorage.setItem(CHECKED_SUBSCRIBERS_IDS_KEY, json);
  }, [checkedSubscribersIds]);

  // dummy data set and pagination, ignore
  useEffect(() => {
    const items = [...DATA].slice(
      page * ITEMS_PER_PAGE,
      page * ITEMS_PER_PAGE + ITEMS_PER_PAGE
    );
    setAllSubscribers(items);
  }, [page]);
  
  const isChecked = useCallback(
    (item) => {
      if (!checkedSubscribersIds) return false;
      return checkedSubscribersIds.includes(item.id);
    },
    [checkedSubscribersIds]
  );

  const handleCheckedClick = useCallback(
    (item) => {
      const idx = checkedSubscribersIds.findIndex((x) => x === item.id);
      if (idx >= 0) {
        checkedSubscribersIds.splice(idx, 1);
      } else {
        checkedSubscribersIds.push(item.id);
      }
      setCheckedSubscribersIds([...checkedSubscribersIds]);
    },
    [checkedSubscribersIds]
  );

  // ignore
  const onPrevPageClick = () => {
    setPage(page <= 0 ? 0 : page - 1);
  };
  // ignore
  const onNextPageClick = () => {
    setPage(page + 1 >= DATA.length / ITEMS_PER_PAGE ? page : page + 1);
  };

  return (
    <div className="App">
      <table>
        <thead>
          <tr>
            <th>Checked</th>
            <th>Id</th>
            <th>Name</th>
          </tr>
        </thead>
        <tbody>
          {allSubscribers.map((x) => (
            <tr key={x.id}>
              <td>
                <input
                  type="checkbox"
                  checked={isChecked(x)}
                  onChange={() => handleCheckedClick(x)}
                />
              </td>
              <td>{x.id}</td>
              <td>{x.name}</td>
            </tr>
          ))}
        </tbody>
      </table>
      <button type="button" onClick={onPrevPageClick}>
        Previous
      </button>
      <button type="button" onClick={onNextPageClick}>
        Next
      </button>
    </div>
  );
}

So basically with my code you added an array that stores ids of items that are “checked”. This array is called checkedSubscribersIds. Next it comes to the handleCheckedClick function declaration, i wrapped it in useCallback just because I used to do that for any function, in your case it is not really needed but still it will be better if you will be aware of useCallback and you should read the react docs about it. It is about performance basically. There is a place to improve it anyway but I dont want to overcomplicate my answer. And the idea is pretty simple, you click on checkbox – handleCheckedClick function is called with item (x) parameter, inside of it you are checking if the id exists in the checkedSubscribersIds and if it does exist – you are removing it from this array. If not – adding. And the final is the setCheckedSubscribersIds([...checkedSubscribersIds]); which is cloning the array and setting the new reference which is the only way to make react understand that the array changed and we need a rerender. Doing so – 2 things are happening. 1) rerender is called which reevaluates the expression inside of <input>checked={isChecked(x)}. 2) useEffect is triggered due to it has [checkedSubscribersIds] as a dependency array, which will serialize this array to json and store it in localStorage.

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement