Search code examples
reactjsapijson-server

React form data submission to an api doesn't show changes in ui


I am working on a simple react app that takes a todo title via a form, and submits to my own API created with json-server package. My code is working so far but, after submitting the data I have to reload the app to show the changes. Now my question is, how to show changes in the UI not by reloading the page ?

I am giving my code references

Simple db.json file in root directory

{
    "todos": [
        {
            "id": 1,
            "title": "first todo"
        },
        {
            "id": 2,
            "title": "second todo"
        },
        {
            "id": 3,
            "title": "third todo"
        }
    ]
}

To run the json-server run this script "json-server": "json-server --watch db.json --port 3001"

In App.js

import { useState, useEffect } from "react";
import axios from "axios";
import TodoForm from "./components/TodoForm";
import TodoList from "./components/TodoList";

const App = () => {
    const [todos, setTodos] = useState();

    useEffect(() => {
        async function fetchData() {
            const { data } = await axios.get("http://localhost:3001/todos");
            setTodos(data);
        }
        fetchData();
    }, []);

    return (
        <>
            <TodoForm />
            <TodoList todos={todos} />
        </>
    );
};

export default App;

In components/TodoForm/index.js

import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
import axios from "axios";

const initialValues = {
    title: "",
};

const validationSchema = Yup.object({
    title: Yup.string().required("Required!"),
});

const onSubmit = async (values) => {
    console.log("Form data", values);
    await axios.post("http://localhost:3001/todos", values);
};

const TodoForm = () => {
    return (
        <Formik
            initialValues={initialValues}
            validationSchema={validationSchema}
            onSubmit={onSubmit}
        >
            <Form className="bg-slate-100 p-4 max-w-sm flex flex-col gap-4">
                <div className="flex gap-2 flex-col">
                    <label
                        htmlFor="title"
                        className="font-poppins font-bold text-lg"
                    >
                        Title
                    </label>
                    <Field name="title" className="rounded-md">
                        {({ field, form, meta }) => {
                            return (
                                <div>
                                    <input
                                        type="text"
                                        id="title"
                                        className="rounded-md"
                                        {...field}
                                    />
                                    {meta.touched && meta.error && (
                                        <p className="text-sm text-red-500">
                                            {meta.error}
                                        </p>
                                    )}
                                </div>
                            );
                        }}
                    </Field>
                </div>

                <button
                    type="submit"
                    className="px-4 py-1 bg-green-500 rounded-md"
                >
                    Submit
                </button>
            </Form>
        </Formik>
    );
};

export default TodoForm;

In components/TodoList/index.js

const TodoList = ({ todos }) => {
    return (
        <>
            {todos && todos.length > 0 ? (
                <>
                    <p className="text-5xl font-poppins font-bold">
                        Todos length: {todos.length}
                    </p>
                    <ul>
                        {todos.map((todo) => (
                            <li key={todo.id}>{todo.title}</li>
                        ))}
                    </ul>
                </>
            ) : (
                <p>Yay! everything is done. 😊</p>
            )}
        </>
    );
};

export default TodoList;

Basically, what I have done is created a <TodoForm /> components with formik and yup also fetching data with axios for simplicity. <TodoList todos={todos} /> is responsible to show all todos that has been saved to todos state in App.js. In App.js useEffect function is responsible for fetching the data from our created api with json server. Now please analyze and tell me what I am doing wrong to achieve my goal ?


Solution

  • You can do this in multiple ways. One simple idea would be to pass a callback from App into TodoForm as a prop. And invoke it when the new todo submission finishes.

    In App:

    <TodoForm addTodo={newTodo => setTodos([...todos, newTodo])} />
    

    In TodoForm:

    const {addTodo} = props;
    const onSubmit = async (values) => {
      try {
        console.log("Form data", values);
        await axios.post("http://localhost:3001/todos", values);
        addTodo(values);
      } catch (err) {
        console.error(err)
      }
    };
    

    Alternatively, this method addTodo will not directly set the state but instead will fetch the whole list of todos from the server immediately after a new one is created.

    In App.js:

    <TodoForm fetchTodos={() => {
           const { data } = await axios.get("http://localhost:3001/todos");
           setTodos(data);
    } />
    

    In TodoForm:

    const {fetchTodos} = props;
    const onSubmit = async (values) => {
      try {
        console.log("Form data", values);
        await axios.post("http://localhost:3001/todos", values);
        fetchTodos();
      } catch (err) {
        console.error(err)
      }
    };