Search code examples
javascriptreactjsjquery-isotope

Reactjs - Isotope - expand and collapse on click - but have ONLY one item at a time


I am working on an isotope application - and I want to smooth this application so that onclick an item expands -- but on clicking another item - it closes the last -- here is the code base - what is the best way of handling this. My problem at the moment is its possible to open up multiple cells at once - and I haven't worked out the logic to collapse non-active items.

enter image description here

https://codesandbox.io/s/brave-sea-tnih7?file=/src/IsotopePopClickEl.js

IsotopePopClickEl.

import React, { Component } from "react";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";
//import Rating from '@material-ui/lab/Rating';

//import parse from 'html-react-parser';

import "./IsotopePopClickEl.css";

class IsotopePopClickEl extends Component {
  constructor(props, context) {
    super(props, context);
    this.state = { expanded: false };
    this.onClick = this.onClick.bind(this);
  }

  expand() {
    this.setState({
      expanded: true
    });
  }

  collapse(event) {
    this.setState({
      expanded: false
    });
  }

  onClick() {
    if (this.state.expanded) {
      this.collapse();
    } else {
      this.expand();
    }
  }

  render() {
    return (
      <div
        className={
          "isotope-pop-click " +
          (this.state.expanded ? "is-expanded" : "is-collapsed")
        }
        onClick={this.onClick}
      >
        <div className="frontFace">
          <Grid container spacing={0}>
            <Grid item xs={12} sm={12}>
              <div className="image-wrapper">
                <img
                  className="image-item"
                  src={this.props.item.image}
                  alt=""
                />
              </div>
              <h3>{this.props.item.label}</h3>
            </Grid>
          </Grid>
        </div>

        <div className="backFace">
          <Grid container spacing={0}>
            <Grid item xs={12} sm={5}>
              <div className="image-wrapper">
                <img
                  className="image-item"
                  src={this.props.item.image}
                  alt=""
                />
              </div>
            </Grid>
            <Grid item xs={12} sm={7}>
              <h3>{this.props.item.label}</h3>
              <div>
                <p>some body text text text text</p>
              </div>
              <div>
                <Button variant="contained" color="primary">
                  Read more
                </Button>
              </div>
            </Grid>
          </Grid>
        </div>
      </div>
    );
  }
}

export default IsotopePopClickEl;

I wasn't sure what is the easier way to handle this -- if its a case of pushing back an active item record into the parent shell IsotopeWrapper - and if a new click is picked up to close all other IsotopePopClickEl's --


Solution

  • I have to make bunch of changed to your codesandbox as there are few issues with existing code. Updated and working codesandbox can be found here.

    Let's go through the changes I made:

    1. Notify parent/container as soon as item is clicked so that parent can update state of other items. To do that, I removed state from IsotopePopClickEl and moved it to IsotopeWrapper where wrapper would set selected property on each item to indicate if it is selected (or you can call it expanded!) or not.
    class IsotopePopClickEl extends Component {
      ...
      onClick() {
        this.props.onClick();
      } 
    
      render() {
        return (
          <div
            className={
              "isotope-pop-click " +
              (this.props.item.selected ? "is-expanded" : "is-collapsed")
            }
            onClick={this.onClick}
          >
          ...
        );
      }
    }
    
    1. Update state (selected) of each item so that IsotopePopClickEl can determine which item to expand to:
    class IsotopeWrapper extends Component {
      constructor(props, context) {
        super(props, context);
        this.state = {
          resultsList: [
            // items in the list
          ]
        };
      }
    
      onClick(item) {
        const newList = this.state.resultsList.map((i) => {
          i.selected = i === item;
          return i;
        });
        this.setState({
          resultsList: newList
        });
      }
    
      render() {
        let items = [
          {
            buildEntity: () => ( // <- converted entity prop to method
              <IsotopePopClickEl
                item={this.state.resultsList[0]} // <- use item from the state
                onClick={() => this.onClick(this.state.resultsList[0])} // <- attach click handler
              />
            ),
            ...
          }
        ...
      }
    }
    
    1. Finally, not sure if you noticed but I replaced entity property on each item in IsotopeWrapper class with buildEntity function as using property would cache view after creating it for the first time and will not update after wards on new state. Update IsotopeHandler to call buildEntity.
    <div className="grid-item-wrapper">{item.buildEntity()}</div>
    

    That's all!