React Js - Table doesn't sort names - javascript

So I've got this table in React. It works fine with all the columns except the one with the country name. I checked API so className definitely matches with it and I am a bit clueless how to fix it.
const Table = () => {
const[country, setCountry] = useState([]);
const[toggle, setToggle] = useState(true);
const sortColumnNumber = (sort, columnName, data) => {
data = data.sort((a, b) => {
return sort ? b[columnName] - a[columnName] : a[columnName] - b[columnName];
});
}
useEffect(() => {
const loadData = async() => {
await fetch('https://api.covid19api.com/summary')
.then(response => response.json())
.then(data => {
const stats = data.Countries;
sortColumnNumber(toggle, 'TotalConfirmed', stats)
setCountry(stats);
})
}
loadData();
}, []);
return(
<React.Fragment>
<table className="table table-bordered table-stripped">
<thead >
<tr onClick={(e) =>{
setToggle(!toggle);
sortColumnNumber(toggle, e.target.className, country);
}} style={{cursor: "pointer"}} className="thead-dark">
<th className="Country" scope="col">Country</th>
<th className="TotalConfirmed" scope="col">Total Cases</th>
<th className="NewConfirmed" scope="col">New Cases</th>
<th className="NewDeaths" scope="col">New Deaths</th>
<th className="TotalDeaths" scope="col">Total Deaths </th>
<th className="TotalRecovered" scope="col">Total Recovered </th>
</tr>
</thead>
<tbody>
<CountryStats country={country} />
</tbody>
</table>
</React.Fragment>
)
}

Try something like that:
const sortColumnNumber = (sort, columnName, data) => {
data = data.sort((a, b) => {
let [first, second] = sort ? [a, b] : [b, a];
if (first[columnName] < second[columnName]) return -1;
if (first[columnName] > second[columnName]) return 1;
return 0;
});
};
It's important to return -1 or 0 or 1 to avoid problem with data types.
See more about sort method in this link: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

It doesn't work with country name because it is a String type and your callback method logic will not work for sorting Strings.
Subtracting two String values results in NaN.
Refer to this SO answer for sorting Strings.

Related

Loop over object and array of objects in React

Calling an API i get this data
{
"count":33,
"results":
[
{"id":365,"firstName":"Maisha","lastName":"Rawlings"},
{"id":613,"firstName":"Lettie","lastName":"Epps"},
{"id":433,"firstName":"Rocco","lastName":"Gant"},
{"id":856,"firstName":"Tova","lastName":"Coyle"},
{"id":896,"firstName":"Tari","lastName":"Mancuso"},
{"id":79,"firstName":"Tora","lastName":"Prince"},
{"id":59,"firstName":"Lashon","lastName":"Dunaway"},
{"id":378,"firstName":"Corey","lastName":"Schaffer"},
{"id":33,"firstName":"Nanci","lastName":"Middleton"},
{"id":390,"firstName":"Carmon","lastName":"Lavender"}
]
}
Which i have it in here for testing purposes
https://mocki.io/v1/d676e413-6659-4410-a8a3-7d5636f4b719
I have the next app made in React
const USERS_URL = "https://mocki.io/v1/d676e413-6659-4410-a8a3-7d5636f4b719";
export default function Table() {
const [names, setNames] = useState([]);
useEffect(() => {
async function getData() {
let response = await fetch(`${USERS_URL}`);
response = await response.json();
console.log(response);
setNames(response);
}
getData();
}, []);
return (
<div>
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead>
<tbody>
{
names
&&
Object.entries(names).map(([key, value, idx]) => {
return <tr key={key}>
<td>{names.key}</td>
<td>{value.results[key]}</td>
{console.log(key, value)}
</tr>
})
}
</tbody>
</table>
</div>
);
}
I can't find the way to loop over "results", which is an array of objects. Any hint please?
Sollution
names.results.map((val,index)=>{
console.log("object",val)
})
us hte . we can access the properties inside and Object first access the result array in the object and then map() on it

Search in custom table - React

I'm trying to make a table with api data searchable. I'm on my way, but unsure on how to proceed further.
Code looks like this:
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);
const handleChange = event => {
setSearchTerm(event.target.value);
};
useEffect(() => {
const results = apiData.filter(person =>
person.toLowerCase().includes(searchTerm)
);
setSearchResults(results);
}, [searchTerm]);
const renderPerson = (contact, index) => {
return (
<tr key={index}>
<td>{contact.ID}</td>
<td>{contact.Info.Name}</td>
<td>{contact.InfoID}</td>
<td>{contact.Info.DefaultEmail.EmailAddress}</td>
<td>{contact.Info.DefaultPhone.Number}</td>
<td>{contact.Info.InvoiceAddress.AddressLine1}</td>
</tr>
)
}
return (
<Fragment>
<input type="text" value={searchTerm} onChange={handleChange} placeholder="Søk.."></input>
<table id="myTable">
<thead>
<tr className="header">
<th>ID</th>
<th>Navn</th>
<th>Info Id</th>
<th>E-post</th>
<th>Telefon</th>
<th>Adresse</th>
</tr>
</thead>
<tbody>
{apiData.map(renderPerson)}
</tbody>
</table>
</Fragment>
)
https://dev.to/asimdahall/simple-search-form-in-react-using-hooks-42pg
I've followed this guide, but since I have the renderPerson function, I'm a bit unsure on how to handle this.
Question: Is there any way to get this working, or am I approaching it the wrong way? I'm aware that I need to put searchResult in the tbody somehow, but then the table won't be populated.
Any help is much appreciated
Edit: displaying code for getting apiData:
useEffect(() => {
getContacts()
}, [])
const getContacts = () => {
$.ajax({
url: `http://localhost:5000/getContacts`,
type: "POST",
data: ajaxObj,
success: (data) => {
let response = JSON.parse(data)
setApiData(response)
setLoading(false)
},
error: () => {
console.log("noe feilet?");
}
});
}
console.log(apiData)
Data looks like this:
data
Change apiData to searchResults on render
<tbody>
{searchResults.map(renderPerson)}
</tbody>
Change your filter way result (Updated)
const results = apiData.filter(person =>
person.Info.Name.toLowerCase().includes(searchTerm)
);
....
const renderPerson = (item, index) => {
return (
<tr key={index}>
<td>{item.ID}</td>
<td>{item.Info.Name}</td>
<td>{item.InfoID}</td>
<td>{item.Info.DefaultEmail.EmailAddress}</td>
<td>{item.Info.DefaultPhone.Number}</td>
<td>{item.Info.InvoiceAddress.AddressLine1}</td>
</tr>
)
}
Try this
export default function () {
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);
const apiData=[
{
ID:'1222',
Info:{
ID:'1222',
EmailAddress:'test#test.com',
Number:'2222222222',
AddressLine1:'test'
}
},
{
ID:'2333',
Info:{
ID:'2333',
EmailAddress:'test2#test.com',
Number:'1111111111',
AddressLine1:'test2'
}
}
]
const handleChange = event => {
setSearchTerm(event.target.value);
if(event.target.value){
const results = apiData.filter(person =>
person.ID.toLowerCase().includes(event.target.value)
);
setSearchResults(results);
}else{
setSearchResults([]);
}
};
const renderPerson = (contact, index) => {
return (
<tr key={index}>
<td>{contact.ID}</td>
<td>{contact.Info.Name}</td>
<td>{contact.Info.ID}</td>
<td>{contact.Info.EmailAddress}</td>
<td>{contact.Info.Number}</td>
<td>{contact.Info.AddressLine1}</td>
</tr>
)
}
return (
<Fragment>
<input type="text" value={searchTerm} onChange={handleChange} placeholder="Søk.."></input>
<table id="myTable">
<thead>
<tr className="header">
<th>ID</th>
<th>Navn</th>
<th>Info Id</th>
<th>E-post</th>
<th>Telefon</th>
<th>Adresse</th>
</tr>
</thead>
<tbody>
{searchResults.map(renderPerson)}
</tbody>
</table>
</Fragment>
)
}

Sort descending and ascending on click

I am sorting a table and when I click, the order goes to ascending but not descending. I have a boolean value that should toggle true or false but it keeps logging out the initial value. Can someone help me understand why the if statement isn't working? Also when I sort how do I return to the original array?
let sortDirection = false;
const sortTable = (key) => {
sortDirection = !sortDirection
const clonedOptions = [...listOfOptions];
clonedOptions.sort((a, b) => {
return sortDirection ? a[key] - b[key] : b[key] - a[key];
})
setListOfOptions(clonedOptions);
}
<div className="outputs" >
<table>
<thead>
<tr>
<th></th>
<th onClick={() => sortTable('clock')}>Date </th>
<th onClick={() => sortTable('name')}>Stock Name</th>
<th onClick={() => sortTable('price')}>Price Of Option</th>
<th onClick={() => sortTable('amountOfOptions')}>Number Of Options</th>
<th onClick={() => sortTable('totalAmountSpent')}>Total Amount Spent</th>
<th onClick={() => sortTable('optionPriceSoldAt')}>Option Sold At</th>
<th onClick={() => sortTable('amountOfOptionsSold')}>Amount Of Options Sold</th>
<th onClick={() => sortTable('totalProfit')}>Proft</th>
</tr>
</thead>
{listOfOptions.map((option) => {
return (
<tbody key={uuidv1()}>
<tr>
<td title="delete" onClick={() => deleteOption(option.id)}><span className="delete">x</span></td>
<td>{option.clock}</td>
<td>{option.name.toUpperCase()}</td>
<td>${option.price}</td>
<td>{option.amountOfOptions}</td>
<td>${option.totalAmountSpent.toFixed(2)}</td>
<td>${option.optionPriceSoldAt}</td>
<td>{option.amountOfOptionsSold}</td>
<td style={{ color: option.totalProfit >= 0 ? 'green' : 'red' }}>${option.totalProfit.toFixed(2)}</td>
</tr>
</tbody>
)
})}
</table>
</div>
</div>
You need to put the boolean into state, otherwise, on each render, it'll start out as false:
const [sortDirection, setSortDirection] = useState(false);
const sortTable = (key) => {
setSortDirection(!sortDirection);
const clonedOptions = [...listOfOptions];
clonedOptions.sort((a, b) => {
return sortDirection ? b[key] - a[key] : a[key] - b[key];
})
setListOfOptions(clonedOptions);
}

Filter method on another method?

I want to run a filter method on the renderTable method that I've defined below. Is it possible to do:
renderTable().filter(x => x.name)?
Right now, I have a table with rows of each category and their results from a url that provided some json data.
I would like to make a feature that allows users to adjust the setting to return their desired results.
CODE
const renderTable = () => {
return players.map(player => {
return (
<tr>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.gender}</td>
<td>{player.state}</td>
<td>{player.status}</td>
</tr>
)
})
}
return (
<div className = "App">
<h1>Players</h1>
<table id = "players">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Gender</th>
<th>State</th>
<th>Status</th>
</tr>
</thead>
<tbody>{renderTable()}</tbody>
</table>
</div>
);
You can apply the filter before the map. Here, give this a try:
const renderTable = () => {
return players
.filter(player => player.state === "NSW")
.map(player => {
return (
<tr>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.gender}</td>
<td>{player.state}</td>
<td>{player.status}</td>
</tr>
);
});
};
Here's a Working Sample CodeSandbox for your ref.
Yes you can. Just filter before map players.filters(...).map. But this is better
const renderTable = () => {
const rows = []
players.forEach((player, index) => {
if(player.name) {
rows.push(
<tr key={index}>
<td>{player.name}</td>
<td>{player.age}</td>
<td>{player.gender}</td>
<td>{player.state}</td>
<td>{player.status}</td>
</tr>
)
}
})
return rows;
}

Why data isn't updating after sorting in React Hooks?

I'm new to React Hooks. I'm trying to sort table inside React Hook, but after click my content doesn't update. Why is that?
This is my hook:
const Main = ({ dataProps }) => {
const [data, setData] = useState(dataProps);
const sortById = (field) => {
let sortedData = data.sort((a, b) => {
if(a[field] < b[field]) { return -1; }
if(a[field] > b[field]) { return 1; }
return 0;
});
setData(sortedData);
};
return (
<table>
<thead>
<tr>
<th>iD <div className="arrows"><div onClick={() => sortById("id")} className="arrow-up"/></th>
<th>First name <div className="arrows"><div onClick={() => sortById("firstName")} className="arrow-up"/></div></th>
</tr>
</thead>
<tbody>
{data.map((user) => {
return <tr key={user.id}>
<td className="number">{user.id}</td>
<td className="firstname">{user.firstName}</td>
</tr>
})}
</tbody>
</table>
);
};
problem is sort return same array which won't cause re-render. if you slice before sorting it will fix issue.
const sortById = (field) => {
let sortedData = data.slice().sort((a, b) => {
if(a[field] < b[field]) { return -1; }
if(a[field] > b[field]) { return 1; }
return 0;
});
setData(sortedData);
};
BTW more efficient way of doing is mentioned by #jonas-wilms
sortById sorts the data array. But it does not trigger the component to rerender. The field here is obviously a state:
const [data, setData] = useState(dataProps);
const [field, setField] = useState(null);
const sorted = useMemo(() => {
if(!field) return data;
return data.slice().sort((a, b) => a[field].localeCompare(b[field]));
}, [ data, field]);
return <div>
<a onClick={() => setField("firstName")}> Sort by Name</a>
{sorted.map(/*...*/)}
</div>;

Categories

Resources