I am rendering a list of dragon ball z characters, and within a useMemo
the user can filter the characters based on gender, race or both.
let dbzCharacters = useMemo<CharacterObj[]>(() => {
if (filterGenderValue && filterRaceValue) {
return characters.filter(
(char: CharacterObj) =>
char.race === filterRaceValue && char.gender === filterGenderValue
);
} else if (filterGenderValue && !filterRaceValue) {
return characters.filter(
(char: CharacterObj) => char.gender === filterGenderValue
);
} else if (!filterGenderValue && filterRaceValue) {
return characters.filter(
(char: CharacterObj) => char.race === filterRaceValue
);
} else {
return characters;
}
}, [filterGenderValue, filterRaceValue]);
my goal now to apply the following functionality:
Whatever the state ofdbzCharacters
is, I also want to be able to search by name via an input. I do not want to code that logic into the useMemo
block as it's going to result in a million if statements. My current attempt is to re-assign the value of dbzCharacters
inside a useEffect
useEffect(() => {
if (dbzCharacters && search.length > 1) {
dbzCharacters = dbzCharacters.filter(({ name }: CharacterObj) =>
name!.toLowerCase().includes(search.toLowerCase())
);
}
}, [search, dbzCharacters]);
The search functionality is not working as is. the dbzCharacters
is not altering. Is there any work around for this?
CodePudding user response:
You can merge the useEffect code inside the useMemo without extra code.
let dbzCharacters = useMemo<CharacterObj[]>(() => {
let result = null;
if (filterGenderValue && filterRaceValue) {
result = characters.filter(
(char: CharacterObj) =>
char.race === filterRaceValue && char.gender === filterGenderValue
);
} else if (filterGenderValue && !filterRaceValue) {
result = characters.filter(
(char: CharacterObj) => char.gender === filterGenderValue
);
} else if (!filterGenderValue && filterRaceValue) {
result = characters.filter(
(char: CharacterObj) => char.race === filterRaceValue
);
} else {
result = characters;
}
if (result && search.length > 1) {
return result.filter(({ name }: CharacterObj) =>
name!.toLowerCase().includes(search.toLowerCase())
);
}
return result;
}, [filterGenderValue, filterRaceValue, search]);
CodePudding user response:
My solution was to apply the same logic but inside another useMemo
and render that.
const dbzCharsFilterByName = useMemo<CharacterObj[]>(() => {
return search.length > 0 && dbzCharacters
? dbzCharacters.filter(({ name }: CharacterObj) =>
name!.toLowerCase().includes(search.toLowerCase())
)
: dbzCharacters;
}, [search, dbzCharacters]);
CodePudding user response:
You are correct that your current useMemo
is getting quite complex and adding a name search would make it only more complex. However the complexity is due to the fact that you want to catch all the scenarios with a single filter()
call.
Instead you can use if-statements (not else if) to incrementally apply the filters.
let cbzCharacters = useMemo<CharacterObj[]>(() => {
let cbzCharacters = characters; // characters should also be in the dependency array if it's not static
if (filterGenderValue) {
cbzCharacters = cbzCharacters.filter(
(char: CharacterObj) => char.gender === filterGenderValue
);
}
if (filterRaceValue) {
cbzCharacters = cbzCharacters.filter(
(char: CharacterObj) => char.race === filterRaceValue
);
}
if (search) { // empty string is falsy
cbzCharacters = cbzCharacters.filter(
({ name }: CharacterObj) => name!.toLowerCase().includes(search.toLowerCase())
);
}
return cbzCharacters;
}, [filterGenderValue, filterRaceValue, search]);
Alternatively you could also use multiple useMemo
calls, which achieves the same thing.
let cbzCharacters = characters;
cbzCharacters = useMemo<CharacterObj[]>(() => {
if (!filterGenderValue) return cbzCharacters;
return cbzCharacters.filter(
(char: CharacterObj) => char.gender === filterGenderValue
);
}, [cbzCharacters, filterGenderValue]);
cbzCharacters = useMemo<CharacterObj[]>(() => {
if (!filterRaceValue) return cbzCharacters;
return cbzCharacters.filter(
(char: CharacterObj) => char.race === filterRaceValue
);
}, [cbzCharacters, filterRaceValue]);
cbzCharacters = useMemo<CharacterObj[]>(() => {
if (!search) return cbzCharacters;
return cbzCharacters.filter(
({ name }: CharacterObj) => name!.toLowerCase().includes(search.toLowerCase())
);
}, [cbzCharacters, search]);