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> {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?
Advertisement
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(...)