We actually already created functions for validating and submitting earlier, so let's fill them up now.
We start with the
handleChecking() function. We first get
name,
value and
checked from the
target object of the
e object.
target will be the checkbox that is checked. So we have
finalValue. If the checkbox is checked, then
finalValue has the value
value. If not, its
value is an empty string.
src/app/page.jsconst handleChecking = (e) => {
const { name, value, checked } = e.target;
var finalValue = (checked ? value : "");
};
Whatever the value of
finalValue, it will be used in
setFormData(). We use the spread operator to destructure
formData, then add in a name-value pair using
name and
finalValue. If
name already appears in
formData, it will thus be overwritten by the value of
finalValue.
src/app/page.jsconst handleChecking = (e) => {
const { name, value, checked } = e.target;
var finalValue = (checked ? value : "");
setFormData({
...formData,
[name]: finalValue
});
};
What we want to do is ensure that at least one checkbox is checked. So we'll test this. Declare
tempFormData as a clone of
formData by using
stringify() and
parse() methods. Modify
tempFormData's property, pointed to by
name, changing its value to
finalValue.
src/app/page.jsconst handleChecking = (e) => {
const { name, value, checked } = e.target;
var finalValue = (checked ? value : "");
var tempFormData = JSON.stringify(formData);
tempFormData = JSON.parse(tempFormData);
tempFormData[name] = finalValue;
setFormData({
...formData,
[name]: finalValue
});
};
Now we use an
If block to check if all the string values are empty strings. If that's true, we reset the value of
finalValue to
value. This ensures that there is never a situation where all the properties are empty strings.
src/app/page.jsconst handleChecking = (e) => {
const { name, value, checked } = e.target;
var finalValue = (checked ? value : "");
var tempFormData = JSON.stringify(formData);
tempFormData = JSON.parse(tempFormData);
tempFormData[name] = finalValue;
if (tempFormData.symbol_bunny + tempFormData.symbol_chicks + tempFormData.symbol_cross + tempFormData.symbol_eggs + tempFormData.symbol_lillies === "") {
finalValue = value;
}
setFormData({
...formData,
[name]: finalValue
});
};
Try it... you can select multiple options but you will find that you'll never be able to deselect all of the options.
That's it for validation! Now we want to handle submitting.
We first ensure that the form is not automatically submitted, by using the
preventDefault() method. We want to implement our own submit logic. After that, we use
setGeneration() to set a loading message.
src/app/page.jsconst handleSubmit = async (e) => {
e.preventDefault();
setGeneration( { poem: "<h2>Pease wait...</h2>" } );
};
We'll add a div, styled using the CSS class
poemContainer, with the generation object's
poem property as content.
src/app/page.jsreturn (
<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 !== "") } />
{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 className={styles.poemContainer} >{ generation.poem } </div>
</div>
);
Here's
poemContainer. Just a bit of alignment and layout. Nothing crazy.
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);
}
.poemContainer {
width: 450px;
padding: 10px;
margin: 0 auto 0 auto;
text-align: center;
}
.label {
display: inline-block;
width: 45%;
float: left;
}
Now try Clicking the SUBMIT button. Looks like the HTML is being treated like text content!
We will rectify this using
dangerouslySetInnerHTML. This renders raw HTML in a React component.
src/app/page.js<div className={styles.poemContainer} dangerouslySetInnerHTML={{ __html: generation.poem }} />
This time, the loading message is properly formatted.
Now that this is done, we're going to make a call to the backend using
fetch(). We'll pair it with
await because its very nature is
async. The endpoint is
poemgen and we will be creating it after this. The result of the call to the backend will be assigned to the object
response.
src/app/page.jsconst handleSubmit = async (e) => {
e.preventDefault();
setGeneration( { poem: "<h2>Pease wait...</h2>" } );
const response = await fetch("/api/poemgen", {
});
};
In
fetch(), the second argument is an object. It contains the method of the call (in this case, it's a
POST), the
headers object (which in this context basically tells
fetch() that we're ending data in JSON format, and the
body property. The last one contains
formData as a JSON-formatted string.
src/app/page.jsconst handleSubmit = async (e) => {
e.preventDefault();
setGeneration( { poem: "<h2>Pease wait...</h2>" } );
const response = await fetch("/api/poemgen", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(formData)
});
};
We then declare
result and use it to store the value of response as a JSON object. Since the method we're using,
json(), is asynchronous, we have to use
await.
src/app/page.jsconst handleSubmit = async (e) => {
e.preventDefault();
setGeneration( { poem: "<h2>Pease wait...</h2>" } );
const response = await fetch("/api/poemgen", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(formData)
});
const result = await response.json();
};
We then check if
status is 200. If so, we run
setGeneration() and set the value of the
poem property to the
message property of
result. If not, we handle the error by logging it in the console.
src/app/page.jsconst handleSubmit = async (e) => {
e.preventDefault();
setGeneration( { poem: "<h2>Pease wait...</h2>" } );
const response = await fetch("/api/poemgen", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(formData)
});
const result = await response.json();
if (response.status === 200) {
setGeneration( { poem: result.message } );
} else {
console.log(result);
}
};
Now, in the
app directory, create the
api subdirectory, and in it, the
poemgen subdirectory. In that, create
route.js. This is the NextJS App router method of creating backend API endpoints. In this file, we export an
async function that responds to the
POST method. There is a parameter,
req, that represents the request.
src/app/api/poemgen/route.jsexport async function POST(req) {
}
We declare
formData and use
await to obtain the JSON object
req. Then we declare
symbols as an empty string.
src/app/api/poemgen/route.jsexport async function POST(req) {
const formData = await req.json();
var symbols = "";
}
This is followed by a series of
If blocks that correspond to each property in
formData. If the value is not an empty string, concatenate that value to
symbols, with a comma and space. Remember, there will never be a case where all the strings are empty. Thus, there will always be at least one comma and space. We remove the one at the end using the
slice() method.
src/app/api/poemgen/route.jsexport async function POST(req) {
const formData = await req.json();
var symbols = "";
if (formData.symbol_bunny !== "") symbols += (formData.symbol_bunny + ", ");
if (formData.symbol_chicks !== "") symbols += (formData.symbol_chicks + ", ");
if (formData.symbol_cross !== "") symbols += (formData.symbol_cross + ", ");
if (formData.symbol_eggs !== "") symbols += (formData.symbol_eggs + ", ");
if (formData.symbol_lillies !== "") symbols += (formData.symbol_lillies + ", ");
symbols = symbols.slice(0, -2);
}
Next, we declare
headers as an object, and populate it with standard properties such as
Authorization and
Content-Type. Since this is for
ChatGPT, we'll need
OpenAI-Oganization also.
Authorization and
OpenAI-Oganization will have values that can be accessed from your
.env file.
src/app/api/poemgen/route.jsexport async function POST(req) {
const formData = await req.json();
var symbols = "";
if (formData.symbol_bunny !== "") symbols += (formData.symbol_bunny + ", ");
if (formData.symbol_chicks !== "") symbols += (formData.symbol_chicks + ", ");
if (formData.symbol_cross !== "") symbols += (formData.symbol_cross + ", ");
if (formData.symbol_eggs !== "") symbols += (formData.symbol_eggs + ", ");
if (formData.symbol_lillies !== "") symbols += (formData.symbol_lillies + ", ");
symbols = symbols.slice(0, -2);
var headers = {
"Authorization": "Bearer " + process.env.NEXT_PUBLIC_API_KEY,
"OpenAI-Oganization": process.env.NEXT_PUBLIC_ORG,
"Content-Type": "application/json"
};
}
The values will look like this in the
.env file. They won't, of course, be "xxx".
.envNEXT_PUBLIC_API_KEY=xxx
NEXT_PUBLIC_ORG=org-xxx
Now create an empty array,
messages. Then create an object,
obj. It should have the property
role, which has a value of "user", and the property
content. Its value should be a prompt we create from
symbols. After that, push
obj into
messages.
src/app/api/poemgen/route.jsexport async function POST(req) {
const formData = await req.json();
var symbols = "";
if (formData.symbol_bunny !== "") symbols += (formData.symbol_bunny + ", ");
if (formData.symbol_chicks !== "") symbols += (formData.symbol_chicks + ", ");
if (formData.symbol_cross !== "") symbols += (formData.symbol_cross + ", ");
if (formData.symbol_eggs !== "") symbols += (formData.symbol_eggs + ", ");
if (formData.symbol_lillies !== "") symbols += (formData.symbol_lillies + ", ");
symbols = symbols.slice(0, -2);
var headers = {
"Authorization": "Bearer " + process.env.NEXT_PUBLIC_API_KEY,
"OpenAI-Oganization": process.env.NEXT_PUBLIC_ORG,
"Content-Type": "application/json"
};
var messages = [];
var obj = {
"role": "user",
"content" : "Generate an Easter-themed poem with the following element(s): " + symbols + "."
};
messages.push(obj);
}
Create
body as an object. In there, we specify the AI model and set the number of tokens to be used, to 2000. The
messages property value will be the array
messages.
src/app/api/poemgen/route.jsexport async function POST(req) {
const formData = await req.json();
var symbols = "";
if (formData.symbol_bunny !== "") symbols += (formData.symbol_bunny + ", ");
if (formData.symbol_chicks !== "") symbols += (formData.symbol_chicks + ", ");
if (formData.symbol_cross !== "") symbols += (formData.symbol_cross + ", ");
if (formData.symbol_eggs !== "") symbols += (formData.symbol_eggs + ", ");
if (formData.symbol_lillies !== "") symbols += (formData.symbol_lillies + ", ");
symbols = symbols.slice(0, -2);
var headers = {
"Authorization": "Bearer " + process.env.NEXT_PUBLIC_API_KEY,
"OpenAI-Oganization": process.env.NEXT_PUBLIC_ORG,
"Content-Type": "application/json"
};
var messages = [];
var obj = {
"role": "user",
"content" : "Generate an Easter-themed poem with the following element(s): " + symbols + "."
};
messages.push(obj);
var body = {
"model": "gpt-3.5-turbo",
"messages" : messages,
"max_tokens" : 2000
};
}
Here, we have a
Try-catch block. In here, we use
fetch() to grab data via a REST endpoint provided by OpenAI, sending header and body as the data. The response is assigned to the variable
apiResponse.
src/app/api/poemgen/route.jsexport async function POST(req) {
const formData = await req.json();
var symbols = "";
if (formData.symbol_bunny !== "") symbols += (formData.symbol_bunny + ", ");
if (formData.symbol_chicks !== "") symbols += (formData.symbol_chicks + ", ");
if (formData.symbol_cross !== "") symbols += (formData.symbol_cross + ", ");
if (formData.symbol_eggs !== "") symbols += (formData.symbol_eggs + ", ");
if (formData.symbol_lillies !== "") symbols += (formData.symbol_lillies + ", ");
symbols = symbols.slice(0, -2);
var headers = {
"Authorization": "Bearer " + process.env.NEXT_PUBLIC_API_KEY,
"OpenAI-Oganization": process.env.NEXT_PUBLIC_ORG,
"Content-Type": "application/json"
};
var messages = [];
var obj = {
"role": "user",
"content" : "Generate an Easter-themed poem with the following element(s): " + symbols + "."
};
messages.push(obj);
var body = {
"model": "gpt-3.5-turbo",
"messages" : messages,
"max_tokens" : 2000
};
try {
const apiResponse = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: headers,
body: JSON.stringify(body)
});
} catch (error) {
}
}
We then check the
ok property of
apiResponse. If this is
false, we return a Response object with an appropriate status. We return the same thing if there is an exception thrown.
src/app/api/poemgen/route.jsexport async function POST(req) {
const formData = await req.json();
var symbols = "";
if (formData.symbol_bunny !== "") symbols += (formData.symbol_bunny + ", ");
if (formData.symbol_chicks !== "") symbols += (formData.symbol_chicks + ", ");
if (formData.symbol_cross !== "") symbols += (formData.symbol_cross + ", ");
if (formData.symbol_eggs !== "") symbols += (formData.symbol_eggs + ", ");
if (formData.symbol_lillies !== "") symbols += (formData.symbol_lillies + ", ");
symbols = symbols.slice(0, -2);
var headers = {
"Authorization": "Bearer " + process.env.NEXT_PUBLIC_API_KEY,
"OpenAI-Oganization": process.env.NEXT_PUBLIC_ORG,
"Content-Type": "application/json"
};
var messages = [];
var obj = {
"role": "user",
"content" : "Generate an Easter-themed poem with the following element(s): " + symbols + "."
};
messages.push(obj);
var body = {
"model": "gpt-3.5-turbo",
"messages" : messages,
"max_tokens" : 2000
};
try {
const apiResponse = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: headers,
body: JSON.stringify(body)
});
if (!apiResponse.ok) {
return new Response(JSON.stringify({ message: "Error in poem generation" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
} catch (error) {
return new Response(JSON.stringify({ message: error.message }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
Here, we obtain
data by converting
apiResponse to JSON using the asynchronous method
json(). And we'll define
html_content as the generated content but with HTML breaks in place of line breaks.
src/app/api/poemgen/route.jsexport async function POST(req) {
const formData = await req.json();
var symbols = "";
if (formData.symbol_bunny !== "") symbols += (formData.symbol_bunny + ", ");
if (formData.symbol_chicks !== "") symbols += (formData.symbol_chicks + ", ");
if (formData.symbol_cross !== "") symbols += (formData.symbol_cross + ", ");
if (formData.symbol_eggs !== "") symbols += (formData.symbol_eggs + ", ");
if (formData.symbol_lillies !== "") symbols += (formData.symbol_lillies + ", ");
symbols = symbols.slice(0, -2);
var headers = {
"Authorization": "Bearer " + process.env.NEXT_PUBLIC_API_KEY,
"OpenAI-Oganization": process.env.NEXT_PUBLIC_ORG,
"Content-Type": "application/json"
};
var messages = [];
var obj = {
"role": "user",
"content" : "Generate an Easter-themed poem with the following element(s): " + symbols + "."
};
messages.push(obj);
var body = {
"model": "gpt-3.5-turbo",
"messages" : messages,
"max_tokens" : 2000
};
try {
const apiResponse = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: headers,
body: JSON.stringify(body)
});
if (!apiResponse.ok) {
return new Response(JSON.stringify({ message: "Error in poem generation" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
const data = await apiResponse.json();
var html_content = data.choices[0].message.content.replaceAll("\n", "<br />");
} catch (error) {
return new Response(JSON.stringify({ message: error.message }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
And once that is done, we will return a new
Response with the status 200 and
html_content as the message.
src/app/api/poemgen/route.jsexport async function POST(req) {
const formData = await req.json();
var symbols = "";
if (formData.symbol_bunny !== "") symbols += (formData.symbol_bunny + ", ");
if (formData.symbol_chicks !== "") symbols += (formData.symbol_chicks + ", ");
if (formData.symbol_cross !== "") symbols += (formData.symbol_cross + ", ");
if (formData.symbol_eggs !== "") symbols += (formData.symbol_eggs + ", ");
if (formData.symbol_lillies !== "") symbols += (formData.symbol_lillies + ", ");
symbols = symbols.slice(0, -2);
var headers = {
"Authorization": "Bearer " + process.env.NEXT_PUBLIC_API_KEY,
"OpenAI-Oganization": process.env.NEXT_PUBLIC_ORG,
"Content-Type": "application/json"
};
var messages = [];
var obj = {
"role": "user",
"content" : "Generate an Easter-themed poem with the following element(s): " + symbols + "."
};
messages.push(obj);
var body = {
"model": "gpt-3.5-turbo",
"messages" : messages,
"max_tokens" : 2000
};
try {
const apiResponse = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: headers,
body: JSON.stringify(body)
});
if (!apiResponse.ok) {
return new Response(JSON.stringify({ message: "Error in poem generation" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
const data = await apiResponse.json();
var html_content = data.choices[0].message.content.replaceAll("\n", "<br />");
return new Response(JSON.stringify({ message: html_content }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
return new Response(JSON.stringify({ message: error.message }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
Now try this! First, try it with one item checked.
Then try it with multiple items checked, and see the difference!
Enjoy your Easter!
This has been my virgin NextJS tutorial. We covered a fair amount of territory, and hopefully had fun in the process!
May your Easter eggs all be found,
His Teochewness will see you around!
T___T