Skip to content Skip to sidebar Skip to footer

React.js And Isotope.js

I'm checking out React.js and trying to figure out how this library can work together with Isotope.js. The documentation of React says that it plays nicely with other libraries, bu

Solution 1:

Here's a working version with Masonry, you should find it easy enough to port to Isotope (or use Masonry :)) http://jsfiddle.net/emy7x0dc/1/.

Here's the crux of the code that makes it work (and allow React to do its job).

var Grid = React.createClass({
    displayName: 'Grid',

    getInitialState: function(){
        return {
            masonry: null
        }
    },

    // Wrapper to layout child elements passed in
    render: function () {
        var children = this.props.children;
        return (
            <div className="grid">
                {children}
            </div>
        );
    },

    // When the DOM is rendered, let Masonry know what's changed
    componentDidUpdate: function() {
        if(this.state.masonry) {
            this.state.masonry.reloadItems();
            this.state.masonry.layout();
        }
    },

    // Set up Masonry
    componentDidMount: function() {
        var container = this.getDOMNode();
        if(!this.state.masonry) {
            this.setState({
                masonry: new Masonry( container )
            });
        } else {
            this.state.masonry.reloadItems();
        }
    }
});

Solution 2:

Here's an updated version of the above code posted by James:

import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import Isotope from 'isotope-layout';

// Container for isotope grid
class ItemGrid extends PureComponent {
    constructor(props) {
        super(props);
        this.state = { isotope: null };
    }

    render() {
        return(
            <div className="item-grid">
                {this.props.children}
            </div>
        )
    }

    // set up isotope
    componentDidMount() {
        const node = ReactDOM.findDOMNode(this);
        if (!this.state.isotope) {
            this.setState({
                isotope: new Isotope( node )
            });
        } else {
            this.state.isotope.reloadItems();
        }
    }

    // update isotope layout
    componentDidUpdate() {
        if (this.state.isotope) {
            this.state.isotope.reloadItems();
            this.state.isotope.layout();
        }
    }
}

export default ItemGrid;

Usage:

Just pass the items you want to keep inside isotope into the ItemGrid component as children:

<ItemGrid>
    {data.map(object => (
      <Item key={object._id} name={object.name} imageUrl={object.imageUrl} />
    ))}
</ItemGrid>

Alternatives

If you can, consider using react-masonry-component.


Solution 3:

My solution with useRef, useState and useEffect hooks. It also works with dynamically generated filter keys and items. The trick is to initialize Isotope after the component is mounted and call its "arrange" method every time the filter keyword changes.

Demo: https://codepen.io/ilovepku/pen/zYYKaYy

const IsotopeReact = () => {
  // init one ref to store the future isotope object
  const isotope = React.useRef()
  // store the filter keyword in a state
  const [filterKey, setFilterKey] = React.useState('*')

  // initialize an Isotope object with configs
  React.useEffect(() => {
    isotope.current = new Isotope('.filter-container', {
      itemSelector: '.filter-item',
      layoutMode: 'fitRows',
    })
    // cleanup
    return () => isotope.current.destroy()
  }, [])

  // handling filter key change
  React.useEffect(() => {
    filterKey === '*'
      ? isotope.current.arrange({filter: `*`})
      : isotope.current.arrange({filter: `.${filterKey}`})
  }, [filterKey])

  const handleFilterKeyChange = key => () => setFilterKey(key)

  return (
    <>
      <ul>
        <li onClick={handleFilterKeyChange('*')}>Show Both</li>
        <li onClick={handleFilterKeyChange('vege')}>Show Veges</li>
        <li onClick={handleFilterKeyChange('fruit')}>Show Fruits</li>
      </ul>
      <hr />
      <ul className="filter-container">
        <div className="filter-item vege">
          <span>Cucumber</span>
        </div>
        <div className="filter-item fruit">
          <span>Apple</span>
        </div>
        <div className="filter-item fruit">
          <span>Orange</span>
        </div>
        <div className="filter-item fruit vege">
          <span>Tomato</span>
        </div>
      </ul>
    </>
  )
}

UPDATE (17/11/21):

TypeScript Demo: https://codesandbox.io/s/react-isotope-typescript-i9x5v

Don't forget to also add @types/isotope-layout as a dev dependency.


Solution 4:

You can manipulate the dom directly inside React. This permits to integrate existing JS libraries or for custom needs not handled well by React.

You can find an exemple here:

https://github.com/stample/gulp-browserify-react-phonegap-starter/blob/master/src/js/home/homeComponents.jsx#L22

And here's what it looks like:

image


The problem with integration of React and a library like Isotope is that you will end up having 2 different libraries trying to update the same dom subtree. As React work with diffs, it kind of assumes that it is alone modyfing the dom.

So the idea could be to create a React component that will render only one time, and will never update itself. You can ensure this with:

shouldComponentUpdate: function() { 
    return false; 
}

With this you can:

  • Use React to generate your isotope item html elements (you can also create them without React)
  • On componentDidMount, initialize isotope on the dom node mounted by React

And that's all. Now React will never update this part of the dom again, and Isotope is free to manipulate it like it wants to without interfering with React.

In addition, as far as I understand, Isotope is not intented to be used with a dynamic list of items so it makes sense to have a React component that never updates.


Solution 5:

Updated Hubert's answer to modern react with Hooks.

import React, { useEffect, useRef, useState } from 'react';
import Isotope from 'isotope-layout';

function IsoContainer (props) {
    const isoRef = useRef();
    const [isotope, setIsotope] = useState(null);

    useEffect(() => {
        if (isotope)
            isotope.reloadItems();
        else
            setIsotope(new Isotope( isoRef.current ));
    })

    return (
        <div ref={isoRef}>
            {props.children}
        </div>
    )
}

export default IsoContainer

Edit: Coming back to Isotope after not using for a few months. Using a container component as above isn't the best practice for isotope. There are functions that exist on the isotope object that are needed, you also need to set the option in the new Isotope(ref, options) function, AND if you need to style the div, it's a little awkward to come back to this component.

It seems a better practice is to instead place the code within this component, into any component you are using isotope in. This way you a) have easy access to the isotope object, b) you can more easily style the container Div, and c) you can more easily pass and edit the isotope options.

You can of course keep the container as it is, though it becomes necessary to lift the state up, making this component a little unnecessary.


Post a Comment for "React.js And Isotope.js"