skip to Main Content

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

article data with author

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;

2

Answers


  1. Chosen as BEST ANSWER

    The solution was to learn the basics of states with the docs, and the issue was the following:

    • getArticleBySlug is supposed to return a TArticle object.

    • a TArticle object has a nested object TUser (the 'author' property).

    • My data was fetched correctly as you can see in the logs, but react can not figure out the type of 'author'

    Why? A nested object is not an object living inside of another, like

    
    let obj1 = {
       brand: "Fakebrand",
       model: "Ultimate"
    }
    
    let obj2 = {
      name = "myFactory",
      tools: machine
    }
    

    A third object could also point to the first one,

    let obj3 = {
       name: "My shop",
       tools: machine
    }
    

    and if you would change obj3.tools.brand = "Another brand", it would affect both obj2.tools.brand and the obj1 itself, because it is all the same object.

    So I assumed that when I fetched the article data, I was going to get a TAuthor object as well just because it was inside of my article, it does not work that way.

    So I basically have to set two things, 1. Article, 2. Author in order to render my component, getArticleBySlug gives me only a TArticle, and it is what it does, it does not give a TUser just because it is inside of TArticle, so you get it as undefined, even if my data was correct and had all the expected properties.

    So the solution was, once TArticle is fetched, make another call to get TUser data, like so:

     useEffect(() => {
            if (!headers ) return;
            if (!slug || state) return;
    
            setLoading(true);
            getArticleBySlug({headers, slug})
            .then((article) => {
                getUserById({headers, userId: article.userId})
                .then((author) => {
                    article.author = author;
                    setArticle(article);
                })
                .catch((error) => { 
                    errorHandler(error) 
                    navigate("/not-found", { replace: true })
                })
            })
            .catch((error) => { 
                errorHandler(error) 
                navigate("/not-found", { replace: true })
            })
            .finally(() => { setLoading(false) })
        },[slug, headers])
    

    Is a async function inside of another, but it solved the issue. Notice that I could not simply call setArticle(articleData) and then mutate like article.author = authorData, because despite of objects being mutable, you are not actually re-rendering the page again so you won't see those changes.

    Recommend to experiment, fail yourself and read the docs, it works.

    Hope this is useful.


  2. Instead of putting some initArticle you 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:

    {!!article && <ArticleButtons article={article} setArticle={setArticle}/>}
    {!artice && <>Loading Article...</>}
    

    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 initArticle then is not needed anymore

    You don’t posted which outcome you expect, but this solution should atleast fix the error from happening.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search