metafizzy / packery

:bento: Gapless, draggable grid layouts
https://packery.metafizzy.co
4.13k stars 315 forks source link

Packery on React #519

Open dylannirvana opened 5 years ago

dylannirvana commented 5 years ago

Hi David, I love your work. Trying to use Packery in React via https://github.com/eiriklv/react-packery-component . Got it very close but am getting a TypeError. And I am using a different pattern than he has in github. I am holding the array object in state and rendering it by calling renderData().

    import React, { Component } from 'react'
    import { 
        Card, 
        CardImg, 
        // CardText, 
        CardBody,
        CardTitle, 
        // CardSubtitle, 
        // Button, 
        Input, 
        InputGroup, 
        // Collapse, 
        // Navbar, 
        // NavbarToggler, 
        // NavbarBrand, 
        // Nav, 
        // NavItem, 
        // NavLink, 
        Container, 
        Row, 
        // Col, 
        Jumbotron 
        } from 'reactstrap';
    import Papa from 'papaparse'
    // import Packery from 'packery'
    import 'bootstrap/dist/css/bootstrap.min.css'
    import './App.css'

    class App extends Component {
        constructor(props) {
            super(props);
            this.state = {data: [] };   
            this.handleChange = this.handleChange.bind(this);
            this.updateData = this.updateData.bind(this)
        }

        handleChange(event) {
            event.preventDefault()
            const inventory = event.target.files[0]
            Papa.parse(inventory, {
                header: true,
                complete: this.updateData
            })
        } // END

        updateData(results) {
            const data = results.data
            console.log(data)
            this.setState({data}) // I have it in state. How to get it in UI?
        }

        renderData() {
            return  this.state.data.length > 1 
               ? this.state.data.map((item) => (
                   <Card key={item.sku} >  
                        <CardImg src={item.image} />
                        <CardTitle> {item.sku} </CardTitle>
                        <CardBody> {item.name} </CardBody>
                   </Card>
               )) 
               : null         
        }

        render() {
            return (
                <div>
                    <Jumbotron>
                        <form >
                            <InputGroup>
                                Name:
                                <Input type="file" onChange={this.handleChange} />
                            </InputGroup>
                        </form>
                    </Jumbotron>

                    <div className="album">
                        <Container>
                                <Row> {this.renderData()} </Row> 
                        </Container>
                        {/* <Packery> {this.renderData()} </Packery> */}
                    </div>
                </div>          
            );
        }
    } // END

    export default App
desandro commented 5 years ago

Hello! Thanks for reporting this issue.

Alas, I do not React expertise so I don't have an answer here. Maybe someone else can chime in?

lextoc commented 5 years ago

I wouldn't complicate things and stick to the original packery package that gets frequent updates. If you want to set it up in React you can do something like this:

(raw paste of code in progress... everything helps right?)

import React, { Component } from 'react';
import Packery from 'packery';
import Draggabilly from 'draggabilly';

import ProductCard from './cards/ProductCard';

const COMPONENT_NAME = 'StyleGuide';

/** Class representing the styleguide. */
export default class StyleGuide extends Component {
  constructor(props) {
    super(props);
    this.packeryContainer = React.createRef();
    this.initializePackery = this.initializePackery.bind(this);
    this.handleDragItemPositioned = this.handleDragItemPositioned.bind(this);
    this.runPackery = this.runPackery.bind(this);
  }

  /**
   * Create packery instance and bind draggabilly to each `.js-col`.
   * @function componentDidMount
   */
  componentDidMount() {
    this.initializePackery();
    this.packery.on('dragItemPositioned', this.handleDragItemPositioned);
  }

  /**
   * Remove event listeners and destroy packery instance.
   * @function componentWillUnmount
   */
  componentWillUnmount() {
    // TODO: test this
    this.packery.unbindDraggabillyevents();
    this.packery.packery('destroy');
  }

  /**
   * Creates a packery instance and creates a
   * draggabilly instance for each packery item element.
   * @function initializePackery
   */
  initializePackery() {
    this.packery = new Packery(this.packeryContainer.current, {
      columnWidth: '.js-col',
      itemSelector: '.js-col',
      percentPosition: true,
    });
    this.packery.getItemElements().forEach((itemElem) => {
      const draggie = new Draggabilly(itemElem, {
        containment: '.js-row',
        handle: '.js-drag-handle',
      });
      this.packery.bindDraggabillyEvents(draggie);
    });
  }

  /**
   * Pass this function to cards to execute when image is loaded
   * so that packery runs again and properly positions everything.
   * @function runPackery
   */
  runPackery() {
    this.packery.shiftLayout();
  }

  /**
   * Renders all HTML elements with their default styling
   * and all reuseable components.
   * @function render
   */
  render() {
    return (
      <main className={`${COMPONENT_NAME}`}>
        <div className={`${COMPONENT_NAME}__content`}>
          <h1>
            Cards
          </h1>
          <p>
            Cards ordered in a grid with packery as they will be displayed on the pages.
            The images serve as a grab handle for dragging.
          </p>
          <section className="row row--with-padding js-row" ref={this.packeryContainer}>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-1">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-2">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-3">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-4">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-5">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-6">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-7">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-8">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-9">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-10">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-11">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
            <div className="col-12 col-sm-md-8 col-lg-xl-6 col--with-padding js-col js-holder-12">
              <ProductCard executeOnResize={this.runPackery} />
            </div>
        </div>
      </main>
    );
  }
}

ProductCard:

import React, { Component } from 'react';

import LikeButton from '../LikeButton';
// import addToCartIcon from '../../assets/images/shared/add-to-cart-icon.svg';

const COMPONENT_NAME = 'ProductCard';

/** Class representing a product card. */
export default class ProductCard extends Component {
  /**
   * Construct the `ProductCard`.
   * `executeOnResize` may be passed as prop for ProductCard's displayed
   * in a packery grid. Execute this function each time the card resizes.
   * @function contructor
   */
  constructor(props) {
    super(props);
    this.state = {
      isImageLoading: true,
    };
    this.handleImageLoad = this.handleImageLoad.bind(this);
    /** TODO: Below is temporary! */
    this.imageSrc = `https://picsum.photos/500/${400 + Math.random() * 1000}/?random`;
  }

  /**
   * Check whether component has resized and `executeOnResize` has to be fired.
   * @function componentDidUpdate
   */
  componentDidUpdate(prevProps, prevState) {
    const { isImageLoading: wasImageLoading } = prevState;
    const { isImageLoading } = this.state;
    const { executeOnResize } = this.props;
    if (wasImageLoading !== isImageLoading && typeof executeOnResize === 'function') {
      executeOnResize();
    }
  }

  /**
   * Update state accordingly when image is loaded.
   * @function handleImageLoad
   */
  handleImageLoad() {
    this.setState({
      isImageLoading: false,
    });
  }

  render() {
    const { isImageLoading } = this.state;

    return (
      <article className={`${COMPONENT_NAME}${isImageLoading ? ` ${COMPONENT_NAME}--image-loading` : ''}`}>
        <div className={`${COMPONENT_NAME}__inner`}>
          <div className={`${COMPONENT_NAME}__LikeButton`}>
            <LikeButton />
          </div>
          <header className={`${COMPONENT_NAME}__header`}>
            <section className={`${COMPONENT_NAME}__content`}>
              <p className={`${COMPONENT_NAME}__name`}>ELISHA EXTRA SLIM FIT - Zakelijk overhemd</p>
              <p className={`${COMPONENT_NAME}__brand`}>Hugo BOSS</p>
            </section>
            <section className={`${COMPONENT_NAME}__financial`}>
              <p className={`${COMPONENT_NAME}__price`}>€79,95</p>
            </section>
            <section className={`${COMPONENT_NAME}__actions`}>
              <button
                className={`${COMPONENT_NAME}__add-to-cart`}
                type="button"
              >
                Add to basket
                <img
                  className={`${COMPONENT_NAME}__add-to-cart-icon`}
                  alt="Add to basket"
                  style={{ display: 'none' }}
                  // src={addToCartIcon}
                />
              </button>
            </section>
          </header>
          <figure className={`${COMPONENT_NAME}__image-wrapper js-drag-handle`}>
            <img
              src={this.imageSrc}
              alt="Name of product"
              onLoad={this.handleImageLoad}
            />
          </figure>
        </div>
      </article>
    );
  }
}