{"id":28048,"date":"2022-03-23T22:59:19","date_gmt":"2022-03-23T22:59:19","guid":{"rendered":"http:\/\/Making-a-Meal-Picker-with-Redwood"},"modified":"2022-03-23T22:59:19","modified_gmt":"2022-03-23T22:59:19","slug":"making-a-meal-picker-with-redwood","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/","title":{"rendered":"Making a Meal Picker"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>with Redwood<\/p>\n<h2>Intro<\/h2>\n<p>Deciding what to eat every day is something that a lot of us struggle with. It\u2019d be so much easier if there was a button you could push and it would choose one of your favorite meals for you.<\/p>\n<p>That\u2019s why we\u2019re going to make a meal picker in this tutorial. We\u2019ll use Redwood to create the front-end and back-end of the app which will let us save recipes and choose a random meal. We\u2019ll also be using Cloudinary to host the video we have associated with the recipes.<\/p>\n<h2>Setting up the app<\/h2>\n<p>To get started, let\u2019s make a new Redwood app that uses TypeScript. In a terminal, run this command.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn create redwood-app --typescript meal-picker\n<\/code><\/span><\/pre>\n<p>This will generate a lot of new files and folders in the <code>meal-picker<\/code> directory. The main two directories are <code>web<\/code> and <code>api<\/code>. The <code>web<\/code> folder contains all of the code for the front-end and the <code>api<\/code> directory contains all of the code for the back-end.<\/p>\n<p>Most times, it\u2019s a good practice to start building the data model of a new app first.<\/p>\n<h2>Building the back-end<\/h2>\n<p>We\u2019ll start by opening the <code>prisma.schema<\/code> file in <code>api &gt; db<\/code> directory. This file holds all of the models for the tables we\u2019ll have in the database. We\u2019ll be using a local Postgres instance, so if you need to download that you can find the right version <a href=\"https:\/\/www.postgresql.org\/download\/\">here<\/a>.<\/p>\n<p>The first thing we\u2019ll do is update the <code>provider<\/code> value to <code>postgresql<\/code>. Next, we need to create a <code>.env<\/code> file in the root of the project. You\u2019ll see the <code>DATABASE_URL<\/code> being read from the environment below the type of database we\u2019re using. In the <code>.env<\/code> file, add the connection string to your local Postgres instance. It might look similar to this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">DATABASE_URL=postgres:<span class=\"hljs-comment\">\/\/postgres:admin@localhost:5432\/meal_picker<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Creating the model<\/h3>\n<p>Now you have everything set to connect to your database. We need to define the model for the meals we\u2019ll be saving to choose from. You can delete the <code>UserExample<\/code> model and replace it with this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">model Meal {\n  id      <span class=\"hljs-built_in\">String<\/span> @id @<span class=\"hljs-keyword\">default<\/span>(cuid())\n  title   <span class=\"hljs-built_in\">String<\/span>\n  recipe <span class=\"hljs-built_in\">String<\/span>\n  video   <span class=\"hljs-built_in\">String<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This model defines a table that will hold all of the details for our meals. There has to be a way for users to input their favorite meals and all of the details. With the model ready, we can go ahead and run the migration to get these changes on the database.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw prisma migrate dev\n<\/code><\/span><\/pre>\n<p>That will define the <code>Meal<\/code> table in Postgres. So now we can use one of the Redwood commands to generate this CRUD functionality for us.<\/p>\n<h2>Creating the meal collection management functionality<\/h2>\n<p>We\u2019ll need to have both the GraphQL back-end in place and the front-end in place to allow users to add, edit, and delete meals. This normally takes a bit of time, but the following command generates all of that for us.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw g scaffold meal\n<\/code><\/span><\/pre>\n<p>Check the <code>api &gt; src &gt; graphql<\/code> directory and you should see <code>meals.sdl.ts<\/code> file that has all of the types for the queries and mutations we need. If you check in <code>api &gt; src &gt; services &gt; meals<\/code>, you\u2019ll see three files. Two of them are for tests and the <code>meals.ts<\/code> file holds all of the resolvers for our queries and mutations. These already connect to the database so we have our entire back-end created!<\/p>\n<h2>Moving to the front-end<\/h2>\n<p>Now that we have the GraphQL server ready to go, let\u2019s take a look at the files the <code>scaffold<\/code> command created on the front-end. In <code>web &gt; src &gt; pages &gt; Meals<\/code>, you\u2019ll see several new directories that correspond to different views of the CRUD for meals.<\/p>\n<p>If you take a look in <code>web &gt; src &gt; components &gt; Meal<\/code>, you\u2019ll see a number of components that we created. These components interact with and display the data that we get from the back-end. It\u2019s worth taking the time to peek at these different files and see how they work, but you can still run the app and see all of the CRUD in action without ever looking at the code.<\/p>\n<p>In your terminal, run this command and navigate to <code>http:\/\/localhost:8910\/meals<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw dev\n<\/code><\/span><\/pre>\n<p>You should see something like this in your browser.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1631654470\/e-603fc55d218a650069f5228b\/bgpn7e5mtzxpo4madk2l.png\" alt=\"how the meals table looks with no entries\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"686\"\/><\/p>\n<p>Now add a few entries by clicking the \u201cNew Meal\u201d button. This will bring up a new page and let you add the details.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1631654448\/e-603fc55d218a650069f5228b\/xdeywkzh1xiwg8f47pqh.png\" alt=\"new meal page\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"686\"\/><\/p>\n<p>If you haven\u2019t uploaded any videos for your meals, take a second to go to <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">your Cloudinary account<\/a> and add those. Make sure you grab the URLs for the videos you want to use because you\u2019ll add them to your new meal entries.<\/p>\n<p>Once you\u2019ve added a few meals, you should see a table that lists all of your entries.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1631654434\/e-603fc55d218a650069f5228b\/fmtlss3c16gzn2r9bgvs.png\" alt=\"table with meal entries\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"479\"\/><\/p>\n<p>That\u2019s everything we need to handle the CRUD functionality for our meals. You might think of this as an admin area in the app. Redwood generated all of this for us with just one command. All that\u2019s left is making the random meal picker page.<\/p>\n<h2>Making the picker page<\/h2>\n<p>Let\u2019s make a new page for the picker. We\u2019ll use another Redwood command.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">yarn rw g page picker \/\n<\/code><\/span><\/pre>\n<p>This updates our routes to make the picker page the root page and it generates some files for us. Go to <code>web &gt; src &gt; pages &gt; PickerPage<\/code> and open <code>PickerPage.tsx<\/code>. This is where we\u2019ll make the button that will tell us what to eat.<\/p>\n<p>We\u2019ll get rid of a lot of the boilerplate code in the <code>PickerPage<\/code> component. Let\u2019s start by adding the import statements for the methods we\u2019ll be using. So your list of import statements should look like this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useQuery } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@redwoodjs\/web'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { MetaTags } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@redwoodjs\/web'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Now let\u2019s add the GraphQL query we need to get all of the meals we have available to choose from. This goes right below the import statements.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> GET_MEALS = gql<span class=\"hljs-string\">`\n  query {\n    meals {\n      title\n      recipe\n      video\n    }\n  }\n`<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>One more quick thing before we start using this query. Since this is a TypeScript app, let\u2019s add the type for a single meal. Below the query just wrote, add the <code>Meal<\/code> type.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">interface<\/span> <span class=\"hljs-selector-tag\">Meal<\/span> {\n  <span class=\"hljs-attribute\">title<\/span>: string\n  recipe: string\n  video: string\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>When we get ready to work with the meals data, now we know exactly what to expect. Now we get to delete a lot of code. Inside the <code>PickerPage<\/code> component, delete everything except the <code>&lt;MetaTags&gt;<\/code> element. Your <code>PickerPage.tsx<\/code> should look like this now.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useQuery } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@redwoodjs\/web'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { MetaTags } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'@redwoodjs\/web'<\/span>\n<span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> GET_MEALS = gql<span class=\"hljs-string\">`\n  query {\n    meals {\n      title\n      recipe\n      video\n    }\n  }\n`<\/span>\n\ninterface Meal {\n  <span class=\"hljs-attr\">title<\/span>: string\n  <span class=\"hljs-attr\">recipe<\/span>: string\n  <span class=\"hljs-attr\">video<\/span>: string\n}\n\n<span class=\"hljs-keyword\">const<\/span> PickerPage = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">MetaTags<\/span>\n        <span class=\"hljs-attr\">title<\/span>=<span class=\"hljs-string\">\"Picker\"<\/span>\n      \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/&gt;<\/span><\/span>\n  )\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> PickerPage\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>All that\u2019s left is adding the data and the elements to display it.<\/p>\n<h3>Handling the data<\/h3>\n<p>Let\u2019s add a new <code>meal<\/code> state in the component. We\u2019ll use our <code>Meal<\/code> type to define what values are expected.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">const &#91;meal, setMeal] = useState<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Meal<\/span>&gt;<\/span>()\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Next we\u2019ll use the <code>useQuery<\/code> hook to fetch our data from the GraphQL server.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> { loading, data } = useQuery(GET_MEALS)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We get both the data and a loading state for it. That way we can account for any latency in the request and show the users a loading screen. If we don\u2019t handle this, the app will likely crash because the data isn\u2019t available yet. Right below the <code>useQuery<\/code> call, we\u2019ll add the code to handle this loading state.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">if<\/span> (loading) {\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Loading...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The last function we need to add before creating the elements to show our random meal will actually be responsible for choosing that meal. When we click a button on the screen, it\u2019ll call this function and set the <code>meal<\/code> state to some random selection.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> loadMeal = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">if<\/span> (data.meals.length !== <span class=\"hljs-number\">0<\/span>) {\n    <span class=\"hljs-keyword\">const<\/span> max = data.meals.length\n    <span class=\"hljs-keyword\">const<\/span> index = getRandomInt(<span class=\"hljs-number\">0<\/span>, max)\n    setMeal(data.meals&#91;index])\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>There is a tiny helper function we for to get that random integer. Add this code below the <code>PickerPage<\/code> component.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getRandomInt<\/span>(<span class=\"hljs-params\">min, max<\/span>) <\/span>{\n  min = <span class=\"hljs-built_in\">Math<\/span>.ceil(min);\n  max = <span class=\"hljs-built_in\">Math<\/span>.floor(max);\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-built_in\">Math<\/span>.floor(<span class=\"hljs-built_in\">Math<\/span>.random() * (max - min) + min);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>We have the data in place and all of the accompanying functions we need. Let\u2019s finally add the elements to display everything.<\/p>\n<h3>The button and meal display<\/h3>\n<p>Below the <code>&lt;MetaTags&gt;<\/code> element, add these elements.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>{meal ? meal.title : 'Find out what you are going to eat'}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{loadMeal}<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">fontSize:<\/span> '<span class=\"hljs-attr\">18px<\/span>', <span class=\"hljs-attr\">padding:<\/span> '<span class=\"hljs-attr\">24px<\/span> <span class=\"hljs-attr\">32px<\/span>', <span class=\"hljs-attr\">width:<\/span> '<span class=\"hljs-attr\">500px<\/span>' }}&gt;<\/span>Tell me what to eat<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The text on the page will change based on whether or not you\u2019ve had a random meal selected. Then there\u2019s the button with a few styles on it that will call the function to choose a new random meal.<\/p>\n<p>If you run the app again with <code>yarn rw dev<\/code>, you\u2019ll see something like this in your browser.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1631654385\/e-603fc55d218a650069f5228b\/y6wduwb9h5e8lism4tdj.png\" alt=\"meal selector button\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"821\"\/><\/p>\n<p>The last piece of code is to display the info for the randomly selected meal. We\u2019ll do this with a conditional render statement below the <code>&lt;button&gt;<\/code>.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">{meal &amp;&amp;\n  <span class=\"hljs-tag\">&lt;&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>{meal.recipe}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{meal.video}<\/span> <span class=\"hljs-attr\">controls<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">'350'<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">'500'<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/&gt;<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This will display the recipe and the video whenever the button is clicked and a meal is selected. Now if you look in the browser, you should see something like this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1631654321\/e-603fc55d218a650069f5228b\/ubxf52p3joar5y65pwvc.png\" alt=\"page after the button has been clicked and a meal has been selected\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"966\"\/><\/p>\n<p>That\u2019s it! You now have a meal picker that will make it hard for you to say you can\u2019t decide what to eat anymore.<\/p>\n<h2>Finished code<\/h2>\n<p>If you want to check out the finished front-end and back-end code, check out the code in <a href=\"https:\/\/github.com\/flippedcoder\/blog-examples\/tree\/main\/food-picker\">the <code>food-picker<\/code> folder of this repo<\/a>. You can see an example of the front-end in <a href=\"https:\/\/codesandbox.io\/s\/dawn-cherry-5i9rr\">this Code Sandbox<\/a>.<\/p>\n<\/div>\n  \n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/dawn-cherry-5i9rr?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"dawn-cherry-5i9rr\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n  <div class=\"wp-block-cloudinary-markdown \"><h2>Conclusion<\/h2>\n<p>Not every project you work on has to be super detailed. Sometimes you just need something to prove a concept or you want to make something for yourself. I know I\u2019ve definitely used this app to pick what I\u2019m going to eat more than I want to admit.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28049,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,177,246,371,303],"class_list":["post-28048","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-javascript","tag-react","tag-under-review","tag-video"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Making a Meal Picker<\/title>\n<meta name=\"description\" content=\"Feeding yourself as an adult is hard sometimes. There are times it&#039;s too hard to make a choice on what to eat because there are too many options. That&#039;s why we&#039;re going to make an app to pick a meal for us!\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Making a Meal Picker\" \/>\n<meta property=\"og:description\" content=\"Feeding yourself as an adult is hard sometimes. There are times it&#039;s too hard to make a choice on what to eat because there are too many options. That&#039;s why we&#039;re going to make an app to pick a meal for us!\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-23T22:59:19+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925558\/Web_Assets\/blog\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd.jpg?_i=AA\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Making a Meal Picker\",\"datePublished\":\"2022-03-23T22:59:19+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/\"},\"wordCount\":4,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925558\/Web_Assets\/blog\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"Javascript\",\"React\",\"Under Review\",\"Video\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/\",\"name\":\"Making a Meal Picker\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925558\/Web_Assets\/blog\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd.jpg?_i=AA\",\"datePublished\":\"2022-03-23T22:59:19+00:00\",\"description\":\"Feeding yourself as an adult is hard sometimes. There are times it's too hard to make a choice on what to eat because there are too many options. That's why we're going to make an app to pick a meal for us!\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925558\/Web_Assets\/blog\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925558\/Web_Assets\/blog\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd.jpg?_i=AA\",\"width\":5503,\"height\":3745},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Making a Meal Picker\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Making a Meal Picker","description":"Feeding yourself as an adult is hard sometimes. There are times it's too hard to make a choice on what to eat because there are too many options. That's why we're going to make an app to pick a meal for us!","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/","og_locale":"en_US","og_type":"article","og_title":"Making a Meal Picker","og_description":"Feeding yourself as an adult is hard sometimes. There are times it's too hard to make a choice on what to eat because there are too many options. That's why we're going to make an app to pick a meal for us!","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-03-23T22:59:19+00:00","twitter_card":"summary_large_image","twitter_image":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925558\/Web_Assets\/blog\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd.jpg?_i=AA","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/"},"author":{"name":"","@id":""},"headline":"Making a Meal Picker","datePublished":"2022-03-23T22:59:19+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/"},"wordCount":4,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925558\/Web_Assets\/blog\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd.jpg?_i=AA","keywords":["Guest Post","Javascript","React","Under Review","Video"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/","name":"Making a Meal Picker","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925558\/Web_Assets\/blog\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd.jpg?_i=AA","datePublished":"2022-03-23T22:59:19+00:00","description":"Feeding yourself as an adult is hard sometimes. There are times it's too hard to make a choice on what to eat because there are too many options. That's why we're going to make an app to pick a meal for us!","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925558\/Web_Assets\/blog\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925558\/Web_Assets\/blog\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd.jpg?_i=AA","width":5503,"height":3745},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/making-a-meal-picker-with-redwood\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Making a Meal Picker"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925558\/Web_Assets\/blog\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd\/a870419ebec927d161adec25e7104b4c4255e590-5503x3745-1_2804946acd.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28048","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28048"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28048\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28049"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28048"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28048"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28048"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}