Home > Software engineering >  Alter value of useMemo state outside of initial function block
Alter value of useMemo state outside of initial function block

Time:07-09

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]);
  • Related