Making a employee directory and I'm trying to convert class component into functional component. The nav, search, and table will render but the call to the api isn't working. Getting a
TypeError: users.filter is not a function
Here is the class component that works and the in progress functional component. I can't figure out what is different.
import React, { Component } from "react";
import DataTable from "./DataTable";
import Nav from "./Nav";
import API from "../utils/API";
import "../styles/DataArea.css";
export default class DataArea extends Component {
state = {
users: [{}],
order: "descend",
filteredUsers: [{}]
}
headings = [
{ name: "Image", width: "10%" },
{ name: "Name", width: "10%" },
{ name: "Phone", width: "20%" },
{ name: "Email", width: "20%" },
{ name: "DOB", width: "10%" }
]
handleSort = heading => {
if (this.state.order === "descend") {
this.setState({
order: "ascend"
})
} else {
this.setState({
order: "descend"
})
}
const compareFnc = (a, b) => {
if (this.state.order === "ascend") {
// account for missing values
if (a[heading] === undefined) {
return 1;
} else if (b[heading] === undefined) {
return -1;
}
// numerically
else if (heading === "name") {
return a[heading].first.localeCompare(b[heading].first);
} else {
return a[heading] - b[heading];
}
} else {
// account for missing values
if (a[heading] === undefined) {
return 1;
} else if (b[heading] === undefined) {
return -1;
}
// numerically
else if (heading === "name") {
return b[heading].first.localeCompare(a[heading].first);
} else {
return b[heading] - a[heading];
}
}
}
const sortedUsers = this.state.filteredUsers.sort(compareFnc);
this.setState({ filteredUsers: sortedUsers });
}
handleSearchChange = event => {
console.log(event.target.value);
const filter = event.target.value;
const filteredList = this.state.users.filter(item => {
// merge data together, then see if user input is anywhere inside
let values = Object.values(item)
.join("")
.toLowerCase();
return values.indexOf(filter.toLowerCase()) !== -1;
});
this.setState({ filteredUsers: filteredList });
}
componentDidMount() {
API.getUsers().then(results => {
this.setState({
users: results.data.results,
filteredUsers: results.data.results
});
});
}
render() {
return (
<>
<Nav handleSearchChange={this.handleSearchChange} />
<div className="data-area">
<DataTable
headings={this.headings}
users={this.state.filteredUsers}
handleSort={this.handleSort}
/>
</div>
</>
);
}
}
import React, { useState, useEffect } from "react";
import DataTable from "./DataTable";
import Nav from "./Nav";
import API from "../utils/API";
import "../styles/DataArea.css";
const DataArea = () => {
const [users, setUsers] = useState([{}]);
const [order, setOrder] = useState("descend");
const [filteredUsers, setFilteredUsers] = useState([{}]);
const headings = [
{ name: "Image", width: "10%" },
{ name: "Name", width: "10%" },
{ name: "Phone", width: "20%" },
{ name: "Email", width: "20%" },
{ name: "DOB", width: "10%" },
];
const handleSort = (heading) => {
if (order === "descend") {
setOrder((order = "ascend"));
} else {
setOrder((order = "descend"));
}
const compareFnc = (a, b) => {
if (order === "ascend") {
// account for missing values
if (a[heading] === undefined) {
return 1;
} else if (b[heading] === undefined) {
return -1;
}
// numerically
else if (heading === "name") {
return a[heading].first.localeCompare(b[heading].first);
} else {
return a[heading] - b[heading];
}
} else {
// account for missing values
if (a[heading] === undefined) {
return 1;
} else if (b[heading] === undefined) {
return -1;
}
// numerically
else if (heading === "name") {
return b[heading].first.localeCompare(a[heading].first);
} else {
return b[heading] - a[heading];
}
}
};
const sortedUsers = filteredUsers.sort(compareFnc);
setFilteredUsers({ filteredUsers: sortedUsers });
};
let handleSearchChange = (event) => {
console.log(event.target.value);
const filtered = event.target.value;
const filteredList = users.filter((item) => {
// merge data together, then see if user input is anywhere inside
let values = Object.values(item).join("").toLowerCase();
return values.indexOf(filtered.toLowerCase()) !== -1;
});
setFilteredUsers((filteredUsers = filteredList));
};
useEffect(() => {
API.getUsers().then((results) => {
setUsers({
users: results.data.results,
filteredUsers: results.data.results,
});
});
}, []);
return (
<>
<Nav handleSearchChange={handleSearchChange} />
<div className="data-area">
<DataTable
headings={headings}
users={filteredUsers}
handleSort={handleSort}
/>
</div>
</>
);
};
export default DataArea;
The issue is in your useEffect
hook where you shoehorn both users
and filteredUsers
into your users
state.
useEffect(() => {
API.getUsers().then((results) => {
setUsers({
users: results.data.results,
filteredUsers: results.data.results,
});
});
}, []);
It should be
useEffect(() => {
API.getUsers().then((results) => {
setUsers(results.data.results);
setFilteredUsers(results.data.results),
});
}, []);
You want to populate both the users
state, and separately the filteredUsers
state.
An issue with setting the sorting order, you are mutating the order
state:
if (order === "descend") {
setOrder((order = "ascend")); // <-- mutates
} else {
setOrder((order = "descend")); // <-- mutates
}
You can simply toggle between the two:
setOrder(order => order === 'ascend' ? 'descend' : 'ascend');
Another issue is at the end of your handleSort
function:
const sortedUsers = filteredUsers.sort(compareFnc);
setFilteredUsers({ filteredUsers: sortedUsers });
This will nest the filteredUsers array, and also mutates the array in-place, it should probably use a functional state update to update from the previous state, use array.slice to make a copy, and then call sort.
setFilteredUsers(filteredUsers => filteredUsers.slice().sort(compareFnc));