Hey, in short:
I want to make an Article page and pass the current article to a child component called "ArticleButtons".
This is how the Article object looks like, it also contains an Author object as property, I show below some init objects I made so that I can so how the page would look like even without any data fom the backend
const initAuthor: TUser = {
id: null,
username: "author",
email: "",
password: "",
image: "",
bio: "",
followersCount: 0,
followingCount: 0,
createdAt: null,
updatedAt: null
}
const initArticle: TArticle = {
id: null,
userId: null,
title: "title",
author: initAuthor,
description: "",
body: "<h1>Hello arti</h1>",
slug: "",
tagList: [],
isFav: false,
favoritesCount: 0,
createdAt: null,
updatedAt: null
}
Now, I'm passing the current article as a state with its correspondingly setArticleState function. But when my UseEffect() function fetch data from the backend the ArticleButtons throws exception because "author is undefined, here is an screenshot of the error:console error, und 58 is the line who launchs that error, the return statement.
function ArticleButtons({ article = initArticle, setArticle } : {
article: TArticle,
setArticle: React.Dispatch<SetStateAction<TArticle>>
}) {
const { author } = article;
const { authState } = useAuth() as TAuthContext;
const { loggedUser } = authState;
const { slug } = useParams();
const checkFollow = (authorId: number | null) => {
if (!authorId) return false;
return false;
}
const handleFollow = (author: TUser) => {
setArticle((prev) => ({...prev, author}))
};
const handleFav = (article: TArticle) => {
setArticle((prev) => ({...prev, article}))
}
//>>>>line 58 is below<<<<
return loggedUser.username === author.username ? (
<AuthorButtons {...article } slug={slug}/>
) : (
<div className="article-buttons">
<FollowButton isFollowing={checkFollow(author.id) } {...author} handler={handleFollow}/>
<FavButton {...article} handleFav={handleFav}/>
</div>
)
}
export default ArticleButtons;
As you can see in the parameters, I tried givin an initial value of initArticle to the ArticleButtons component so it don't blow up at first, but when the data from the backend is retrieved then it gives that undefined error.
I put some console.logs in there to see the data from the backend, and it seems to be just exactly as it has to be, here they are the screenshots Up is the initArticle data, below is the article data retrieved from the backend:
article data, as you can see, the author has all its properties defined
well, the problem seems to be happening when setArticle is called, I also tried another things like calling "setArticle(initArticle2)" with another fake initArticle2, and it works... there should be an issue when retrieving data from the backend?
- I also planned to then simply pass the slug to the ArticleButton component and then it will retrieve the article itself but I would like to make this work and do not use that workaround, most important, would like to learn why this is happening.
Thanks for your time mate
PD: here is the Article parent component
function Article() {
const { state } = useLocation();
const navigate = useNavigate();
const [ article, setArticle ] = useState<TArticle>(state || initArticle);
const { title, description, body, createdAt, author, tagList } = article || {};
const { authState } = useAuth() as TAuthContext;
const { headers, isAuth } = authState;
const { slug } = useParams();
useEffect(() => {
console.log("article ", article)
if (state) return;
getArticleBySlug({slug: slug || "", headers: headers })
.then((articleData) => {
console.log("artData", articleData)
setArticle(articleData)})
.catch((error) => {
console.error(error);
navigate("/not-found", { replace:true })
});
}, [slug, headers, isAuth, state, navigate])
return (
<div className="article-page">
<BannerContainer>
<h1>{title}</h1>
</BannerContainer>
<ArticleMeta
createdAt={createdAt}
author={author}>
<ArticleButtons article={article} setArticle={setArticle}/>
</ArticleMeta>
<ContainerRow addClass={"articlecont"}>
{body && <Markdown options={{ forceBlock:true }}>{body}</Markdown>}
<ArticleTags tagList={article.tagList}/>
<div className="row">
<ArticleMeta
createdAt={createdAt}
author={author}>
<ArticleButtons article={article} setArticle={setArticle}/>
</ArticleMeta>
</div>
</ContainerRow>
<Outlet/>
</div>
);
}
export default Article;
Instead of putting some
initArticleyou should make a check when the actual article is fetched (in the parent).Basically put a flag before the child component to optionally render it:
While fetching things from the Backend you usually have to display some sort of loading image/spinner instead, because the operation is async. Also the
initArticlethen is not needed anymoreYou don't posted which outcome you expect, but this solution should atleast fix the error from happening.