I am encountering an error in my React app:
react-dom.development.js:15408 Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
Things I’ve tried to fix the issue:
- Versions of React and React DOM: I checked that both
react
andreact-dom
are at the same version in mypackage.json
. - Following the Rules of Hooks: I have ensured that I am using hooks (like
useState
) correctly inside the body of the functional component. There are no hooks inside conditionals or nested functions. - Multiple Copies of React: I used
npm ls
react to ensure there’s only one instance of React in mynode_modules
, and it looks correct.
Additional information:
- I’m not using
npm link
or any local linking methods, so the issue shouldn’t be related to multiple copies of React. - This issue happens even on the first render when I try to call the
HandleSave
function.
The error targets these two lines:
-
Storage
component:const [entries, setEntries] = useState<{ title: string, description: string, status: string }[]>([]);
-
JournalEntry
component:const HandleSave = () => { if(inputValue.trim() && textareaValue.trim()){ onSave({ title: inputValue, description: textareaValue, status: status, }); setInputValue('') setTextAreaValue('') setStatus('Pending') } else{ alert('Please fill out both the title and description!'); }
Here’s my code for routes:
import Home from "./pages/home";
import PrayerJournal from "./pages/prayerjournal";
import JournalEntry from "./pages/journalentry";
import Storage from "./components/Storage";
const routes = [
{
path: "/",
element: <Home/>
},
{
path: "prayer",
element: <PrayerJournal/>
},
{
path: "journalentry",
element: <JournalEntry onSave={Storage}/>
},
]
export default routes;
Here’s my code for Storage
component:
import { useState, useEffect} from "react";
import JournalEntry from "../pages/journalentry";
const Storage = () => {
const [entries, setEntries] = useState<{ title: string, description: string, status: string }[]>([]);
const saveEntry = (entry: { title: string, description: string, status: string }) => {
console.log("saveEntry invoked:", entry);
setEntries(prevEntries => {
const updatedEntries = [...prevEntries, entry]
try {
localStorage.setItem("journalEntries", JSON.stringify(updatedEntries));
console.log("Entries saved to localStorage:", updatedEntries);
} catch (error) {
console.error("Failed to save to localStorage:", error);
}
return updatedEntries;
});
};
useEffect(() => {
const savedEntries = localStorage.getItem('journalEntries')
if (savedEntries) {
try {
const parsedEntries = JSON.parse(savedEntries);
setEntries(parsedEntries);
console.log("Entries loaded from localStorage:", parsedEntries);
} catch (error) {
console.error("Failed to parse localStorage data:", error);
}
}
else {
console.log("No saved entries found in localStorage.");
}
}, [])
console.log("saveEntry function being passed:", saveEntry);
return(
<div>
<JournalEntry onSave={saveEntry} />
<h2>saved entries</h2>
<ul>
{entries.map((entry, index) => {
return (
<li key={index}>
<h3>{entry.title}</h3>
<p>{entry.description}</p>
<p>Status: {entry.status}</p>
</li>)
})}
</ul>
</div>
)
};
export default Storage;
and here’s my code from JournalEntry
component:
import React from "react";
import { useState } from "react";
const JournalEntry = ({onSave}: { onSave: (entry: { title: string, description: string, status: string }) => void }) => {
console.log('onSave prop:', onSave);
const [inputValue, setInputValue] = useState('');
const [textareaValue, setTextAreaValue] = useState('');
const [status, setStatus] = useState('Pending');
const ChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => setInputValue(e.target.value);
const TextAreaChangeValue = (e: React.ChangeEvent<HTMLTextAreaElement>) => setTextAreaValue(e.target.value);
const ChangeStatus = () => {
setStatus(prevStatus => prevStatus === 'Pending' ? 'Answered' : 'Pending');
}
const HandleSave = () => {
if(inputValue.trim() && textareaValue.trim()){
onSave({
title: inputValue,
description: textareaValue,
status: status,
});
setInputValue('')
setTextAreaValue('')
setStatus('Pending')
}
else{
alert('Please fill out both the title and description!');
}
}
return(
<>
<div className="bg-customYellow h-screen ">
<nav className="w-full flex justify-between items-center absolute">
<div className="text-[3rem] font-belle relative top-2 text-textBlackish ml-5">Love, Jesus</div>
<div className="flex gap-[3rem] mr-5 relative top-2">
<div className="flex gap-3 ">
<button className="text-white font-annie bg-customBrown pt-1 pb-1 pr-6 pl-6 rounded-2xl">Cancel</button>
<button className="text-white font-annie bg-customBrown pt-1 pb-1 pr-6 pl-6 rounded-2xl" onClick={HandleSave}>Save</button>
</div>
<div className="w-8 h-8 bg-slate-300 rounded-2xl "></div>
</div>
</nav>
<div className="w-full h-[80%] relative top-[5rem] flex justify-center items-center ">
<div className="flex-col w-[50%] gap-5 h-full flex justify-center items-start relative ">
<div className="relative flex items-center w-full">
<input
className="z-20 peer bg-transparent focus: outline-none w-full h-10 focus:text-[3rem] focus:text-textBlackish focus:font-annie relative"
type="text"
value= {inputValue}
onChange={ChangeValue}
placeholder= ''
/>
<span className="text-[3rem] z-10 font-annie text-textBlackish absolute peer-focus:hidden">Title</span>
</div>
<button
className="pt-1 pb-1 pl-7 pr-7 bg-customBrown text-white rounded-2xl font-annie text-[1rem]"
onClick={ChangeStatus}>{status}</button>
<textarea
className="w-full h-full placeholder:text-textBlackish placeholder:font-annie focus:text-textBlackish focus:font-annie bg-transparent focus:outline-none"
value= {textareaValue}
onChange={TextAreaChangeValue}
placeholder= 'It is time to seek the Lord :)'
/>
</div>
</div>
</div>
</>
)
}
export default JournalEntry;
2
Answers
can’t you change
to
because inside Storage Component, JournalEntry is rendered.
According to your code,
Storage is the parent component and it manage state and also has the save method.
JournalEntry is a child component inside it
Main issue with your code is in your routes you are trying to pass Storage component as a prop instead of function.
As your storage component already using the JournalEntry component it will work as expected.