Categories
React Projects

Create a Voting App with React and JavaScript

React is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to create a voting app with React and JavaScript.

Create the Project

We can create the React project with Create React App.

To install it, we run:

npx create-react-app voting-app

with NPM to create our React project.

Create the Voting App

To create the voting app, we write:

import React, { useState, useEffect } from "react";

export default function App() {
  const [choice, setChoice] = useState("");
  const [results, setResults] = useState({});

  const vote = () => {
    if (!localStorage.getItem("vote-result")) {
      localStorage.setItem("vote-result", JSON.stringify({}));
    }
    setResults({ ...results, [choice]: (results[choice] ?? 0) + 1 });
  };

  useEffect(() => {
    localStorage.setItem("vote-result", JSON.stringify(results));
  }, [results]);

  return (
    <div>
      <form>
        <div>
          <label>What's your favorite fruit?</label>
          <input
            type="radio"
            value="apple"
            checked={"apple" === choice}
            onChange={(e) => setChoice(e.target.value)}
          />
          apple
          <input
            type="radio"
            value="orange"
            checked={"orange" === choice}
            onChange={(e) => setChoice(e.target.value)}
          />
          orange
          <input
            type="radio"
            value="grape"
            checked={"grape" === choice}
            onChange={(e) => setChoice(e.target.value)}
          />
          grape
        </div>
        <button type="button" onClick={vote}>
          vote
        </button>
      </form>
      <div>
        <h1>result</h1>
        {Object.entries(results).map(([key, val]) => {
          return (
            <p key={key}>
              {key}: {val}
            </p>
          );
        })}
      </div>
    </div>
  );
}

We create the choice state that stores the choice that the user selects.

Then we define the results state that stores the vote results.

Next, we define the vote function that gets the local storage item with the key vote-result.

If it’s not set, then we add it.

Next, we set the results state with setResults .

We make a copy of results and then add the choices to it.

?? is the nullish coalescing operator, which returns 0 only if results[choice] isn’t set or is undefined .

Then we add 1 to it to register the vote.

Next, we call useEffect with a callback that calls localStorage.setItem to store the latest result value in local storage.

Below that, we define a form with the question and the choices to choose from.

We have the radio button inputs.

We set them to different values, and we set the checked value so that they’re checked when we make the choice.

And in the onChange callback, we call setChoice with e.target.value to set choice to the selected value.

We call vote when we click on the vote button.

Below the form, we show the results to the user.

Object.entries returns an array of keys and values of the object in an array.

The array entries are the arrays of keys and values.

Conclusion

We can create a voting app easily with React and JavaScript.

Categories
React Projects

Create a Drawing App with React and JavaScript

React is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to create a drawing app with React and JavaScript.

Create the Project

We can create the React project with Create React App.

To install it, we run:

npx create-react-app drawing-app

with NPM to create our React project.

Create the Drawing App

To create the drawing app, we write:

import React, { useEffect, useRef, useState } from "react";

export default function App() {
  const canvas = useRef();
  const [pos, setPos] = useState({});

  const setPosition = (e) => {
    setPos({
      x: e.clientX,
      y: e.clientY
    });
  };

  const resize = () => {
    const ctx = canvas.current.getContext("2d");
    ctx.canvas.width = window.innerWidth;
    ctx.canvas.height = window.innerHeight;
  };

  const draw = (e) => {
    if (e.buttons !== 1) {
      return;
    }
    const ctx = canvas.current.getContext("2d");
    ctx.beginPath();
    ctx.lineWidth = 5;
    ctx.lineCap = "round";
    ctx.strokeStyle = "green";
    ctx.moveTo(pos.x, pos.y);
    setPosition(e);
    ctx.lineTo(pos.x, pos.y);
    ctx.stroke();
  };

  useEffect(() => {
    window.addEventListener("resize", resize);
    return () => window.removeEventListener("resize", resize);
  }, []);

  return (
    <div>
      <style>
        {`
          #canvas {
            border: 1px solid black;
          }
        `}
      </style>
      <canvas
        ref={canvas}
        onMouseMove={draw}
        onMouseDown={setPosition}
        onMouseEnter={setPosition}
        id="canvas"
      ></canvas>
    </div>
  );
}

We create the canvas ref which will be assigned to the canvas element.

Then we define the pos state to set the position of the canvas pointer.

Then we define the setPosition function which sets the pos state value from the clientX and clientY event property values.

Next, we define the resize function that gets the canvas and then set the width and height of the canvas when the window is resized.

After that, we define the draw function to draw the line.

We get the e parameter with the event object.

First, we check if we click on the left button by checking if e.buttons is 1.

We continue only if e.buttons is 1.

Next, we get the canvas element object.

Then we call beginPath to start drawing.

And we set the line width, line cap style, and stroke style.

Then we call moveTo to move the pointer to the given position.

Then we call setPosition to set the pos value to the same value.

And we call lineTo again with the end coordinate of the line to set the end coordinate of the line.

And then we call ctx.stroke to draw the line.

Then we add the useEffect hook with a callback that resizes the canvas when we resize the window.

And below that, we add the style element to add a border to the canvas.

And finally, we add the canvas and assign the event handlers that are run when we click and move the mouse.

Conclusion

We can create a drawing app easily with React and JavaScript.

Categories
React Projects

Create a Currency Converter with React and JavaScript

React is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to create a currency converter with React and JavaScript.

Create the Project

We can create the React project with Create React App.

To install it, we run:

npx create-react-app currency-converter

with NPM to create our React project.

Create the Currency Converter

To create the currency converter, we write:

import React, { useMemo, useState } from "react";

export default function App() {
  const [value, setValue] = useState(0);
  const [fromCurrency, setFromCurrency] = useState("");
  const [toCurrency, setToCurrency] = useState("");
  const [currencies] = useState(["EUR", "USD", "CAD"]);
  const [result, setResult] = useState(0);

  const fromCurrencies = useMemo(() => {
    return currencies.filter((c) => c !== toCurrency);
  }, [currencies, toCurrency]);

  const toCurrencies = useMemo(() => {
    return currencies.filter((c) => c !== fromCurrency);
  }, [currencies, fromCurrency]);

  const convert = async (e) => {
    e.preventDefault();
    const formValid = +value >= 0 && fromCurrency && toCurrency;
    if (!formValid) {
      return;
    }
    const res = await fetch(
      `https://api.exchangeratesapi.io/latest?base=${fromCurrency}`
    );
    const { rates } = await res.json();
    setResult(+value * rates[toCurrency]);
  };

  return (
    <div>
      <form onSubmit={convert}>
        <div>
          <label>value</label>
          <input value={value} onChange={(e) => setValue(e.target.value)} />
        </div>
        <div>
          <label>from currency</label>
          <select
            value={fromCurrency}
            onChange={(e) => setFromCurrency(e.target.value)}
          >
            {fromCurrencies.map((c) => (
              <option key={c}>{c}</option>
            ))}
          </select>
        </div>
        <div>
          <label>to currency</label>
          <select
            value={toCurrency}
            onChange={(e) => setToCurrency(e.target.value)}
          >
            {toCurrencies.map((c) => (
              <option key={c}>{c}</option>
            ))}
          </select>
        </div>
        <button type="submit">convert</button>
      </form>
      <div>
        {value} {fromCurrency} is {result.toFixed(2)} {toCurrency}
      </div>
    </div>
  );
}

We have the value state that stores the value of the currency value to convert from.

fromCurrency has the currency to convert from.

toCurrency has the currency to convert to,

currencies has the currency choices.

result has the converted result.

fromCurrencies has the choices we can choose to convert from.

We use the useMemo hook with a callback to return all the currencies that we can pick from.

We exclude the toCurrency value from the returned array.

The 2nd argument has the values to watch in order for the first callback to run again to update the returned value.

Likewise, we do the same to define toCurrencies but we exclude the fromCurrency value instead.

Next, we define the convert function that does the currency conversion.

Inside it, we call e.preventDefault() to let us do client-side form submission.

Then we check if value is bigger than 0 and fromCurrency and toCurrency are set.

If all the conditions are met, we get the exchange rate from the Exchange Rate API.

Then we call setResult to compute converted currency value.

Next, we add the form to let us enter the values.

It has the onSubmit prop set to the convert function, which is run when we click on the button that has the type submit .

In it, it has an input to set the value .

e.target.value has the inputted value.

onChange is run whenever we change the entered value.

Likewise, we have the select dropdowns that lets us choose the currencies to convert from and to respectively.

We set their values as we did with the input with the value and onChange props.

We render the fromCurrencies and toCurrencies inside the select element to render the choices.

Below the form, we show the result to the user.

The toFixed method returns the string form of the number with the number of decimal places we pass in.

Conclusion

We can create a currency converter with React and JavaScript.

Categories
React

How to add Drag and Drop features to your React app

Drag and drop is a commonly used feature in web apps. It provides the benefit of an intuitive way to manipulate data. React is a great library to use for building your web UI since it has high quality drag and drop libraries written for it in the form of React components. react-beautiful-dnd is written by Atlassian, the creator of JIRA task management system. It is a drag and drop library that is one of the easiest to incorporate into your app.

In this story, we will build a to-do list which incorporates drag and drop capability. We have a home page with a form to create a task at the top, then below it, there will be 2 lists side by side, with the left list displaying the to do item, and the right list displaying the done items. There will also be a way for user to delete the task from any of the 2 lists. A Redux store will be used to store the whole to do list.

To start building the app, we use one of the easiest way possible, the Create React App program from Facebook. To use it, we run npx create-react-app todo-app . This will create the project folder with the initial app code inside.

Next we install some libraries that we need. We need a HTTP client, the react-beautiful-dnd library, Bootstrap, Redux and React Router. We need a HTTP client to make HTTP requests, Bootstrap makes styling easy, and we use React Router for client side routing. We run npm i axios bootstrap react-bootstrap formik yup react-beautiful-dnd react-router-dom react-redux to install the libraries. Axios is our HTTP client, and Formik and Yup are form validation libraries that can be used with React Bootstrap to save some effort for creating the form.

With all the libraries we need installed, we can start writing the app. We put everything in the src folder unless mentioned otherwise. To start, we create a file called actionCreator.js and add:

import { SET_TASKS } from './actions';

const setTasks = (tasks) => {
    return {
        type: SET_TASKS,
        payload: tasks
    }
};

export { setTasks };

and we create action.js and in it, we put:

const SET_TASKS = 'SET_TASKS';

export { SET_TASKS };

These 2 files together create the action we dispatch to the Redux store which we will build shortly.

Next in App.js , we replace what is there with the following:

import React from 'react';
import { Router, Route, Link } from "react-router-dom";
import HomePage from './HomePage';
import { createBrowserHistory as createHistory } from 'history'
import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import './App.css';
const history = createHistory();

function App() {
  return (
    <div className="App">
      <Router history={history}>
        <Navbar bg="primary" expand="lg" variant="dark" >
          <Navbar.Brand href="#home">Drag and Drop App</Navbar.Brand>
          <Navbar.Toggle aria-controls="basic-navbar-nav" />
          <Navbar.Collapse id="basic-navbar-nav">
            <Nav className="mr-auto">
              <Nav.Link href="/">Home</Nav.Link>
            </Nav>
          </Navbar.Collapse>
        </Navbar>
        <Route path="/" exact component={HomePage} />
      </Router>
    </div>
  );
}

export default App;

This adds the navigation bar on the top and let us display the routes we define on the bottom. Home page should be the only route.

In HomePage.js , we put:

import React from 'react';
import { useState, useEffect } from 'react';
import './HomePage.css';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import { connect } from 'react-redux';
import TaskForm from './TaskForm';
import { editTask, getTasks, deleteTask } from './requests';

function HomePage({ tasks }) {
  const [items, setItems] = useState([]);
  const [todoItems, setTodoItems] = useState([]);
  const [doneItems, setDoneItems] = useState([]);
  const [initialized, setInitialized] = useState(false);

  const onDragEnd = async (evt) => {
    const { source } = evt;
    let item = {};
    if (source.droppableId == "todoDroppable") {
      item = todoItems[source.index];
      item.done = true;
    }
    else {
      item = doneItems[source.index];
      item.done = false;
    }
    await editTask(item);
    await getTodos();
  };

  const setAllItems = (data) => {
    if (!Array.isArray(data)) {
      return;
    }
    setItems(data);
    setTodoItems(data.filter(i => !i.done));
    setDoneItems(data.filter(i => i.done));
  }

  const getTodos = async () => {
    const response = await getTasks();
    setAllItems(response.data);
    setInitialized(true);
  }

  const removeTodo = async (task) => {
    await deleteTask(task.id);
    await getTodos();
  }

  useEffect(() => {
    setAllItems(tasks);
    if (!initialized) {
      getTodos();
    }
  }, [tasks]);

  return (
    <div className="App">
      <div className='col-12'>
        <TaskForm />
        <br />
      </div>
      <div className='col-12'>
        <div className='row list'>
          <DragDropContext onDragEnd={onDragEnd}>
            <Droppable droppableId="todoDroppable">
              {(provided, snapshot) => (
                <div
                  className='droppable'
                  ref={provided.innerRef}
                >
                  &nbsp;
                  <h2>To Do</h2>
                  <div class="list-group">
                    {todoItems.map((item, index) => (
                      <Draggable
                        key={item.id}
                        draggableId={item.id}
                        index={index}
                      >
                        {(provided, snapshot) => (
                          <div
                            className='list-group-item '
                            ref={provided.innerRef}
                            {...provided.draggableProps}
                            {...provided.dragHandleProps}
                          >
                            {item.description}
                            <a onClick={removeTodo.bind(this, item)}>
                              <i class="fa fa-close"></i>
                            </a>
                          </div>
                        )}
                      </Draggable>
                    ))}
                  </div>
                  {provided.placeholder}
                </div>
              )}
            </Droppable>
            <Droppable droppableId="doneDroppable">
              {(provided, snapshot) => (
                <div
                  className='droppable'
                  ref={provided.innerRef}
                >
                  &nbsp;
                  <h2>Done</h2>
                  <div class="list-group">
                    {doneItems.map((item, index) => (
                      <Draggable
                        key={item.id}
                        draggableId={item.id}
                        index={index}
                      >
                        {(provided, snapshot) => (
                          <div
                            className='list-group-item'
                            ref={provided.innerRef}
                            {...provided.draggableProps}
                            {...provided.dragHandleProps}
                          >
                            {item.description}
                            <a onClick={removeTodo.bind(this, item)}>
                              <i class="fa fa-close"></i>
                            </a>
                          </div>
                        )}
                      </Draggable>
                    ))}
                  </div>
                  {provided.placeholder}
                </div>
              )}
            </Droppable>
          </DragDropContext>
        </div>
      </div>
    </div>
  );
}

const mapStateToProps = state => {
  return {
    tasks: state.tasks,
  }
}

export default connect(
  mapStateToProps,
  null
)(HomePage);

This is where the drag and drop logic resides. We have 2 Droppable components where we have the 2 lists to store the to do and done items. The only drag and drop handler we need is the onDragEnd handler. This is where we update the done flag of the task item according to the source where the item is dragged from. If we drag from the Droppable component with the droppableId of todoDroppable , then we set done to true . Otherwise, we set done to false . After that is done, we update the task, then get the latest tasks, and display them. We also have a function to remove a task called removeTodo where we can remove a task. Once it’s removed the latest list will be obtained from back end. We have setAllItems function to distribute the the tasks into the to do and done item lists.

In the useEffect function, we have a second argument with the array with tasks as the argument. This is for triggering the useEffect callback to be called the tasks changes. The same thing can be used for handling any prop change, if you replace tasks with some other prop.

In HomePage.css , we put:

.item {
  padding: 20px;
  border: 1px solid black;
  width: 40vw;
}

.list {
  margin-left: 0px;
}

.list-group-item {
  display: flex;
  justify-content: space-between;
}

.list-group-item a {
  cursor: pointer;
}

.droppable {
  min-height: 100px;
  width: 40vw;
  margin-right: 9vw;
}

to style our lists by adding some spacing to them.

For add tasks, we have a dedicated TaskForm component to add a task. We create a file called TaskForm.js and add:

import React from 'react';
import { Formik } from 'formik';
import Form from 'react-bootstrap/Form';
import Col from 'react-bootstrap/Col';
import Button from 'react-bootstrap/Button';
import * as yup from 'yup';
import { addTask, getTasks } from './requests';
import { connect } from 'react-redux';
import { setTasks } from './actionCreators';
import './TaskForm.css';

const schema = yup.object({
    description: yup.string().required('Description is required'),
});

function ContactForm({ setTasks }) {
    const handleSubmit = async (evt) => {
        const isValid = await schema.validate(evt);
        if (!isValid) {
            return;
        }
        await addTask(evt);
        const response = await getTasks();
        setTasks(response.data);
    }

    return (
        <div className="form">
            <Formik
                validationSchema={schema}
                onSubmit={handleSubmit}
            >
                {({
                    handleSubmit,
                    handleChange,
                    handleBlur,
                    values,
                    touched,
                    isInvalid,
                    errors,
                }) => (
                        <Form noValidate onSubmit={handleSubmit}>
                            <Form.Row>
                                <Form.Group as={Col} md="12" controlId="firstName">
                                    <Form.Label>
                                        <h4>Add Task</h4>
                                    </Form.Label>
                                    <Form.Control
                                        type="text"
                                        name="description"
                                        placeholder="Task Description"
                                        value={values.description || ''}
                                        onChange={handleChange}
                                        isInvalid={touched.description && errors.description}
                                    />
                                    <Form.Control.Feedback type="invalid">
                                        {errors.description}
                                    </Form.Control.Feedback>
                                </Form.Group>
                            </Form.Row>
                            <Button type="submit" style={{ 'marginRight': '10px' }}>Save</Button>
                        </Form>
                    )}
            </Formik>
        </div>
    );
}

ContactForm.propTypes = {
}

const mapStateToProps = state => {
    return {
        tasks: state.tasks,
    }
}

const mapDispatchToProps = dispatch => ({
    setTasks: tasks => dispatch(setTasks(tasks))
})

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(ContactForm);

We have the add task form with form validation to check if the description is filled in. Once it’s valid, we submit it to back end, then get the latest tasks and store them in our Redux store. That is all done in the handleSubmit function. Notice that we just create a schema object with the Yup library and then pass that into the validationSchema object of the Formik component, then the Formik component provides the handleChange function, values object, and errors object which we use in our React Bootstrap form directly, saving us from writing all the change handler code ourselves. Also we need || ‘’ at the end of values prop so that the value prop stays defined all time, preventing the triggering of uncontrolled input errors. The mapStateToProps at the bottom of the file map the tasks state in the store to the tasks props of our component and mapDispatchToProps maps our setTasks dispatch function which we use to update the store with tasks to the props of the TaskForm component.

Next in index.js , we replace the existing code with:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { tasksReducer } from './reducers';
import { Provider } from 'react-redux'
import { createStore, combineReducers } from 'redux'

const taskskApp = combineReducers({
    tasks: tasksReducer,
})

const store = createStore(taskskApp);

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>
    , document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

to so that we can use the Redux store that we created in our app.

Then we create a file called reducers.js and add:

import { SET_TASKS } from './actions';

function tasksReducer(state = {}, action) {
    switch (action.type) {
        case SET_TASKS:
            state = JSON.parse(JSON.stringify(action.payload));
            return state;
        default:
            return state
    }
}

export { tasksReducer };

so that we can store our tasks into the store.

Next we make a file called requests.js to store the code for the functions for the HTTP requests that we make. We add:

const APIURL = 'http://localhost:3000';
const axios = require('axios');
export const getTasks = () => axios.get(`${APIURL}/tasks`);

export const addTask = (data) => axios.post(`${APIURL}/tasks`, data);

export const editTask = (data) => axios.put(`${APIURL}/tasks/${data.id}`, data);

export const deleteTask = (id) => axios.delete(`${APIURL}/tasks/${id}`);

to let us do CRUD operations on our tasks. It is used in HomePage and TaskForm component.

In public/index.html , we replace the existing code with:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="logo192.png" />
  <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
  <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
  <title>React Drag and Drop App</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />
  <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"
    integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
</head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
</body>

</html>

We added the Bootstrap CSS and Font Awesome icon so that we get the Bootstrap style and the close icon that we used in the HomePage ‘s i tag.

To start back end, we first install the json-server package by running npm i json-server . Them go to our project folder and run:

json-server --watch db.json

In db.json , change the text to:

{
  "tasks": [
  ]
}

so that we have the tasks endpoints defined in requests.js available.

Categories
React Projects

Create a Recipe App with React and JavaScript

React is an easy to use JavaScript framework that lets us create front end apps.

In this article, we’ll look at how to create a recipe app with React and JavaScript.

Create the Project

We can create the React project with Create React App.

To install it, we run:

npx create-react-app recipe-app

with NPM to create our React project.

We also need the uuid package to let us generate unique IDs for our recipe items.

To do this, we run:

npm i uuid

Create the Recipe App

To create the recipe app, we write:

import React, { useState } from "react";
import { v4 as uuidv4 } from "uuid";

export default function App() {
  const [recipe, setRecipe] = useState({
    name: "",
    ingredients: "",
    steps: ""
  });
  const [recipes, setRecipes] = useState([]);

  const addRecipe = (e) => {
    e.preventDefault();
    const { name, ingredients, steps } = recipe;
    const formValid = name && ingredients && steps;
    if (!formValid) {
      return;
    }
    setRecipes((recipes) => [
      ...recipes,
      {
        id: uuidv4(),
        ...recipe
      }
    ]);
  };

  const deleteRecipe = (index) => {
    setRecipes((recipes) => recipes.filter((_, i) => i !== index));
  };

  return (
    <div>
      <style>
        {`
        .content {
          white-space: pre-wrap;
        }
        `}
      </style>
      <form onSubmit={addRecipe}>
        <div>
          <label>name</label>
          <input
            value={recipe.name}
            onChange={(e) =>
              setRecipe((recipe) => ({ ...recipe, name: e.target.value }))
            }
          />
        </div>
        <div>
          <label>ingredients</label>
          <input
            value={recipe.ingredients}
            onChange={(e) =>
              setRecipe((recipe) => ({
                ...recipe,
                ingredients: e.target.value
              }))
            }
          />
        </div>
        <div>
          <label>steps</label>
          <textarea
            value={recipe.steps}
            onChange={(e) =>
              setRecipe((recipe) => ({ ...recipe, steps: e.target.value }))
            }
          ></textarea>
        </div>
        <button type="submit">add recipe</button>
      </form>
      {recipes.map((r, index) => {
        return (
          <div key={r.id}>
            <h1>{r.name}</h1>
            <h2>ingredients</h2>
            <div className="content">{r.ingredients}</div>
            <h2>steps</h2>
            <div className="content">{r.steps}</div>
            <button className="button" onClick={() => deleteRecipe(index)}>
              delete
            </button>
          </div>
        );
      })}
    </div>
  );
}

We have the recipe state which is used to store the form data.

recipes has the recipe entries.

Then we define the addRecipe function that lets us add a recipe.

Inside it, we call e.preventDefault() to do client-side form submission.

Then we check if name , ingredients , and steps are set.

If they are, then we call setRecipes to add the entry.

We pass in a callback that takes the existing recipes value, then we return a copy of it with an object that has the new entry at the end.

We call uuidv4 to return a unique ID for the new entry.

The deleteRecipe function calls setRecipes with a callback that takes the existing recipes . Then it returns a copy of it without the entry at the given index .

Below that, we add the style element to style the steps.

And we have the form with the onSubmit prop set to addRecipe to add an entry when we click on the button with type submit .

Inside the form, we have the inputs with an onChange prop that are set to functions that call setRecipe to set the inputted value to a property in the recipe object.

Below the form, we render the recipes values into a div.

Inside it, we show the name , ingredients , and steps .

And below that, we have a button that calls deleteRecipe with the index when we click it.

Conclusion

We can create a recipe app with React and JavaScript.