Search code examples
javascriptreactjstypescriptsvgreact-icons

Change SVG icon color based on 'liked' status


I have an Upvote component in my React/TypeScript app that allows user to upvote a post, or remove their upvote. the upvote itself is an icon from the react-icons package, specifically from the Grommet-Icons section. When a user upvotes a post by clicking on the icon, I dynamically change the fill of the icon like this:

e.target.querySelector("path").setAttribute("fill", "green");

e.target is an SVG element. Within that element, I use querySelector to find its path child. On the path child, I set its 'fill' attribute to green.

This works fine, but I want to extend it. When I render each post, I check if the user that's logged in already upvoted that specific post. I want to change the color of the icon based on the boolean result of whether the user already upvoted the post or not, so this way the icon can already be green and the user will see that they already upvoted the post. My question is how can I access that 'fill' attribute WITHOUT the e.target- since the user hasn't clicked on anything when the page loads. I guess my question is pretty specific to the usage of the react-icons package, because when I add an icon to my return function, it's wrapped by a wrapper component, so I can't access the path element (In this case the component is GrSign).

Here is the Upvote component for context:

import React, { useEffect, useState } from "react";
import { GrSign } from "react-icons/gr";
import { useSelector } from "react-redux";
import { InitialState } from "../store/reducers/rootReducer";

export const Upvote = (props: any) => {

const userState = useSelector((state: InitialState) => {
    return state.auth;
  });

const [upvoted, setUpvoted] = useState(false);

  useEffect(() => {
    if (userState && userState.user !== undefined) {
      const userId = userState.user.user._id;
      if (props.upvotes.indexOf(userId) > -1) {
        // The user already upvoted this post
        setUpvoted(true);
      }
    }

    if (upvoted) {
        
    }
  }, []);

const handleClick = (e: any) => {
    console.log(e.target);
    e.target.querySelector("path").setAttribute("fill", "red");
  };
 
  return (
    <div className="flex items-center">
      <GrSign
        size="2em"
        className="transform transition duration-300 hover:scale-125"
        onClick={handleClick}
      />
      <div className="pl-2 text-lg">{props.upvotes.length}</div>
    </div>
  );
};

in the useEffect function, I would like to add a line that will change the color of the icon if upvoted === true.

Just in case this helps, this is what gets printed to the console when I write console.log(e.target) inside the handleClick function:

<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" class="transform transition duration-300 hover:scale-125" height="2em" width="2em" xmlns="http://www.w3.org/2000/svg"><path fill="red" stroke="#000" stroke-width="2" d="M8,23 C10,23 12.9996892,23 15,23 C18,23 19,21 19,18 L19,6 C19,4.00000008 18,3.99999992 17.5,4 C17,4.00000008 15.9998779,4.00000008 15.9998779,5.99999984 L15.9998779,13 C15.9998777,12 16.0001888,10.9999999 14.5003109,10.9999999 C13.000433,10.9999999 13,13 13,13 C13,12 13,11 11.5,10.9999999 C10,10.9999999 10,12 10,12.9999999 L10,4 C10,3 10.029402,2 8.5,2 C7,2 7,3 7,4 L7,18 L7,14 C7,13 6.44999986,12 5.00000005,12 C5,12 4,12 4,12 C4,12 4.00000001,14.0384045 4,18 C3.99999999,21.9615955 6,23.023861 8,23 Z"></path></svg>

Solution

  • You can create a ref to the icon container. Also add a new useEffect block that is executed every time the upvoted property changes:

    export const Upvote = (props: any) => {
          //...
          const [upvoted, setUpvoted] = useState(false);
          const iconRef = useRef(null);
        
          useEffect(() => {
            if (userState && userState.user !== undefined) {
              const userId = userState.user.user._id;
              if (props.upvotes.indexOf(userId) > -1) {
                // The user already upvoted this post
                setUpvoted(true);
              }
            }
          }, []);
        
          useEffect(() => {
            if (upvoted) {
                 iconRef.current.querySelector("svg path").setAttribute("fill", "red")
            }
          }, [upvoted]);
        
          const handleClick = e => {
            //...
          };
          return (
            <div className="flex items-center" ref={iconRef}>
              <GrSign
                size="2em"
                className="transform transition duration-300 hover:scale-125"
                onClick={handleClick}
              />
              <div className="pl-2 text-lg">{upvotes.length}</div>
            </div>
          );
        }