Tuesday, 26 February, 2019 UTC


Summary

Last week on the code challenge we set out to build an image gallery with Infinte Scroll and consuming the Unsplash API. You can complete the challenge here if you haven't.
In this post, we shall solve this challenge using React.js, a popular Frontend JavaScript library. Also, Chris solved the challenge live on Twitch, you can also see it in the banner media of this post.
In his solution he built out a simple backend node server to handle all the API requests from Unsplash, thereby protecting the API keys. Also, he used tools like Styled components and CSS grid which make building out awesome interfaces really simple.
For this post, we shall solve the challenge simply using CSS grid as well.
The Challenge
We are required to build out the image gallery using the base codepen provided, which includes basic HTML to house and style the images repectively. Also the API endpoint to fetch random images was provided. You can find the base code here.
The Solution
To solve this challenge, we would use React.js to build out the interface, utilize Axios to make the HTTP requests, and use the react-infinite-scroll library to implement the infinite scroll feature. We shall do these in 5 steps:
  1. Install all required packages
  2. Build the base component to house the images
  3. Build a simple image component
  4. Fetch the random images from Unsplash and render the images
  5. Style the gallery
Also, for this challenge, we'll employ React Hooks, which shipped in react 16.8, thus, using functional components throughout the project.

Install required packages

Import all required dependencies from a CDN. Codepen provides a search bar with which you can type in the nameof the module and you can select one from the result and it adds it to your project. Dependencies installed are:
  • React
  • React-DOM
  • React-Infinite-Scroll-Component
  • Axios
Go on to Unsplash to create an application and obtained an access key.

Build the base component

In React, the HTML template for the parent component is written in JSX, we shall proceed to write these HTML elements which make the template, in JSX. Create a functional component and render on the DOM with:
let Collage = () => {

    // Return JSX
  return (
    <div className="hero is-fullheight is-bold is-info">
      <div className="hero-body">
        <div className="container">
          <div className="header content">
            <h2 className="subtitle is-6">Code Challenge #16</h2>
            <h1 className="title is-1">
              Infinite Scroll Unsplash Code Challenge
            </h1>
          </div>
    // Images go here

        </div>
      </div>
    </div>
  );
};

// Render the component to the DOM element with ID of root
ReactDOM.render(<Collage />, document.getElementById("root"));
Here, you simply created the parent compoenent Collage , returned the HTML elements in JSX and rendered it in the DOM element with id of root. Bulma classes were used to provide basic styling of the page.

Build a single image component

We move on to create a single component for a single image fetched. This would help us set the position of each image. Create a single functional component with:
const UnsplashImage = ({ url, key }) => (
  <div className="image-item" key={key} >
    <img src={url} />
  </div>
);
This component receives props of url and key which are the URL of the image to be displayed and the key for each rendered image. In the compoennt we use the <img/> element to display the fetched image.

Fetch random images from unsplash and render images

Unsplash provides a free API to fetch random images from which we'll use. The images will be stored in a state container and passed to the DOM from state. Since we'll use react hooks, we'll handle state and lifecycle methods with useState and useEffect respectively. In the Collage component, create two state variables, one to hold the incoming images and the other to store a boolean if the images are loaded or not.
[...]

const [images, setImages] = React.useState([]);
const [loaded, setIsLoaded] = React.useState(false);

[...]
Next, we'll create a function which fetches 10 random images using Axios. This is done by making a GET request to the API endpoint whilst passing your obtained access key and the amount of images you want returned. Do this with:
const fetchImages = (count = 10) => {
    const apiRoot = "https://api.unsplash.com";
    const accessKey = "{input access key here}";

    axios
      .get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
      .then (res => {
        setImages([...images, ...res.data]);
        setIsLoaded(true);
      });
};
Axios is a promise based library and on the resolution of the request, we use the setImages method to fill in the images fetched as well as spread any previous images fetched. Also, we set the value of loaded to true.
Now we have images stored in state, let's call this fetchImages function once the component in mounted. Previously, we would do this with the componentDidMount lifecycle method. However, React provides the useEffect hook to handle all lifecycle operations in a functional component. In the Collage component, call fetchImages on mount with:
[...]

React.useEffect(() => {
    fetchImages();
}, []);

[...]
The useEffect hook takes a second parameter which is an array. The provided function in the hook will run for everytime the array is updated or modified.
Now you have an page which fetches ten random images from Unsplash. Let's proceed to render the images in an infinite scroll container.
React-infinite-scroll-component is a simple component which we imported earlier and provides the ability to display a loading spinner or any element as a placeholder, call a function to fetch more data once the loader is in view or apporaches view, and display any specified data. In the returned JSX of Collage and after the div with a class of header, render the images in the infinite scroll component with:
<InfiniteScroll
     dataLength={images}
     next={() => fetchImages(5)}
     hasMore={true}
     loader={
      <img
         src="https://res.cloudinary.com/chuloo/image/upload/v1550093026/scotch-logo-gif_jq4tgr.gif"
         alt="loading"
      />}
 >
     <div className="image-grid" style={{ marginTop: "30px" }}>
        {loaded ? 
            images.map((image, index) => (
                <UnsplashImage url={image.urls.regular} key={index} />
            )): ""}
    </div>
</InfiniteScroll>
In the InfiniteScroll component, we passed a function to the next parameter. The function simply calls the fetchImages function and passes a parameter of 5 which is the number of images to be fetched subsequently. In the loader parameter, we passed an image in JSX to serve as the loading placeholder.
.map() is used to iterate through the images array in state and renders each image using the UnsplashImage component.

Style the gallery

For this we'll use CSS grid to stlye the images fetched. Edit the CSS to this:
.title {
  font-family: 'Carter One';
}
.container {
  margin-top: 50px;
  text-align: center;
}

.image-grid {
  display: grid;
  grid-gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  grid-auto-rows: minmax(50px, auto);

  .image-item:nth-child(5n){
    grid-column-end: span 2;
  }

  img{
    display: flex;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}
This creates a grid with columns having a width of 250px and fills the entire image container. Also the rows are set to have a minimum of 50px and a maximum of auto to fit each image. To create a masonry, we used the grid-column-end property on the every 5th image to make it take up 2 image space instead of 1.
The object-fit property ensures each image fits or contains the full size of its container.
You can find the completed gallery here https://codepen.io/Chuloo/full/BMPXqy.
Conclusion
In this solution post we built out an image gallery using React hooks, as well as using CSS grid. You can try to play around with the grid to create an even better masonry. Watch this video on Youtube to see the solution as well. Watch out for the next challenge. Happy coding!