I'm trying to sort a complex array. This array is grabbed with GraphQL from my headless CMS as follows:
query MyQuery {
teams {
id
name
match {
teams {
name
}
homePoints
awayPoints
date
trophy
}
}
}
Now I'm mapping through all the teams and calculated the total points of a team (depending whether a team is playing home or away) with the following:
<Tbody>
{!fetching && data.teams.map((team, i) => {
const points = team.match.map(match => match.teams[0].name === team.name ? match.homePoints : match.awayPoints).reduce((a, b) => a b, 0)
return (
<Tr key={team.id}>
<Td>{i 1}</Td>
<Td>{team.name}</Td>
<Td>{points}</Td>
</Tr>
)
})}
</Tbody>
The result is displayed well within a table, except the following: it is not sorted. The team with the highest amount of points should be on the top of the table. How to solve this?
CodePudding user response:
For a clean code, create a few helper functions before the render / return part. It should looks something like this:
// Util functions outside component
const withPoints = (team, i) => {
const points = team.match.map(match =>
match.teams[0].name === team.name ? match.homePoints :
match.awayPoints).reduce((a, b) => a b, 0)
return {team, points}
};
const byPoints = (a, b) => b.points - a.points
// My component
const MyComponent = ({fetching, data}) => {
// temporary assignment in component body to make the JSX part cleaner
const teams = fetching ?
data.teams
.map(withPoints)
.sort(byPoints)
: null
return <Tbody>
{teams && teams.map((t, i) => (
<Tr key={t.team.id}>
<Td>{i 1}</Td>
<Td>{t.team.name}</Td>
<Td>{t.points}</Td>
</Tr>
))}
</Tbody>
}
CodePudding user response:
Oh i see, you basically want to sort the array. However you didn't prepare the array ahead of the time, that's the trouble you get yourself into.
To resolve this, let's prepare the array first.
// this the the teams with points
// you did good job of processing it
let teams = data.teams.map(...)
// sort the array
// please test the sorting order
teams.sort(function(a, b) {
return Math.sign(b.point - a.point)
})
// this is the place for render
return (
<Tbody>
{teams.map(team => (
<Tr key={team.id}>
...
</Tr>
)}
<Tbody>
)
A general good practice is that, don't bring any logic code into the render layer. In React, it's about the line of the return
, if you can put most of the logic above that line, it'll help you refactor your render later on.
CodePudding user response:
I suggest creating a helper function for calculating the points of a team.
// outside the component
function calculatePoints(team) {
return team.match
.map((match) => {
// introduction of variable for code clarity (self explaining code)
const isHomeMatch = match.teams[0].name === team.name;
return isHomeMatch ? match.homePoints : match.awayMatch;
})
.reduce((total, points) => total points, 0);
}
To prevent calculations each render you can use useMemo
to store the calculated points for each team. You could place them in a Map
for fast access.
// inside the component
const points = useMemo(() => {
const teams = data?.teams || []; // default to an empty array if not present
return new Map(teams.map(team => [team, calculatePoints(team)]));
}, [data?.teams]);
Then create a sorted copy of data.teams
based on each teams points. Note that sort()
sorts elements in place (aka mutates the array). For this reason you should create a copy so the original array stays unmodified.
// inside the component
const sortedTeams = useMemo(() => {
const teams = data?.teams || []; // default to an empty array if not present
// create a shallow copy because `sort()` sorts the elements in place
const sorted = Array.from(teams);
sorted.sort((a, b) => points.get(b) - points.get(a)); // desc = b - a
return sorted;
}, [data?.teams]);
Finally, use sortedTeams
instead of data.teams
for the return value of your render function.
<Tbody>
{!fetching && sortedTeams.map((team, i) => (
<Tr key={team.id}>
<Td>{i 1}</Td>
<Td>{team.name}</Td>
<Td>{points.get(team)}</Td>
</Tr>
))}
</Tbody>
data?.teams
will only access the teams
property if data is not nullish (null
or undefined
). If it is nullish, undefined
will be returned.