Search code examples
javascriptreactjssetstatereact-context

React changing the states of the whole elements in the map function, not only the button that is pressed


Hello everyone, I am trying to passing a method through a context api component to another component which, i have a map function there. I want my showInfo state changes to true or false depending on the button clicking, when i clicked the button, all the showInfo's of my states is changes, so thats not what i want, I want that specific item to change when i press to it. Can someone explaine where is the mistake that i've made?

MY CONTEXT APİ

import React from "react";

export const ToursContext = React.createContext();

class ToursContextProvider extends React.Component {
  constructor(props) {
    super(props);
    this.changeState = this.changeState.bind(this);
    this.state = {
      tours: [
        {
          id: 0,
          imageURL:
            "https://images.unsplash.com/photo-1524231757912-21f4fe3a7200?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1351&q=80",
          title: "İstanbul'un Güzelliğinin Sadece Bir Parçası Galata Kulesi",
          showInfo: true,
          info: "LOREM İPSUM AMET 1",
        },
        {
          id: 1,
          imageURL:
            "https://images.unsplash.com/photo-1541432901042-2d8bd64b4a9b?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=1319&q=80",
          title: "Tarihi Süleymaniye Camii",
          showInfo: true,
          info: "LOREM İPSUM AMET 2",
        },
      ],
    };
  }

  changeState(itemdelete) {
    this.setState({
      showInfo: !this.state.showInfo,
    });
    console.log(itemdelete);
  }

  render() {
    return (
      <ToursContext.Provider
        value={{ ...this.state, changeState: this.changeState }}
      >
        {this.props.children}
      </ToursContext.Provider>
    );
  }
}

export default ToursContextProvider;

MY MAP LIST COMPONENT

import React from "react";
import { ToursContext } from "../contexts/Tours";

function Tours() {
  return (
    <div className="container">
      <div className="row">
        <ToursContext.Consumer>
          {(value) => {
            const { changeState } = value;

            return value.tours.map((item) => (
              <div className="col-md-4" key={item.id}>
                <div className="card bg-dark text-white">
                  <img src={item.imageURL} className="card-img" alt="..." />
                  <div className="card-img-overlay">
                    <h5 className="card-title">{item.title}</h5>
                    <button
                      type="button"
                      onClick={changeState.bind(this, item)}
                      className="btn-sm btn-primary"
                    >
                      Bilgiyi Göster!
                    </button>
                  </div>
                  {value.showInfo ? "true" : "false"}
                </div>
              </div>
            ));
          }}
        </ToursContext.Consumer>
      </div>
    </div>
  );
}

export default Tours;

Solution

  • You state is atomic. This means that it is treated as a single value. With classes, you have option to modify state object partially. For example, you have object with fields a and b. You can change both fields at once, only a or only b. But there is no option to modify state deeply. Let's imagine that you have state object like this:

    {
      "a": { "subfield_1": [], "subfield_2": "some string"},
      "b": 3
    }
    

    You again, can modify a or b, but if you want to add item into array a.subfield_1 or change a.subfield_2, you will have to modify whole a, like this:

    setState({
      a: {
        ...a,
        subfield_1: this.state.a.subfield_1.concat("new item"),
      },
    });
    

    In you case, to change something inside tours key, you will have to modify whole tours key. It would be something like this:

    changeState(itemdelete) {
      this.setState({
        tours: tours.map((item) =>
          item.id !== itemdelete.id ? item : { ...item, showInfo: !item.showInfo }
        ),
      });
    }