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 ?
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)
}
};