Search code examples
javascriptreactjsreact-reduxlodashreact-hooks

Using Hooks along with Redux---Bad Practice?


I'm trying to implement a sorted table as shown in the Semantic UI Docs.

https://react.semantic-ui.com/collections/table/#variations-sortable

I'm using redux on my project and confused as to how I should implement both the sorted tables and my search input function.

On page load I am storing all data in a state called baseball. I also have a cardsToShow state which is updated if a user is searching for something, triggered by searchCards function. The cardsToShow state is what I'm using to display my data.

const mapStateToProps = state => {
  return {
    baseball: state.baseball,
    cardsToShow: searchCards(state)
  };
};

const searchCards = ({ baseball, search }) => {
  return search
    ? baseball.filter(a =>
        a.title[0].toLowerCase().includes(search.toLowerCase())
      )
    : baseball;
};

In the Semantic UI/Lodash example, they update and sort the data state when a user clicks a column.

I've tried to replicate this using some state hooks for the column, direction and filteredData fields.

const Cards = props => {
  const [column, setColumn] = useState(null);
  const [direction, setDirection] = useState(null);
  const [filteredData, setData] = useState(props.cardsToShow);

  const handleSort = clickedColumn => {
    if (column !== clickedColumn) {
      setColumn(clickedColumn);
      setData(_.sortBy(filteredData, [clickedColumn]));
      setDirection("ascending");
      return;
    }

    setData(_.sortBy(filteredData.reverse()));
    direction === "ascending"
      ? setDirection("descending")
      : setDirection("ascending");
  };

This is also what my column header looks like:

  <Table.HeaderCell
                    sorted={column === "title" ? direction : null}
                    onClick={handleSort("title")}
                  >
                    Card Title
                  </Table.HeaderCell>

First of all, i'm getting a 'Too many re-renders' error on the code above which is one problem. But the main issue is that I now have a filteredData field which I'm updating ....yet I have the other state cardsToShow.

I know whatever I'm doing is here is wrong, but curious to see if anyone has any guidance as to how to best accomplish this. Is it acceptable to use hooks like this, along with having a redux store which holds your global state? Or should i somehow create another reducer for the sorting that I'm trying to do? Thanks.

---EDIT--UPDATED COMPONENT BELOW

import React, { useState } from "react";
import Search from "./Search";
import TimeAgo from "react-timeago";
import { useSelector} from "react-redux";
import { Table } from "semantic-ui-react";
import _ from "lodash";
// import { useField } from "../hooks";

const searchCards = ({ baseball, search }) => {
  return search
    ? baseball.filter(a =>
        a.title[0].toLowerCase().includes(search.toLowerCase())
      )
    : baseball;
};

const Cards = () => {
  const baseball = useSelector(state => state.baseball);
  const search = useSelector(state => state.search);
  const cardsToShow = searchCards(baseball, search);
  const [column, setColumn] = useState(null);
  const [direction, setDirection] = useState(null);
  const [filteredData, setData] = useState(cardsToShow);

  const handleSort = clickedColumn => {
    if (column !== clickedColumn) {
      setColumn(clickedColumn);
      setData(_.sortBy(filteredData, [clickedColumn]));
      setDirection("ascending");
      return;
    }

    setData(_.sortBy(filteredData.reverse()));
    direction === "ascending"
      ? setDirection("descending")
      : setDirection("ascending");
  };

  return (
    <>
      <div>
        <Search />
        <h3>Vintage Card Search</h3>
        <Table sortable celled fixed striped>
          <Table.Header>
            <Table.Row>
              <Table.HeaderCell
                sorted={column === "title" ? direction : null}
                onClick={handleSort("title")}
              >
                Card Title
              </Table.HeaderCell>
              <Table.HeaderCell># Bids</Table.HeaderCell>
              <Table.HeaderCell>Watchers</Table.HeaderCell>
              <Table.HeaderCell>Price</Table.HeaderCell>
              <Table.HeaderCell>Time Left</Table.HeaderCell>
            </Table.Row>
          </Table.Header>
          <Table.Body>
            {filteredData.map(card => (
              <>
                <Table.Row key={card.id}>
                  <Table.Cell>{card.title}</Table.Cell>
                  <Table.Cell>
                    {card.sellingStatus[0].bidCount
                      ? card.sellingStatus[0].bidCount
                      : 0}
                  </Table.Cell>
                  <Table.Cell>
                    {card.listingInfo[0].watchCount
                      ? card.listingInfo[0].watchCount
                      : 0}
                  </Table.Cell>
                  <Table.Cell>
                    $
                    {card.sellingStatus &&
                      card.sellingStatus[0].currentPrice[0]["__value__"]}
                  </Table.Cell>
                  <Table.Cell>
                    <TimeAgo
                      date={new Date(
                        card.listingInfo && card.listingInfo[0].endTime
                      ).toLocaleDateString()}
                    />
                  </Table.Cell>
                </Table.Row>
              </>
            ))}
          </Table.Body>
        </Table>
      </div>
    </>
  );
};

export default Cards;

Solution

  • Instead using Redux as you were used, try the new Redux implementation in hooks in this way:

    import React, {useState} from "react";
    import {useSelector } from "react-redux";
    
    const searchCards = (baseball, search) => {
      return search
        ? baseball.filter(a =>
            a.title[0].toLowerCase().includes(search.toLowerCase())
          )
        : baseball;
    };
    
    export const Cards = () => {
      const baseball = useSelector(state => state.baseball);
      const search = useSelector(state => state.search);
      const cardsToShow = searchCards(baseball, search);
      const [column, setColumn] = useState(null);
      const [direction, setDirection] = useState(null);
      const [filteredData, setData] = useState(cardsToShow);
    
      const handleSort = clickedColumn => {
        if (column !== clickedColumn) {
          setColumn(clickedColumn);
          setData(_.sortBy(filteredData, [clickedColumn]));
          setDirection("ascending");
          return;
        }
    
        setData(_.sortBy(filteredData.reverse()));
        direction === "ascending"
          ? setDirection("descending")
          : setDirection("ascending");
      };
    
      return {filteredData, handleSort};
    };
    

    From what you explain you want to achieve I find you need to export the filteredData element (the baseball cards to show already filtered and sorted) and the handleSort method to be able to trigger the changes from your Table component. I don't think you would need to store in Redux again the filtered and sorted elements. Using reselect and memoizing the selector should be enough if you want to go that way and improve the performance.