Home > OS >  React after refresh TypeError: articles[(articles.length - 1)] is undefined
React after refresh TypeError: articles[(articles.length - 1)] is undefined

Time:09-24

I am making a react app and I have a problem. Whenever I refresh the page I get the TypeError: articles[(articles.length - 1)] is undefined and it happens beacause of the src parameter. I want to display images of 3 newest articles that I have added to json server. But if i change src to for example "" and after that i run server everything works fine. I can change the src value back to articles[(articles.length - 1)] and without refreshing everything as it should. I think problem is that articles are being compiled before they gets the value from json server and I don't know how can it can be fixed. Tried many ways like fetching change but none of them worked.

in ArticlesList.js

const ArticlesList = ({ articles }) => {
  
  return (
    <div className="whole">
      {console.log(articles.length)}
      <Carousel  fade> 
        <Carousel.Item>
          <img
            className="d-block w-100"
            src={articles[articles.length-1].img}
            alt="First slide"
            width="1100" height="400"
          />
          <Carousel.Caption>
            <h3>TESTESTESSTESTESTES</h3>
          </Carousel.Caption>
        </Carousel.Item>
        <Carousel.Item>
          <img
            className="d-block w-100"
            src={articles[articles.length-2].img}
            alt="Second slide"
            width="1100" height="400"
          />
          <Carousel.Caption>
            <h3>Second slide labelxd</h3>
          </Carousel.Caption>
        </Carousel.Item>
        <Carousel.Item>
          <img
            className="d-block w-100"
            src={articles[articles.length-3].img}
            alt="Third slide"
            width="1100" height="400"
          />
          <Carousel.Caption>
            <h3>Third slide label</h3>
          </Carousel.Caption>
        </Carousel.Item>
      </Carousel>
      <CardGroup className="articles"> 
          {articles.map((article) => (
            <Col xs={4} md={4} lg={4} key={article.id}>
              <Article article={article}/>
            </Col>
          ))}
      </CardGroup>
    </div>
  )
}

and in App.js

function App() {

  const [articles, setArticles] = useState([])

  useEffect(() => {
    const getArticles = async () =>{
      const articlesFromServer = await fetchArticles()
      setArticles(articlesFromServer)
    }

    getArticles()
  }, [])

  const fetchArticles = async () =>{
    const res = await fetch(
      'http://localhost:5000/articles')
    const data = await res.json()

    return data
  }
  return (
    <Router>
      <>
      <Header />
      <Route path='/' exact render={(props) => (
        <>
          <ArticleList articles={articles} />
        </>
      )} />
      <Route path='/details/:id' exact component={ArticleDetails} />
      </>
    </Router>
  );
}

CodePudding user response:

When you're refreshing the page, application tries to get data from backend server, but your component ArticleDetails already loaded. Since articles state is an empty array, it will return articles[articles.length-x] as undefined (which in turn results in uncaught error in articles[articles.length-x].img).

Therefore use optional chaining as follows in such cases.

articles[articles.length-x]?.img

Complete code will be as follows.

const ArticlesList = ({ articles }) => {
  
  return (
    <div className="whole">
      {console.log(articles.length)}
      <Carousel  fade> 
        <Carousel.Item>
          <img
            className="d-block w-100"
            src={articles[articles.length-1]?.img}
            alt="First slide"
            width="1100" height="400"
          />
          <Carousel.Caption>
            <h3>TESTESTESSTESTESTES</h3>
          </Carousel.Caption>
        </Carousel.Item>
        <Carousel.Item>
          <img
            className="d-block w-100"
            src={articles[articles.length-2]?.img}
            alt="Second slide"
            width="1100" height="400"
          />
          <Carousel.Caption>
            <h3>Second slide labelxd</h3>
          </Carousel.Caption>
        </Carousel.Item>
        <Carousel.Item>
          <img
            className="d-block w-100"
            src={articles[articles.length-3]?.img}
            alt="Third slide"
            width="1100" height="400"
          />
          <Carousel.Caption>
            <h3>Third slide label</h3>
          </Carousel.Caption>
        </Carousel.Item>
      </Carousel>
      <CardGroup className="articles"> 
          {articles.map((article) => (
            <Col xs={4} md={4} lg={4} key={article.id}>
              <Article article={article}/>
            </Col>
          ))}
      </CardGroup>
    </div>
  )
}

Hope this would solve your issue.

CodePudding user response:

The main misunderstanding you have lies inside the App component. You assume that React componentDidMount lifecycle will wait for async calls (in your case fetchArticles).

What happens is that the App component will mount regardless if your API has returned the articles you are after. Therefore every component that doesn't guards against a not defined value for articles is bound to fails with null exception.

Add null checks for articles in those components.

CodePudding user response:

I don't know React, but I would suggest this

This is based on my approach in Vue etc. I would check the length of articles before doing anything that depended on there being content in there.

In your render function, could you just insert an if, for example like this:

const ArticlesList = ({ articles }) => {
  let result = ""
  if (articles && articles.length >0){

    result  = ( // Put things for .length-1 
         )
  } 
  if (articles && articles.length >1){

    result  = ( // Put things for .length-2 
         )
  } 
        if (articles && articles.length >2){

    result  = ( // Put things for .length-3 
         )
  }
  return result 

Please don't shout at me if this is not correct React syntax: I am just showing you the principle I would use in Vue. 8-)

You could make it a loop to avoid repetition

You only need to state once how to display an article:
result = ""
const last3articles = articles.slice(Math.max(articles.length-3,0)).reverse()
last3articles.forEach(article=>{
   result  = ........// the part for a single article.
})
  • Related