Memes Generator in React

A project in Reactjs for Memes lovers (a.k.a true programmers)

In this project we will create a memes generator, for that we will make use of imgflip api. Here I have add a feature to download the memes but you can improve this project be adding features like share button, send additional parameters to change font and styling of text and anything else that comes in your mind (Do share it with me in the comments).

So to start with let's first divide what we have to do in steps.

  1. Display all the meme's image
  2. When clicked on a image open that image and display input boxes and buttons
  3. On submitting the input fields display the generated meme
  4. Allow user to download that meme.

Display all available images

To get all the memes we will use this API, send a GET request and store it in state named data. So in App.js lets get data using fetch in the useEffect hook.

  useEffect(()=>{
      if(data===null)
      {
        fetch('https://api.imgflip.com/get_memes')
        .then( (res) => res.json())
        .then( (respons) => {setData(respons.data.memes))
        setLoading(false)
      }
  },[data])

Now we will display the images in a grid by mapping our data using map().

return (
    <div className="app">
     {
        loading === true || data===null?
        <div className="loading">
          Loading
        </div>
        :
        <div className="container">
          {data.map((m,index)=>(
              <div className="card"  key={index}>
                <img src={m.url} alt={m.id} className="image"/>
              </div>
          ))}
        </div>
    }
    </div>
  );

The css will contain the following data:

* {
  padding: 0px;
  margin: 0px;
}

.loading {
  width: 100vh;
  text-align: center;
  margin: 100px;
  font-size: 20px;
}

.container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  padding: 50px;
  grid-auto-rows: 250px;
  grid-gap: 20px;
}

.card {
  border: 1px solid black;
  align-items: center;
  cursor: pointer;
}

.image {
  object-fit: cover;
  width: 100%;
  height: 100%;
}

@media (max-width: 800px) {
  .container {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 500px) {
  .container {
    grid-template-columns: 1fr;
  }
}

We are done with our first step.

When clicked on a image open that image and display input boxes and buttons

To complete this step we will need one more state that will store the meme user clicked and initialize it to null, then we need to add onClick to the div with className card in our App.js i.e onClick = {()=>{setMeme(m)}} Then if meme is null we will display the grid, else we will display the Meme with input boxes. So our App.js file will be modified to

import React, { useEffect, useState } from "react";
import "./style.css";
import Meme from './Meme'
export default function App() {
  const [data, setData] = useState(null);
  const [meme, setMeme] = useState(null);
  const [loading, setLoading] = useState(true)
  useEffect(()=>{
      if(data===null)
      {
        fetch('https://api.imgflip.com/get_memes')
        .then( (res) => res.json())
        .then( (respons) => {setData(respons.data.memes)
        })
        setLoading(false)
      }
  },[data])


  return (
    <div className="app">
     {
        loading === true || data===null?
        <div className="loading">
          Loading
        </div>
        :

        meme !== null?
        <div>
          <Meme meme={meme} setMeme={setMeme}/>
        </div>
        :
        <div className="container">
          {data.map((m,index)=>(
              <div className="card" onClick = {()=>{setMeme(m)}} key={index}>
                <img src={m.url} alt={m.id} className="image"/>
              </div>
          ))}
        </div>
    }
    </div>
  );
}

We now need to create a Meme.js file and display the meme data in it. Our meme object consist of different parameters which include id, name, url, height, width and box_count. Among these parameters we will need url and box_count to display the image and input boxes respectively. So Meme.js file initially will contain the following code :-


const Meme = ({meme, setMeme})=>{

  return(
      <div className="meme-container">
        <h1>Meme</h1>
          <div>
          <img src={meme.url} alt={meme.id} className="img"/>
          {
          [...Array(meme.box_count)].map((_, index) =>(
            <input 
            placeholder={`Enter Input ${index+1}`}
              className="input" 
              key={index}
            />
          ))
        }
        <div>
        <button>Generate Meme</button>
        <button>Back</button>
        </div>
        </div>
      </div>
  )
}

Here we used [...Array(meme.box_count)].map() because we need box_count number of input boxes and box_count is an integer so we will not be able to map it. Instead of this you can make use of for loop too.

On submitting the input fields display the generated meme

We need to add our logic to make the Generate meme and back button functional. Call generateMeme() function onClicking Generate meme button and for the back button we will simply set meme state to null which will render the images grid as per logic we wrote in App.js earlier.

To complete generateMeme() function we will need to generate a state, lets call it inp, this state will hold the data that we need to pass as parameters in our request. So the state will look like

const [inp, setInp] = useState({
    template_id: meme.id,
    username: "username",
    password: "password",
    boxes: []
  })

Here you can use your own username and password. The boxes will store the content of input boxes. To do so we need to add onChange event to input boxes which will add the data to boxes on Input change.

 <input 
            placeholder={`Enter Input ${index+1}`}
              className="input" 
              key={index}
              onChange={(e) => {
                const newBoxes = inp.boxes;
                newBoxes[index] = { text: e.target.value };
                setInp({ ...inp, boxes: newBoxes });
              }}
            />

Once we are ready with our data we will pass it in the POST request we will call in generateMeme()

  const generateMeme = ()=>{
    setLoading(true)
    let url = `https://api.imgflip.com/caption_image? template_id=${inp.template_id}&username=${inp.username}&password=${inp.password}`;

    inp.boxes.map((box, index) => {
      url += `&boxes[${index}][text]=${box.text}`;
    });

    fetch(url)
    .then((res) => res.json())
    .then((data) => {
      setMeme({ ...meme, url: data.data.url })
      setLoading(false)
    })
    .catch(error => {
       // ...handle/report error
       console.log("error")
    });
  }

Now lets understand what we did in our generateMeme function. We added url "api.imgflip.com/caption_image" and different parameters like template_id, username and password to it. We are left with adding boxes parameter, but we have stored it as array of Objects and we will need it in format 'boxes[0][text]=Input' So in boxes.map() we convert it in desired format and append to url. Then we call fetch request and update Meme url with the url we received. This completes the meme-generator app but we will add one more functionality to it .i.e download the generated image, keep reading if you want to add that too.

Download the generated meme

We will add a Download button with the two buttons we already have (Generate meme and back button). Add onClick event to the download button which will call the downloadMeme function. In the generateMeme function we will set Download with the url we received and the downloadMeme function will be defined as follows

const downloadMeme = ()=> {
  fetch(download, {
    method: "GET",
    headers: {}
  })
    .then(response => {
      response.arrayBuffer().then(function(buffer) {
        const url = window.URL.createObjectURL(new Blob([buffer]));
        const link = document.createElement("a");
        link.href = url;
        link.setAttribute("download", "meme.png"); 
        document.body.appendChild(link);
        link.click();
      });
    })
    .catch(err => {
      console.log(err);
    });
  }

In this downloadMeme function we are passing the url link to fetch and then with the response we received we are creating a blob and adding it as href to anchor tag. So the final code in our Meme.js will be

import React, {useState, useEffect} from 'react'
import "./style.css";

const Meme = ({meme, setMeme})=>{
  const [inp, setInp] = useState({
    template_id: meme.id,
    username: "digpog",
    password: "ImageGenerator",
    boxes: []
  })

  const [download, setDownload] = useState(null)
  const [loading, setLoading] = useState(false)
  const generateMeme = ()=>{
    setLoading(true)
    let url = `https://api.imgflip.com/caption_image?template_id=${inp.template_id}&username=${inp.username}&password=${inp.password}`;
    inp.boxes.map((box, index) => {
      url += `&boxes[${index}][text]=${box.text}`;
    });
    fetch(url)
    .then((res) => res.json())
    .then((data) => {
      setMeme({ ...meme, url: data.data.url })
      setDownload(data.data.url)
      setLoading(false)
    })
    .catch(err => {
      console.log(err);
    });
  }
const downloadMeme = ()=> {
  fetch(download, {
    method: "GET",
    headers: {}
  })
    .then(response => {
      response.arrayBuffer().then(function(buffer) {
        const url = window.URL.createObjectURL(new Blob([buffer]));
        const link = document.createElement("a");
        link.href = url;
        link.setAttribute("download", "meme.png"); 
        document.body.appendChild(link);
        link.click();
      });
    })
    .catch(err => {
      console.log(err);
    });
  }

  return(
      <div className="meme-container">
        <h1>Meme</h1>
        {loading===true? <div>Generating your Meme</div>: <div>
          <img src={meme.url} alt={meme.id} className="img"/>
        {
          [...Array(meme.box_count)].map((_, index) =>(
            <input 
            placeholder={`Enter Input ${index+1}`}
              className="input" 
              key={index}
              onChange={(e) => {
                const newBoxes = inp.boxes;
                newBoxes[index] = { text: e.target.value };
                setInp({ ...inp, boxes: newBoxes });
              }}
            />
          ))
        }
        <div>
        <button onClick={()=>{generateMeme()}}>Generate Meme</button>
        <button onClick={()=>{setMeme(null)}}>Back</button>
        {download === null?<></>: <button onClick={e => downloadMeme(e)}>Download</button>}
        </div>
        </div>
        }

      </div>
  )
}
export default  Meme;

You can view the code and demo using this stackblitz project. Thank you for reading and hoped yo enjoyed it, do let me know if there are any changes/improvements in the comments.

Did you find this article valuable?

Support Shweta by becoming a sponsor. Any amount is appreciated!