0

I would like to filter data based on pressing multiple checkbox buttons. Currently only the most recently pressed button works and shows the output instead of also showing outputs from other buttons which are pressed as well.

The state of checkbox buttons works correctly i.e. when clicked it is true, when unclicked it is false - however I am not sure how to connect it with my find function which fetches the data.

 const JobsList = (props) => {

  const pageNumber = props.pageNumber || 1;
  const [jobs, setJobs] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);
  const [page, setPage] = useState(pageNumber);
  const [pages, setPages] = useState(1);

  useEffect(() => {
    const fetchJobs = async () => {
      try {
        retrieveJobs();
        retrievePages();
        pages = retrievePages();
        setJobs(jobs);
        setLoading(false);
      } catch (error) {
        console.log(error);
        setLoading(false);
        setError("Some error occured");
      }
    };
    fetchJobs();
  }, [page]);

  const retrievePages = () => {
    JobDataService.getPage(pages)
      .then((response) => {
        setPages(response.data.totalPages);
      })
      .catch((e) => {
        console.log(e);
      });
  };
  const Checkbox = ({ type = "checkbox", name, checked = false, onChange }) => {
    return (
      <input
        type={type}
        name={name}
        checked={checked}
        onChange={onChange}
        className="btn--position"
      />
    );
  };
 //plain object as state
  const [checkedItems, setCheckedItems] = useState({}); //plain object as state
  const filteredItems = [];
  const handleChange = (event) => {
    // updating an object instead of a Map
    setCheckedItems({
      ...checkedItems,
      [event.target.name]: event.target.checked,
      filteredItems.
    });
    console.log("from HANDLECHANGE: ", checkedItems)
    // console.log(checkedItems[event.target.checked])
    // find(event.target.name)
  };

  useEffect(() => {
    console.log("checkedItems from UseEffect: ", checkedItems);
    // console.log(checkedItems)
    // find(checkedItems)
  }, [checkedItems]);
  const checkboxes = [
    {
      name: "🤵‍♀️ Finance",
      key: "financeKey",
      label: "financeLabel",
    },
    {
      name: "👩‍🎨 Marketing",
      key: "marketingKey",
      label: "marketingLabel",
    },
    {
      name: "👨‍💼 Sales",
      key: "salesKey",
      label: "salesLabel",
    },
    {
      name: "🥷 Operations",
      key: "operationsKey",
      label: "financeLabel",
    },
    {
      name: "👨‍💻 Software Engineering",
      key: "softwareEngineeringKey",
      label: "softwareEngineeringLabel",
    },
  ];

  const retrieveJobs = () => {
    JobDataService.getAll(page)
      .then((response) => {
        console.log(response.data);
        setJobs(response.data.jobs);
      })
      .catch((e) => {
        console.log(e);
      });
  };
  const refreshList = () => {
    retrieveJobs();
  };

  const find = (query, by) => {
    JobDataService.find(query, by)
      .then((response) => {
        console.log(response.data);
        setJobs(response.data.jobs);
        // setPage(response.data.total_results)
        setPages(response.data.totalPages);
      })
      .catch((e) => {
        console.log(e);
      });
  };

  return (
        <div className="hero-container">
          <div>
            <div className="allButtons-div">
              <div className="buttons-div">
                <div>
                  <label>
                    {checkedItems[""]}
                    {/* Checked item name : {checkedItems["check-box-1"]}{" "} */}
                  </label>
                  {checkboxes.map((item) => (
                    <label key={item.key}>
                      {item.name}
                      <Checkbox
                        name={item.name}
                        checked={checkedItems[item.name]}
                        onChange={handleChange}
                      />
                    </label>
                  ))}
                </div>
</div>
</div>
</div>
</div>
);

The function below fetches data from the MongoDB Realm database

  const find = (query, by) => {
    JobDataService.find(query, by)
      .then((response) => {
        setJobs(response.data.jobs);
        setPages(response.data.totalPages);
      })
      .catch((e) => {
        console.log(e);
      });
  };
3
  • Your code sample is pretty dirty - the code after Checkbox isn't in its own component, the function doesn't return anything (it just transitions straight into JSX), and most importantly your "find" function seems to use React hooks (see setJobs, setPages) but it's not clear where or how it's declared and how that declaration relates to the quasi-components you've shown us. A little bit of clarity will go a long way towards getting some help with this problem you're having! : ) Commented Jul 27, 2021 at 7:15
  • Sorry for that, I tried to cut only some parts of the code to make it short and in consequence made it unreadable. I just edited the code - please let me know if it's easier to understand. Commented Jul 27, 2021 at 7:52
  • Does this code run? I'd be really surprised, as there are a lot of problems with it. You're fetching data and setting state in multiple places, and have a lot of unused variables and perhaps too much state. I'm going to post an answer trying to clean some of this up, but I highly recommend reading this post about useEffect() a couple of times to try to get a sense of what some might call "thinking in React." Commented Jul 27, 2021 at 17:16

1 Answer 1

2

To answer your question, our find() function should be a lot like your retrieveJobs() and retrievePages() functions - they interact with the data layer of your app. That said, if all we're trying to do is filter the data we already have (let's say that retrieveJobs() and retrievePages() fetches all of the jobs and pages you'll need), then we don't need refetch the data based on what's checked in your UI - we simply need to use JavaScript to filter the results by using things you should already be familiar with like map(), sort(), reduce(), filter(), etc.

To go further, this code has a lot of problems. We're using state probably a little more than we should, we're setting state in multiple places redundantly, we're using useEffect() calls that don't do much, the list goes on. I've been there - trying to do things in a "React" way can sometimes result in the opposite effect, where you're lost in endless useState() and useEffect() calls and trying to figure out where to call what event handler and why. I've gone through and made some fairly obvious changes to your code to hopefully get you on the right track to understanding what's going on a little bit better going forward, but I highly recommend going through the React docs and reading this post by Dan Abramov until you understand it (I had to read and re-read a couple paragraphs in that article over and over before it clicked, but I think it will go a long way for you).

Here's the code, it likely still has a lot of problems but best of luck moving forward!

// Since this is a constant set of data, you don't need to include it in your component; remember
// that React components are just regular old functions, so having this constant array value in your
// component means that it's being created anew every render. Let's move it above the component.
const checkboxes = [
  {
    name: '🤵‍♀️ Finance',
    key: 'financeKey',
    label: 'financeLabel',
  },
  {
    name: '👩‍🎨 Marketing',
    key: 'marketingKey',
    label: 'marketingLabel',
  },
  {
    name: '👨‍💼 Sales',
    key: 'salesKey',
    label: 'salesLabel',
  },
  {
    name: '🥷 Operations',
    key: 'operationsKey',
    label: 'financeLabel',
  },
  {
    name: '👨‍💻 Software Engineering',
    key: 'softwareEngineeringKey',
    label: 'softwareEngineeringLabel',
  },
];

// the same principle applies with this smaller component. It doesn't use 
// state or props from JobsList, so we should move the component outside of 
// your JobsList component to make sure it's not created over and over again
// on each render; let's move it outside of JobsList
const Checkbox = ({ type = 'checkbox', name, checked = false, onChange }) => {
  return (
    <input
      type={type}
      name={name}
      checked={checked}
      onChange={onChange}
      className="btn--position"
    />
  );
};

// Since these functions seem to interact with the data layer of your app (depending on how JobDataService works of course),
// why don't we try making them functions that return a value from the data layer? Also, it looks like we're using async/await
// syntax in our useEffect call, why don't we try that here?
const retrievePages = async (pages) => {
  try {
    const response = await JobDataService.getPage(pages);
    return response;
  } catch (e) {
    console.log(e);
  }
};

// as an aside, I'm not sure of the difference between pages and page, but we'll keep this the same for now
const retrieveJobs = async (page) => {
  try {
    const response = await JobDataService.getAll(page);
    return response;
  } catch (e) {
    console.log(e);
  }
};

// to hopefully kind of answer your question, this find() function is a lot like the retrieveJobs and retrievePages functions above:
// it just interacts with your data layer - let's try and make it an async function and pull it out of the component so it can return
// results we need. As I explained above, though, if we grabbed all of our jobs and all of our pages already and just need to filter 
// the data, why do we need to make a network call for that? Surely we can just use JS functions like filter(), map(), sort(), and reduce()
// to filter the results into the structures that our app needs
const find = async (query, by) => {
  try {
    const response = await JobDataService.find(query, by);
    return response;
  } catch (e) {
    console.log(e);
  }
};

const JobsList = (props) => {
  const pageNumber = props.pageNumber || 1;
  const [jobs, setJobs] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);
  // if pageNumber is passed as a prop, why do we need to store it in state? Presumably the parent component
  // of <JobsList /> will handle keeping track of pageNumber, which is why we pass data as props. Let's comment
  // out this useState call
  // const [page, setPage] = useState(pageNumber);
  const [pages, setPages] = useState(1);

  useEffect(() => {
    const fetchJobs = async () => {
      try {
        const jobsData = await retrieveJobs(props.page);
        const pageData = await retrievePages(pages);
        setJobs(jobsData);
        setPages(pageData);
        // why do we call retrievePages() twice? also, you've decided to store pages in state, so we'll want to use setPages
        // for this instead of a normal assignment. let's comment out this assignment
        // pages = retrievePages();
        setLoading(false);
      } catch (error) {
        console.log(error);
        setLoading(false);
        setError('Some error occured');
      }
    };
    fetchJobs();
  }, [props.page, pages]);

  const [checkedItems, setCheckedItems] = useState({});
  // this is where we could do things like filter based on the checked items instead of making another network call; we have all of our data,
  // we just need to do stuff with it (this is contrived but hopfully you get the idea) - every time React re-renders the JobsList component based on a new set of state or props (think something gets checked or unchecked),
// we'll just filter the data we've already fetched based on that new reality
  const filteredJobs = jobs.filter((job) => job.id === checkedItems[job.id]);
  const filteredPages = pages.filter((page) => page.id === checkedItems[page.id]);
  const handleChange = (event) => {
    // updating an object instead of a Map
    setCheckedItems({
      ...checkedItems,
      [event.target.name]: event.target.checked,
      // not sure what this is, perhaps a typo; let's comment it out
      // filteredItems.
    });
    // this find call needs two arguments, no? let's comment it out for now
    // find(event.target.name)
  };

  // not sure of the purpose behind this second useEffect call, let's comment it out
  // useEffect(() => {
  //   console.log("checkedItems from UseEffect: ", checkedItems);
  //   // console.log(checkedItems)
  //   // find(checkedItems)
  // }, [checkedItems]);

  // we'll ignore this for now as well and comment it out, we should probably be refreshing our data based on state or prop updates
  // const refreshList = () => {
  //   retrieveJobs();
  // };

  return (
    <div className="hero-container">
      <div>
        <div className="allButtons-div">
          <div className="buttons-div">
            <div>
              <label>
                {checkedItems['']}
                {/* Checked item name : {checkedItems["check-box-1"]}{" "} */}
              </label>
              {checkboxes.map((item) => (
                <label key={item.key}>
                  {item.name}
                  <Checkbox
                    name={item.name}
                    checked={checkedItems[item.name]}
                    onChange={handleChange}
                  />
                </label>
              ))}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much - this is a super helpful explanation and the comments. I'll definitely read the post!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.