I'm Building a react/nextjs/redux app I have components each will call an individual backend API
The issue I'm having is making the component that loads in the data more reusable there is some repetitiveness that I am hoping to get rid of
Component 1:
import { StateLoading } from "@/shared/constants/loading";
import { useSelectorEffect } from "@/hooks/useSelector";
import { getGames, selectGames, selectStatus } from "@/store/games/gamesSlice";
import { Game } from "@/shared/interfaces/game";
const GamesDisplay = () => {
const games = useAppSelector(selectGames) as Game[];
const isClientLoaded = useSelectorEffect(games, getGames); //custom hook below
const isLoading = useAppSelector(selectStatus) === StateLoading.LOADING;
if (isLoading) {
return <div>Loading....</div>;
}
return (
<>
<div>Games Display</div>
{isClientLoaded && (
<ul>
{games?.map((comic: Game) => (
<li key={comic.id}>{comic.title}</li>
))}
</ul>
)}
</>
);
};
export default GamesDisplay;
Component 2:
import { Comic } from "@/shared/interfaces/comic";
import { StateLoading } from "@/shared/constants/loading";
import { useSelectorEffect } from "@/hooks/useSelector";
const ComicsDisplay = () => {
const comics = useAppSelector(selectComicsArray) as Comic[];
const isClientLoaded = useSelectorEffect(comics, getComics); //custom hook below
const isLoading = useAppSelector(selectStatus) === StateLoading.LOADING;
if (isLoading) {
return <div>Loading....</div>;
}
return (
<>
<div>MusicDisplay</div>
{isClientLoaded && (
<ul>
{comics?.map((comic: Comic) => (
<li key={comic.id}>{comic.title}</li>
))}
</ul>
)}
</>
);
};
export default ComicsDisplay;
As you can see they're quite similar
custom hook:
import { useEffect, useState } from "react";
import { useAppDispatch } from "./store.hooks";
export const useSelectorEffect = (slice: any, dispatchAction: any) => {
const dispatch = useAppDispatch();
const [isClient, setIsClient] = useState(false);
useEffect(() => {
if (slice === undefined) {
dispatch(dispatchAction());
}
setIsClient(true);
}, [slice, dispatchAction, dispatch, isClient]);
return isClient;
};
You'll need to declare an interface for Comic and Game.
import React from "react";
import { StateLoading } from "@/shared/constants/loading";
import { useSelectorEffect } from "@/hooks/useSelector";
import { useAppSelector } from "@/store/store.hooks";
interface ReusableDisplayProps<T> {
itemsSelector: (state: any) => T[];
statusSelector: (state: any) => string;
fetchAction: () => void;
itemRenderer: (item: T) => React.ReactNode;
title: string;
}
const ReusableDisplay = <T extends { id: string }>({
itemsSelector,
statusSelector,
fetchAction,
itemRenderer,
title,
}: ReusableDisplayProps<T>) => {
const items = useAppSelector(itemsSelector);
const isClientLoaded = useSelectorEffect(items, fetchAction);
const isLoading = useAppSelector(statusSelector) === StateLoading.LOADING;
if (isLoading) {
return <div>Loading....</div>;
}
return (
<>
<div>{title}</div>
{isClientLoaded && (
<ul>
{items?.map((item) => (
<li key={item.id}>{itemRenderer(item)}</li>
))}
</ul>
)}
</>
);
};
export default ReusableDisplay;
Game display
const GamesDisplay = () => {
return (
<ReusableDisplay<Game>
itemsSelector={selectGames}
statusSelector={selectStatus}
fetchAction={getGames}
itemRenderer={(game) => game.title}
title="Games Display"
/>
);
};
export default GamesDisplay;
Component 2:
const ComicsDisplay = () => {
return (
<ReusableDisplay<Comic>
itemsSelector={selectComicsArray}
statusSelector={selectStatus}
fetchAction={getComics}
itemRenderer={(comic) => comic.title}
title="Comics Display"
/>
);
};
export default ComicsDisplay;