Skip to content

How make filter in sub component in reactjs?

The list doesn’t update after sorting in the parent component.

/**
 * Shop List
 */
import React, { useEffect, useState } from 'react';
import { NavLink } from 'react-router-dom';
import { Helmet } from 'react-helmet';

// Redux
import { useDispatch, useSelector } from 'react-redux';
import { GetShopList } from 'Actions';

// Components
import { Conditional } from 'Components/Conditional';
import { RctCard } from 'Components/RctCard';
import {
  Card,
  CardImg,
  CardText,
  CardBody,
  CardTitle,
  Badge,
} from 'reactstrap';
import { ListFilter } from '../../../components/ListFilter/ListFilter';

// Utils
import PageTitleBar from 'Components/Page/TitleBar';
import IntlMessages from 'Util/IntlMessages';
import { imageLoader } from 'Helpers';

const ShopList = ({ match }) => {
  const shops = useSelector((state) => state.shops.list);
  const errors = useSelector((state) => state.shops.errors);
  const isLoading = useSelector((state) => state.shops.isLoading);
  const dispatch = useDispatch();

  const [showList, setShowList] = useState([]);

  useEffect(() => {
    dispatch(GetShopList());
  }, []);
  useEffect(() => {
    setShowList(shops);
  }, [shops]);

  return (
      <Conditional isLoaded={isLoading} isValid={errors}>
          <div className="profile-wrapper">
              <IntlMessages id="sidebar.seo.shops">
                  {(title) => (
                      <Helmet>
                          <title>{title}</title>
                          <meta name="description" content={title} />
                      </Helmet>
                  )}
              </IntlMessages>

              <PageTitleBar title="shops" match={match} />

              <RctCard>
                  <div className="shop-wrapper">
                      <ListFilter
                          showList={showList}
                          setShowList={setShowList}
                          list={shops}
                      />
                      <div className="row">
                          {showList &&
                              showList.map((shop, idx) => (
                                  <div
                                      className="col-sm-12 col-md-3 mb-30"
                                      key={idx}
                                  >
                                      <NavLink to={`${match.url}/${shop.id}`}>
                                          <Card>
                                              <div className="card__image">
                                                  <CardImg
                                                      top
                                                      src={imageLoader(
                                                          shop.image
                                                      )}
                                                  />
                                              </div>
                                              <CardBody>
                                                  <CardTitle>
                                                      <strong>
                                                          {shop.name}
                                                      </strong>
                                                  </CardTitle>
                                                  <CardText className="card__description">
                                                      {shop.description}
                                                  </CardText>
                                                  <CardText>
                                                      <i className="zmdi zmdi-pin"></i>
                                                      &nbsp; {shop.country},
                                                      {shop.city}
                                                  </CardText>
                                                  {shop.isOnline ? (
                                                      <Badge color="success">
                                                          <IntlMessages id="table.online" />
                                                      </Badge>
                                                  ) : (
                                                      <Badge color="secondary">
                                                          <IntlMessages id="table.offline" />
                                                      </Badge>
                                                  )}
                                              </CardBody>
                                          </Card>
                                      </NavLink>
                                  </div>
                              ))}
                      </div>
                  </div>
              </RctCard>
          </div>
      </Conditional>
  );
};

export default ShopList;

console.log(‘sortedList’, sorted); – sort correctly ListFilter component:

/**
 * List filter
 */
import React, { useEffect, useState } from 'react';

// Antd
import { Button, FormGroup, Label, Input, Col, Badge } from 'reactstrap';

// Material UI
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';

// Utils
import IntlMessages from 'Util/IntlMessages';

export const ListFilter = ({ showList, setShowList, list = [], match }) => {
debugger;
    console.log("showList", showList);

    const sort = [
        { title: "По обновлению", field: "updatedAt" },
        { title: "По созданию", field: "createdAt" },
        { title: "По имени", field: "name" },
    ];

    const [show, setShow] = useState(false);
    const [sortBy, setSortBy] = useState(sort[0]);

    const sortByHandler = (e, v) => {
        setSortBy(v);
    };

    const showToggle = () => {
        setShow((current) => !current);
    };

    useEffect(() => {
        debugger;
        var sorted = [];
        if (sortBy.field !== "name") {
            sorted = showList.sort((a, b) => {
                var dateA = new Date(a[sortBy.field]),
                    dateB = new Date(b[sortBy.field]);
                return dateA - dateB;
            });
        } else {
            sorted = showList.sort((a, b) => {
                var titleA = a[sortBy.field].toLowerCase(),
                    titleB = b[sortBy.field].toLowerCase();
                if (titleA < titleB) return -1;
                if (titleA > titleB) return 1;
                return 0;
            });
        }

        console.log('sortedList', sorted);

        setShowList(sorted);
    }, [sortBy]);

    return (
        <>
            <div style={{ margin: "10px" }}>
                <FormGroup row>
                    <Label for="city" sm={3}>
                        <Button
                            outline={show}
                            color="primary"
                            onClick={showToggle}
                        >
                            <IntlMessages id="button.list-filter" />
                        </Button>
                    </Label>
                    <Col>{show 
                            ?
                            <>
                                <Autocomplete
                                    id="SortBy"
                                    onChange={sortByHandler}
                                    options={sort.filter(el => el.field)}
                                    getOptionLabel={option => option.title}
                                    closeIcon={false}
                                    value={sortBy}
                                    renderInput={(params) => (
                                    <TextField
                                        {...params}
                                        variant="standard"
                                        label="SortBy"
                                        placeholder="SortBy"
                                    />
                                    )}
                                />
                            </>
                            : ""}
                    </Col>
                </FormGroup>
            </div>
        </>
    );
}

What is wrong?

Answer

The re-render is skipped because you have mutated state. It was mutated from within a child component making it even harder to catch.

sort mutates the original array. As you can see in the examples from the docs, they don’t assign the result to a new variable in order for the sorting to take effect. This is different from some other array methods you may be used to (most common in React being map) which return a new array without changing the original.

Since JavaScript objects (and therefore arrays) are assigned to variables by reference, the mutation in the child will also effect the parent component as long as you do not re-assign the variable (ie showList = newArray). However, re-assigning would be mutating props, and isn’t much better.

To correctly sort, without mutating state or props, you can simply create a new array right before sorting and use the result to update state:

sorted = [...showList].sort(...)