I have a react component (written with Typescript) as below.
In this component, I try to read the excel in which I have the data & then use the data for rendering the component
Since the data is not read at before the useEffect
is run, the menuData is initialized a null
.
Reading the data works fine & I have no error with menuData.
However, the line let meals = [...new Set(menuData.map((item: menuItem) => item.Meal))];
is throwing the error TypeError: menuData is undefined
. I think this is because this line is executed when the menuData is still undefined
(before the useEffect runs).
How to overcome this error? In other words, how to run let meals = [...new Set(menuData.map((item: menuItem) => item.Meal))];
AFTER useEffect has run?
I would like to keep this line outside of useEffect because there re many more array functions (map,filter...etc) that need to be performed on menuData array of objects.
export const CategoryItems = ({ toggled }: CategoryItemsProps) => {
const [menuData, setMenuData] = useState<menuData | undefined>(undefined);
/*Code to read the excel with menu items once the page is loaded.
The sheet will be read only once*/
useEffect(() => {
let url = "data.xlsx";
........
....
.....
setMenuData(XLSX.utils.sheet_to_json(worksheet, { raw: true }));
}, []);
let meals = [...new Set(menuData.map((item: menuItem) => item.Meal))];
return (
<div className={`category-items ${toggled ? "theme-dark" : "theme-light"}`}>
.
.
.
//additional code to render the component based on the variable `meals`
.
.
.
</div>
</div>
);
};
CodePudding user response:
Just initialize your state as an empty array already.
const [menuData, setMenuData] = useState<menuData>([]);
That should work!
CodePudding user response:
Personally I like to exit the render stage if data is yet to come, no point in going any further as the setState
inside the useEffect
is going to cause another render anyway.
if (!menuData) return null;
let meals = [...new Set(menuData.map((item: menuItem) => item.Meal))];
null
is just a simple way to render nothing..
What I like about this way of doing it, if you find useEffect
can sometimes take some time, instead of returning null
, we can maybe return some form of loading indicator..
if (!menuData) return <div className="loading">Loading..</div>;
let meals = [...new Set(menuData.map((item: menuItem) => item.Meal))];
Initialising the data to something empty works, but you kind of loose some implicit state, and if the structures your rendering are more complex, exiting early I find much easier to reason with..