uber
uber

Reputation: 5073

How to style an individual <li> as opposed to all <li> in React?

I am having some trouble changing the colour of an item that has been added to a cart at an e-commerce project. I can make it so that when the item is clicked, the class changes and the item get a colour. As a side effect, all accompanying items get the colour as well.

The 'Service' component

const Service = (props) => {

    const context = useContext(ThemeContext)

    return (
        <>
            <li className={context.cartItems.some(item => item.type === "service") ? "inCart" : ""}
                onClick={() => { context.cartItems.some(item => item) ?
                    context.removeFromCart(props) : context.addToCart(props)}} >
                {props.name}
            </li>
        </>
    )
}

The 'Options' component which renders the above component

const Options = () => {

    const context = useContext(ThemeContext)

    const serviceElements = servicesList.map(service => 
        <Service key={service.id} id={service.id} name={service.name} type={service.type} /> )

    return (
        <div className={`Options-${context.theme}`}>
            <ul>
                {serviceElements}
            </ul>
        </div>
    )
}

The add and remove from cart methods

function addToCart(newItem) { 
        cartItems.map(item => newItem.type === item.type && removeFromCart(item)) 
        setCartItems(prevItems => [...prevItems, newItem])
    }

    function removeFromCart(itemToRemove) { 
        setCartItems(prevItems => prevItems.filter(item => 
            `${item.id}-${item.type}` !== `${itemToRemove.id}-${itemToRemove.type}`)) 

    }

Scss

.Options-light {
  .inCart {
    background-color: blue;
}

Upvotes: 2

Views: 253

Answers (3)

Always Learning
Always Learning

Reputation: 5581

In your Service component you are use context.cartItems.some to determine if "inCart" should be used as the className. This is the problem because some() will be true if any items are in the cart. That means every <li> will either have no className, or all with use "inCart".

What you really want is whether the current item is in the cart so I recommend the following changes:

const Service = (props) => {
  const context = useContext(ThemeContext);
  const inCart = context.cartItems.find(item => item.id === props.id);
  return (
    <>
      <li className={inCart ? "inCart" : ""} 
        onClick={() => { 
          inCart ? 
            context.removeFromCart(props) : 
            context.addToCart(props)
         }} > {props.name}
      </li>
    </>
  )
}

This will only set <li className="inCart" for list items that are in the cart.

Your onClick also uses some() incorrectly. The way it's written it will always be true and always will call removeFromCart(props). I changed it to act as I expect you intended, which is to add it if the item isn't there, and remove if it is.

Upvotes: 2

Tunn
Tunn

Reputation: 1536

Looks like a couple things can change. First of all, your className logic in Service is going to add inCart to all of your li's because it's checking the whole context array independent of the current props. So try:

context.cartItems.find(item => item.id === props.id) ? "inCart" : ""

Also can clean up your content functions:

function addToCart(newItem) {
  // Looks like you were trying to do a check to make sure that the item isn't
  // already in the cart
  const alreadyInCart = cartItems.some(item => item.id === newItem.id);
  if (!alreadyInCart) {
    setCartItems(prevItems => [...prevItems, newItem]);
  }
}

function removeFromCart(itemToRemove) {
  // I assume id is unique because you use it as a key for the Service component
  setCartItems(prevItems => prevItems.filter(item => item.id !== itemToRemove.id));

}

I think you'd probably be better off with a toggleCartItem though to make things cleaner and if your later "Cart" service only needs an id, then you can clean up further. Let me know if this works a little better:

const Options = () => {

  const context = useContext(ThemeContext);

  const serviceElements = servicesList.map(service => 
    <Service
      key={service.id}
      id={service.id}
      inCart={context.cartItems.some(id => service.id === id)}
    />
  );

  return (
    <div className={`Options-${context.theme}`}>
      <ul>
        {serviceElements}
      </ul>
    </div>
  )
}

const Service = (props) => {

  const context = useContext(ThemeContext);

  return (
    <li
      className={props.inCart ? "inCart" : ""}
      onClick={() => toggleCartItem(props.id)}
    >
      {props.name}
    </li>
  )
}

function toggleCartItem(newItemId) {
  const alreadyInCart = cartItems.some(id => id === newItemId);
  if (alreadyInCart) {
    setCartItems(prevIds => prevId.filter(id => id !== newItemId));
  } else {
    setCartItems(prevIds => [...prevIds, newItemId]);
  }
}

Upvotes: 3

Richard Price
Richard Price

Reputation: 490

Your SCSS doesn't have a closing bracket? it should be:

.Options-light {
  .inCart {
    background-color: blue;
  }
}

Upvotes: 0

Related Questions