Tautvydas
Tautvydas

Reputation: 1

React does not re-render component when the state changes

I have 3 different divs which I want to work like a radio select. When I click on the div I want to change that div's color, when I click on another div I want to remove the previous div's color and add color to the new clicked div.

I have conditional style rendering, which works, except it does not remove the previously clicked div's color. It looks like it does not re-render. What did I do wrong?

import React from "react";
import ServiceItem from "../components/serviceItem";
import { Row } from "../utils/general";
import styled from "styled-components";
import ServicePVZ from "../assets/service.png";
import { SERVICES } from "../mocs/SERVICES";

const Column = styled.div`
  width: 50%;
`;

const ServiceSelection = () => {
  return (
    <div>
      <h1>Select service</h1>
      <Row>
        <Column>
          <img src={ServicePVZ} />
          {SERVICES.map((service, index) => (
            <ServiceItem
              name={service.name}
              price={service.price}
              key={index}
              id={index}
            />
          ))}
        </Column>
        <Column> 2 </Column>
      </Row>
    </div>
  );
};

export default ServiceSelection;

import React, { useState, useEffect } from "react";
import styled from "styled-components";

const ServiceBox = styled.div`
  width: 300px;
  height: 50px;
  border: 1px solid #f9f9f9;
  border-radius: 5px;
  margin-bottom: 10px;
  padding: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  :hover {
    border: 1px solid black;
  }
`;

const ServiceItem = ({ name, price, id }) => {
  const [active, updateActive] = useState(false);
  const [selectedValue, updateSelectedValue] = useState(0);

  const changeActiveService = id => {
    if (id > 0) {
      updateSelectedValue(id);
      console.log("Changed ID to " + id);
    } else {
      console.log("You clicked div with ID 0 ");
      updateSelectedValue(0);
    }
  };

  return (
    <>
      <ServiceBox
        onClick={() => changeActiveService(id)}
        style={{ backgroundColor: selectedValue === id ? "red" : "green" }}
      >
        <div>{id}</div>
        <div>{name}</div>
        <div>{price}</div>
      </ServiceBox>
    </>
  );
};

export default ServiceItem;

Upvotes: 0

Views: 72

Answers (2)

EVeras
EVeras

Reputation: 324

Right now you are using a state hook for each individual ServiceItem. What you should do instead is lift up the state to the parent component and simply pass an 'active' prop to the ServiceItem. Something like this:

import React, {useState} from "react";
import ServiceItem from "../components/serviceItem";
import { Row } from "../utils/general";
import styled from "styled-components";
import ServicePVZ from "../assets/service.png";
import { SERVICES } from "../mocs/SERVICES";

const Column = styled.div`
  width: 50%;
`;

const ServiceSelection = () => {
  const [active, updateActive] = useState(false);
  const [selectedValue, updateSelectedValue] = useState(0);

  const changeActiveService = id => {
    if (id > 0) {
      updateSelectedValue(id);
      console.log("Changed ID to " + id);
    } else {
      console.log("You clicked div with ID 0 ");
      updateSelectedValue(0);
    }
  };
  return (
    <div>
      <h1>Select service</h1>
      <Row>
        <Column>
          <img src={ServicePVZ} />
          {SERVICES.map((service, index) => (
            <ServiceItem
              name={service.name}
              price={service.price}
              key={index}
              id={index}
              active={selectedValue === index}
              onClick={changeActiveService}
            />
          ))}
        </Column>
        <Column> 2 </Column>
      </Row>
    </div>
  );
};

export default ServiceSelection;
import React, { useState, useEffect } from "react";
import styled from "styled-components";

const ServiceBox = styled.div`
  width: 300px;
  height: 50px;
  border: 1px solid #f9f9f9;
  border-radius: 5px;
  margin-bottom: 10px;
  padding: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  :hover {
    border: 1px solid black;
  }
`;

const ServiceItem = ({ name, price, id, active, onClick }) => {
  

  return (
    <>
      <ServiceBox
        onClick={onClick}
        style={{ backgroundColor: active ? "red" : "green" }}
      >
        <div>{id}</div>
        <div>{name}</div>
        <div>{price}</div>
      </ServiceBox>
    </>
  );
};

export default ServiceItem;

Upvotes: 1

Breno Prata
Breno Prata

Reputation: 732

You are handling the state on the wrong place, you should handle the state change on ServiceSelection component, the ServiceItem component should be a simple component that only displays data, receives the selected value and triggers a callback to the parent telling that a new option was selected

Upvotes: 1

Related Questions