I have a update button that lets someone update information through a form in Update.js
I'm trying to get the old data stored in my db.json file through json-server and show them by default on the input fields in the Update form in Update.js
Fetching data part is done through a custom hook called useFetch
.
But all I get is a blank page and the object I get from useFetch
is null for some reason (I have included the error I got at bottom)
I've read similar questions here but none seems have the exact problem I'm facing.
Update.js
import { useParams } from "react-router-dom";
import useFetch from "./useFetch";
import { useState } from "react";
const Update = () => {
const {id} = useParams();
const {data: old, isLoading, error} = useFetch(`http://localhost:8000/blogs/${id}`)
// console.log(old.title);
// line 14
const [newTitle, setNewTitle] = useState(old.title);
const [newAuthor, setNewAuthor] = useState('');
const [newBody, setNewBody] = useState('');
// const handleUpdate = () => {
// fetch(`http://localhost:8000/blogs/${id}`,{
// method: 'PUT',
// headers: {'Content-Type': 'application/json'},
// body: JSON.stringify()
// })
// }
return (
<div className="update-article">
{/* <p>{old.title}</p> */}
<form action="">
<label htmlFor="">Blog Title</label>
<input type="text"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
/>
</form>
</div>
);
}
export default Update;
I have used useFetch
to get data in another component and it works without issues there.
UseFetch.js
import { useState, useEffect } from "react";
const useFetch = (url) => {
// vars for blog items in an array
const [data, setData] = useState(null);
//loader
const [isLoading, setIsLoading] = useState(true);
//error storing
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then((response) => {
//check for fetch failure
if(!response.ok){
console.log("Server error");
throw Error("Could not fetch data for that resource");
}
return response.json();
})
.then((data) => {
setData(data);
setIsLoading(false);
setError(null);
})
.catch((error) => {
setError(error.message);
setIsLoading(false);
})
return () => console.log("Cleanup running...");
}, [url]);
return {data, isLoading, error}
}
export default useFetch;
When I try to run this, I get a blank page and in the browser console and I get Uncaught TypeError: old is null
for line 14 in Update.js
Image link
Line 14 is: const [newTitle, setNewTitle] = useState(old.title);
The blank page goes away and shows the input text box if I remove the old.title
in line 14 (setting the initial value of newTitle
to empty string or null)
And if I remove the old.title
as initial value and type console.log(old.title)
. It shows the correct data in the console. So old is not null (?)
What I have tried:
Tried to assign everything in the old
object to temporary variables and assign them to the value field in imput box.
Using defaultValue
instead of value
in input box.
The problem here is that on the first render old will be null as the api call is still pending. Now after the api call is fulfilled, you sure will receive something and so old is no more null.
Here you are accessing title on first render as well as subsequent renders (old.title). So on first render you get the error Uncaught TypeError: old is null
. You cann't access anything on null or undefined object.
Now if we talk about your console log, the reason it's printing the actual value is if you are printing object x times in console, it will only give you the last updated value all the x times. Here is a wonderful answer you can take a look at.
Anyways in order to solve the problem there are 2 ways.
Add ?
before accessing title from old so it will become old?.title
for line 14 in Update.js. This is the easiest method.
Add a useEffect
hook to update newTitle
state whenever there is a change in old
and there also you need a check for null or undefined. Here is the updated code for Update.js
.
import { useParams } from 'react-router-dom';
import useFetch from './useFetch';
import { useState, useEffect } from 'react';
const Update = () => {
const { id } = useParams();
const {
data: old,
isLoading,
error,
} = useFetch(`http://localhost:8000/blogs/${id}`);
// console.log(old.title);
// line 14
const [newTitle, setNewTitle] = useState(null);
const [newAuthor, setNewAuthor] = useState('');
const [newBody, setNewBody] = useState('');
// const handleUpdate = () => {
// fetch(`http://localhost:8000/blogs/${id}`,{
// method: 'PUT',
// headers: {'Content-Type': 'application/json'},
// body: JSON.stringify()
// })
// }
useEffect(() => {
if (old) {
setNewTitle(old.title);
}
}, [old]);
return (
<div className="update-article">
{/* <p>{old.title}</p> */}
<form action="">
<label htmlFor="">Blog Title</label>
<input
type="text"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
/>
</form>
</div>
);
};
export default Update;