Do note that not a lot of things will be new here; the bulk of it is still doable in a standard ReactJS setup, or even NodeJS.
To begin, I will assume that NextJS has been installed on your machine. We'll create the app with this command. "easter-poem-generator" is the name of the app, and consequently, the folder that it will be created in.
npx create-next-app@latest easter-poem-generator
The script will ask some questions. These are the options I have chosen.
Would you like to use TypeScript? No
Would you like to use ESLint? No
Would you like to use Tailwind CSS? No
Would you like your code inside a `src/` directory? Yes
Would you like to use App Router? (recommended) Yes
Would you like to use Turbopack for `next dev`? No
Would you like to customize the import alias (`@/*` by default)? No
Once done, navigate to the src folder. We'll make some changes to the layout.
src/app/layout.js
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata = {
title: "Easter Poem Generator",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable}`}>
{children}
</body>
</html>
);
}
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata = {
title: "Easter Poem Generator",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable}`}>
{children}
</body>
</html>
);
}
Next, in the public directory, add these images.
![]() |
public/symbol_bunny.jpg |
![]() |
public/symbol_chicks.jpg |
![]() |
public/symbol_cross.jpg |
![]() |
public/symbol_eggs.jpg |
![]() |
public/symbol_lillies.jpg |
Time to create the form. We start with "use client" to state that this file is only for front-end JavaScript. Since JavaScript is being used for both front and back ends of the application, it's important to specify because this determines what features of JavaScript you'll get to use in the file.
src/app/page.js
"use client"
Then, because we have links in this page, we'll import the Link component. We will also import styles that have already been compiled. Finally, we will import useState because we will be using state variables.
src/app/page.js
"use client"
import Link from "next/link";
import styles from "./page.module.css";
import { useState } from "react";
import Link from "next/link";
import styles from "./page.module.css";
import { useState } from "react";
Next, we create a main function for this page. We will create the return statement in advance.
src/app/page.js
"use client"
import Link from "next/link";
import styles from "./page.module.css";
import { useState } from "react";
export default function Home() {
return (
);
}
import Link from "next/link";
import styles from "./page.module.css";
import { useState } from "react";
export default function Home() {
return (
);
}
Now, if you know ReactJS at all, this should be familiar ground. This is why we imported useState earlier, so we can set state variables. The first thing we want is form data. Thus, formData will be an object containing these properties. As default, only symbol_bunny is not an empty string.
src/app/page.js
export default function Home() {
const [formData, setFormData] = useState({
symbol_bunny: "bunny",
symbol_chicks: "",
symbol_cross: "",
symbol_eggs: "",
symbol_lillies: ""
});
return (
);
}
const [formData, setFormData] = useState({
symbol_bunny: "bunny",
symbol_chicks: "",
symbol_cross: "",
symbol_eggs: "",
symbol_lillies: ""
});
return (
);
}
And then we have generation, which is an object containing the property poem.
src/app/page.js
export default function Home() {
const [formData, setFormData] = useState({
symbol_bunny: "bunny",
symbol_chicks: "",
symbol_cross: "",
symbol_eggs: "",
symbol_lillies: ""
});
const [generation, setGeneration] = useState({
poem: ""
});
return (
);
}
const [formData, setFormData] = useState({
symbol_bunny: "bunny",
symbol_chicks: "",
symbol_cross: "",
symbol_eggs: "",
symbol_lillies: ""
});
const [generation, setGeneration] = useState({
poem: ""
});
return (
);
}
Next, we have two functions. handleChecking() is something standar that happens instantaneously, to ensure that the input is correct. handleSubmit() is an asynchronous function because we are going to have to call API endpoints and wait for responses in this function.
src/app/page.js
const [formData, setFormData] = useState({
symbol_bunny: "bunny",
symbol_chicks: "",
symbol_cross: "",
symbol_eggs: "",
symbol_lillies: ""
});
const [generation, setGeneration] = useState({
poem: ""
});
const handleChecking = (e) => {
};
const handleSubmit = async (e) => {
};
return (
);
symbol_bunny: "bunny",
symbol_chicks: "",
symbol_cross: "",
symbol_eggs: "",
symbol_lillies: ""
});
const [generation, setGeneration] = useState({
poem: ""
});
const handleChecking = (e) => {
};
const handleSubmit = async (e) => {
};
return (
);
In the return statement there will be JSX. In here, we have a div styled using the CSS class generationContainer, and a form. The form will run handleSubmit() when data is submitted.
src/app/page.js
return (
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
</form>
</div>
);
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
</form>
</div>
);
Clear out this file. We will add in the generationContainer CSS class. It's basically a centered div with a fixed width. Then we'll style the form. It has rounded corners and a solid light red border.
src/app/page.module.css
.generationContainer {
width: 500px;
margin: 0 auto 0 auto;
}
.generationContainer form {
width: 450px;
padding: 10px;
margin: 0 auto 0 auto;
border-radius: 10px;
border: 1px solid rgb(255, 100, 100);
}
width: 500px;
margin: 0 auto 0 auto;
}
.generationContainer form {
width: 450px;
padding: 10px;
margin: 0 auto 0 auto;
border-radius: 10px;
border: 1px solid rgb(255, 100, 100);
}
Not much to look at right now, but this will change soon.
We then have a line of instructions, and we will display a series of checkboxes based on the formData object. For this, we use the map() method on formData. Since formData is an object, we need to make it iterable by using the entries() method of Object, on it. entries() will turn formData into an array of objects containing properties and values.
src/app/page.js
return (
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
))}
</form>
</div>
);
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
))}
</form>
</div>
);
In this, we have a label tag styled using CSS class label. Because the ReactJS engine will complain if we don't, use key to populate the value of the key attribute.
src/app/page.js
return (
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
<label className={styles.label} key={key}>
</label>
))}
</form>
</div>
);
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
<label className={styles.label} key={key}>
</label>
))}
</form>
</div>
);
We will then have a checkbox within that label tag. The name attribute will be key. handleChecking() will be run for the onchange() attribute. The checked value will depend if value is an empty string.
src/app/page.js
return (
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
<label className={styles.label} key={key}>
<input type="checkbox" name={key} value={key.replace("symbol_", "")} onChange={handleChecking} checked={ (value !== "") } />
<br />
</label>
))}
</form>
</div>
);
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
<label className={styles.label} key={key}>
<input type="checkbox" name={key} value={key.replace("symbol_", "")} onChange={handleChecking} checked={ (value !== "") } />
<br />
</label>
))}
</form>
</div>
);
Then add the text label key, again less the string "symbol_".
src/app/page.js
return (
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
<label className={styles.label} key={key}>
<input type="checkbox" name={key} value={key.replace("symbol_", "")} onChange={handleChecking} checked={ (formData[key] === key.replace("symbol_", "")) } />
{key.replace("symbol_", "")}
<br />
</label>
))}
</form>
</div>
);
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
<label className={styles.label} key={key}>
<input type="checkbox" name={key} value={key.replace("symbol_", "")} onChange={handleChecking} checked={ (formData[key] === key.replace("symbol_", "")) } />
{key.replace("symbol_", "")}
<br />
</label>
))}
</form>
</div>
);
Back to styles, label has a width that will take up less than half of its container, and be floated left.
src/app/page.module.css
.generationContainer form {
width: 450px;
padding: 10px;
margin: 0 auto 0 auto;
border-radius: 10px;
border: 1px solid rgb(255, 100, 100);
}
.label {
display: inline-block;
width: 45%;
float: left;
}
width: 450px;
padding: 10px;
margin: 0 auto 0 auto;
border-radius: 10px;
border: 1px solid rgb(255, 100, 100);
}
.label {
display: inline-block;
width: 45%;
float: left;
}
This is nothing that a CSS break won't fix.
Add this CSS break after adding a SUBMIT button.
src/app/page.js
return (
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
<label className={styles.label} key={key}>
<input type="checkbox" name={key} value={key.replace("symbol_", "")} onChange={handleChecking} checked={ (formData[key] === key.replace("symbol_", "")) } />
{key.replace("symbol_", "")}
<br />
</label>
))}
<button type="submit" className={styles.button}>Go!</button>
<br style={{ clear:"both" }} />
</form>
</div>
);
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
<label className={styles.label} key={key}>
<input type="checkbox" name={key} value={key.replace("symbol_", "")} onChange={handleChecking} checked={ (formData[key] === key.replace("symbol_", "")) } />
{key.replace("symbol_", "")}
<br />
</label>
))}
<button type="submit" className={styles.button}>Go!</button>
<br style={{ clear:"both" }} />
</form>
</div>
);
Just some styling for the button.
src/app/page.module.css
.generationContainer form {
width: 450px;
padding: 10px;
margin: 0 auto 0 auto;
border-radius: 10px;
border: 1px solid rgb(255, 100, 100);
}
.label {
display: inline-block;
width: 45%;
float: left;
}
.button {
display: inline-block;
width: 5em;
height: 2em;
float: right;
}
width: 450px;
padding: 10px;
margin: 0 auto 0 auto;
border-radius: 10px;
border: 1px solid rgb(255, 100, 100);
}
.label {
display: inline-block;
width: 45%;
float: left;
}
.button {
display: inline-block;
width: 5em;
height: 2em;
float: right;
}
All falling into place nicely.
OK, we'll now add a Link component. Each link's route will be symbol followed by the identifier such as "bunny" or "eggs", thus I cleverly just took key and replaced the underscore with a forward slash.
src/app/page.js
return (
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
<label className={styles.label} key={key}>
<input type="checkbox" name={key} value={key.replace("symbol_", "")} onChange={handleChecking} checked={ (formData[key] === key.replace("symbol_", "")) } />
{key.replace("symbol_", "")}
<Link href={ ("/" +key.replace("_", "/")) } target="_blank">►</Link>
<br />
</label>
))}
<button type="submit" className={styles.button}>Go!</button>
<br style={{ clear:"both" }} />
</form>
</div>
);
<div className={styles.generationContainer} >
<form onSubmit={handleSubmit}>
<h1>Generate an Easter Poem!</h1>
<b>Include at least one of these elements</b>
{
Object.entries(formData).map(([key, value]) => (
<label className={styles.label} key={key}>
<input type="checkbox" name={key} value={key.replace("symbol_", "")} onChange={handleChecking} checked={ (formData[key] === key.replace("symbol_", "")) } />
{key.replace("symbol_", "")}
<Link href={ ("/" +key.replace("_", "/")) } target="_blank">►</Link>
<br />
</label>
))}
<button type="submit" className={styles.button}>Go!</button>
<br style={{ clear:"both" }} />
</form>
</div>
);
In order for the link to work, the route needs to work. So create the symbol directory in app. And in that directory, create the [symbolName] directory, square brackets and all. This tells NextJS that symbolName is a parameter value rather than an actual fixed value. And in that directory, create page.js. We'll want to import Image because we'll be working with images. As in the previous page we were working on, we want to import the CSS.
src/app/symbol/[symbolName]/page.js
import Image from "next/image";
import styles from "../../page.module.css";
import styles from "../../page.module.css";
We'll want to export this function. As always, we add a return statement inside the function.
src/app/symbol/[symbolName]/page.js
import Image from "next/image";
import styles from "../../page.module.css";
export default function SymbolPage({ params }) {
return (
);
}
import styles from "../../page.module.css";
export default function SymbolPage({ params }) {
return (
);
}
params will contain symbolName, which will typically be "bunny", "lillies", etc. Thankfully, we've already saved those in the public directory, so they're accessible with just a slash. They can thus be used as the value of the src attribute for the Image component. Here, I have included the width and height of the image. The Image component will be placed inside a div styled using the symbolImage CSS class.
src/app/symbol/[symbolName]/page.js
export default function SymbolPage({ params }) {
return (
<div className={ styles.symbolImage } >
<Image src={ "/symbol_" + params.symbolName + ".jpg" } alt={ params.symbolName } width={ 300 } height={ 300 } />
</div>
);
}
return (
<div className={ styles.symbolImage } >
<Image src={ "/symbol_" + params.symbolName + ".jpg" } alt={ params.symbolName } width={ 300 } height={ 300 } />
</div>
);
}
I am using symbolImage mostly for positioning. I have also set it so that any img tag inside symbolImage will have rounded corners.
src/app/page.module.css
.symbolImage {
width: 100%;
min-height: auto;
margin: 10px auto 10px auto;
text-align: center;
}
.symbolImage img{
border-radius: 10px;
}
.generationContainer {
width: 500px;
margin: 0 auto 0 auto;
}
width: 100%;
min-height: auto;
margin: 10px auto 10px auto;
text-align: center;
}
.symbolImage img{
border-radius: 10px;
}
.generationContainer {
width: 500px;
margin: 0 auto 0 auto;
}
You can see the arrows. Now click on maybe the one beside "bunny".
You see a picture, in a new tab, with rounded corners!
We now add an object, texts. It has properties that mirror the possible values of symbolName.
src/app/symbol/[symbolName]/page.js
import Image from "next/image";
import styles from "../../page.module.css";
export default function SymbolPage({ params }) {
const texts = {
"bunny": ,
"chicks": ,
"cross": ,
"eggs": ,
"lillies":
};
return (
<div className={ styles.symbolImage } >
<Image src={ "/symbol_" + params.symbolName + ".jpg" } alt={ params.symbolName } width={ 300 } height={ 300 } />
</div>
);
}
import styles from "../../page.module.css";
export default function SymbolPage({ params }) {
const texts = {
"bunny": ,
"chicks": ,
"cross": ,
"eggs": ,
"lillies":
};
return (
<div className={ styles.symbolImage } >
<Image src={ "/symbol_" + params.symbolName + ".jpg" } alt={ params.symbolName } width={ 300 } height={ 300 } />
</div>
);
}
Here, I'm going to add text strings as values for these properties. These were generated using Copilot.
src/app/symbol/[symbolName]/page.js
import Image from "next/image";
import styles from "../../page.module.css";
export default function SymbolPage({ params }) {
const texts = {
"bunny": "The bunny, or Easter rabbit, is a symbol of fertility and new life, which aligns with the themes of rebirth and renewal celebrated during Easter. Originating from ancient pagan traditions, the rabbit was associated with the goddess Ēostre, who represented spring and fertility. As Christianity spread, the symbolism of the rabbit was incorporated into Easter celebrations. The Easter bunny is often depicted delivering eggs, which are also symbols of new life and rebirth, to children, adding a playful and joyful element to the holiday.",
"chicks": "Chicks are another symbol of new life and rebirth, aligning with the overall themes of Easter. Just as chicks hatch from eggs, they represent the idea of life emerging from the seemingly lifeless, echoing the resurrection of Jesus. They also symbolize innocence and the start of new beginnings, adding to the joyful and hopeful atmosphere of Easter celebrations. The imagery of chicks, along with eggs and bunnies, helps to convey the sense of renewal and the promise of new life that Easter embodies.",
"cross": "The crucifix is a powerful symbol in Christianity, especially during Easter. It represents the crucifixion of Jesus Christ, an event central to Christian belief. It signifies the ultimate sacrifice Jesus made by dying on the cross to atone for humanity's sins. It also symbolizes the immense suffering Jesus endured during the crucifixion. The crucifix serves as a reminder of the redemption and salvation offered through Jesus' sacrifice. Easter celebrates Jesus' resurrection, signifying victory over death and the promise of eternal life for believers. This powerful imagery serves as a reminder of Jesus' love and the hope that comes with his resurrection, making the crucifix an integral part of Easter celebrations.",
"eggs": "Eggs are a rich and multifaceted symbol in Easter traditions. They embody the themes of new life, rebirth, and resurrection, which are central to the celebration of Easter. They represent the emergence of new life from within, mirroring the resurrection of Jesus from the tomb. Just as a chick hatches from an egg, the concept of rebirth is symbolized, signifying the idea of starting anew. Historically, eggs have been seen as symbols of fertility and renewal, aligning with the arrival of spring. The egg, appearing lifeless on the outside but containing life within, represents transformation and the miracle of life. Eggs are often decorated and used in Easter egg hunts, making them a playful and engaging way to celebrate the holiday while reflecting on its deeper meanings.",
"lillies": "Lilies, particularly white lilies, hold significant symbolism in Easter celebrations. They represent purity, virtue, and the resurrection of Jesus Christ. The trumpet shape of the lily flower is said to symbolize the trumpet call of God, heralding the resurrection. Lilies are often used in Easter decorations and church services to convey a sense of hope, renewal, and new beginnings. Their association with new life and purity makes them a fitting symbol for the themes of Easter."
};
return (
<div className={ styles.symbolImage } >
<Image src={ "/symbol_" + params.symbolName + ".jpg" } alt={ params.symbolName } width={ 300 } height={ 300 } />
</div>
);
}
import styles from "../../page.module.css";
export default function SymbolPage({ params }) {
const texts = {
"bunny": "The bunny, or Easter rabbit, is a symbol of fertility and new life, which aligns with the themes of rebirth and renewal celebrated during Easter. Originating from ancient pagan traditions, the rabbit was associated with the goddess Ēostre, who represented spring and fertility. As Christianity spread, the symbolism of the rabbit was incorporated into Easter celebrations. The Easter bunny is often depicted delivering eggs, which are also symbols of new life and rebirth, to children, adding a playful and joyful element to the holiday.",
"chicks": "Chicks are another symbol of new life and rebirth, aligning with the overall themes of Easter. Just as chicks hatch from eggs, they represent the idea of life emerging from the seemingly lifeless, echoing the resurrection of Jesus. They also symbolize innocence and the start of new beginnings, adding to the joyful and hopeful atmosphere of Easter celebrations. The imagery of chicks, along with eggs and bunnies, helps to convey the sense of renewal and the promise of new life that Easter embodies.",
"cross": "The crucifix is a powerful symbol in Christianity, especially during Easter. It represents the crucifixion of Jesus Christ, an event central to Christian belief. It signifies the ultimate sacrifice Jesus made by dying on the cross to atone for humanity's sins. It also symbolizes the immense suffering Jesus endured during the crucifixion. The crucifix serves as a reminder of the redemption and salvation offered through Jesus' sacrifice. Easter celebrates Jesus' resurrection, signifying victory over death and the promise of eternal life for believers. This powerful imagery serves as a reminder of Jesus' love and the hope that comes with his resurrection, making the crucifix an integral part of Easter celebrations.",
"eggs": "Eggs are a rich and multifaceted symbol in Easter traditions. They embody the themes of new life, rebirth, and resurrection, which are central to the celebration of Easter. They represent the emergence of new life from within, mirroring the resurrection of Jesus from the tomb. Just as a chick hatches from an egg, the concept of rebirth is symbolized, signifying the idea of starting anew. Historically, eggs have been seen as symbols of fertility and renewal, aligning with the arrival of spring. The egg, appearing lifeless on the outside but containing life within, represents transformation and the miracle of life. Eggs are often decorated and used in Easter egg hunts, making them a playful and engaging way to celebrate the holiday while reflecting on its deeper meanings.",
"lillies": "Lilies, particularly white lilies, hold significant symbolism in Easter celebrations. They represent purity, virtue, and the resurrection of Jesus Christ. The trumpet shape of the lily flower is said to symbolize the trumpet call of God, heralding the resurrection. Lilies are often used in Easter decorations and church services to convey a sense of hope, renewal, and new beginnings. Their association with new life and purity makes them a fitting symbol for the themes of Easter."
};
return (
<div className={ styles.symbolImage } >
<Image src={ "/symbol_" + params.symbolName + ".jpg" } alt={ params.symbolName } width={ 300 } height={ 300 } />
</div>
);
}
Then we add a paragraph styled using CSS class symbolText. The HTML in here will be the current property of texts being pointed to by symbolName.
src/app/symbol/[symbolName]/page.js
return (
<div className={ styles.symbolImage } >
<Image src={ "/symbol_" + params.symbolName + ".jpg" } alt={ params.symbolName } width={ 300 } height={ 300 } />
<p className={styles.symbolText} >{ texts[params.symbolName] }</p>
</div>
);
<div className={ styles.symbolImage } >
<Image src={ "/symbol_" + params.symbolName + ".jpg" } alt={ params.symbolName } width={ 300 } height={ 300 } />
<p className={styles.symbolText} >{ texts[params.symbolName] }</p>
</div>
);
The symbolText CSS class just imposes some padding.
src/app/page.module.css
.symbolImage {
width: 100%;
min-height: auto;
margin: 10px auto 10px auto;
text-align: center;
}
.symbolImage img{
border-radius: 10px;
}
.symbolText {
padding: 10px;
}
.generationContainer {
width: 500px;
margin: 0 auto 0 auto;
}
width: 100%;
min-height: auto;
margin: 10px auto 10px auto;
text-align: center;
}
.symbolImage img{
border-radius: 10px;
}
.symbolText {
padding: 10px;
}
.generationContainer {
width: 500px;
margin: 0 auto 0 auto;
}
You see the paragraph appears!
We're done for the time being. So far, we've covered a bit of NextJS routing and built-in components.
No comments:
Post a Comment