skip to Main Content

I tried searching on google and chatGPT but I had no luck , and this is making me lose my hair. I cannot figure out why the react states are behaving this way. I have an application that fetches news from different APIs, then merges them in one big array, and then displays them in the form of a list and you can apply filters on them as you like as well as preserve the preference for later use.

Following is the structure of my app:

App -> Home(Router-dom) -> News Component -> List Component + Filter Component

Home doesnt have anything much. Just some html and News Component. News component is where I am maintaining state for both List of news and Filter.

News Component:


import React, {useEffect, useState} from 'react'
import List from '../../components/List';
import SearchBar from '../../components/SearchBar';
import { FilterOptions, filter, getNews } from '../../utils/data';
import Filter from '../Filter';
import Cookies from 'js-cookie';

export interface NewsItem {
  id : string
  apiSource : "news_api" | "guardian_news" | "nyt_api";
  author : string;
  description : string;
  publishedAt : string;
  source : string;
  image: string;
  title : string;
  url : string;
}

export const sourcingFrom = {
  news_api: "News API",
  guardian_news: "The Guardian Open Platform",
  nyt_api: "New York Times Article Search"
} 

export type ApiSource = keyof typeof sourcingFrom;


function News() {

 
  const [newsItems, setNewsItems] = useState([])
  const [filters, setFilters] = useState<FilterOptions>({})



  
  const updateFilters = (newFilters : FilterOptions) => {
    const updateFiltersQuery :FilterOptions = {
      ...filters,
      ...newFilters,
    }   
    setFilters(prev => updateFiltersQuery)
  }
  
  const updateNews = (filterData: FilterOptions = {}) => {    
    const filterRequest = Object.keys(filterData).length <= 0 ? filterData :  filters
    console.log(filterRequest)
    filter(filterRequest).then(newNewsItems => {
      setTimeout(() => {
        setNewsItems(prev => newNewsItems);
      }, 500);
      
    });
  };
  const updateQuery = (e : string) => {  
    const updateFiltersQuery :FilterOptions = {
      ...filters,
      query : e,
    }   
    setFilters(prev => updateFiltersQuery)
  }

  
  const resetFilters = () => {
  }
  
  const resetPreferences = () => {
    Cookies.remove('filterPreferences');
  }

  const savePreferences = () => {
    Cookies.set('filterPreferences', JSON.stringify(filters), { expires: 30 });
  }
  

  useEffect(() => {

    const savedPreferences = Cookies.get('filterPreferences');
    if(savedPreferences){
      const filterData : FilterOptions = JSON.parse(savedPreferences)
      updateFilters(filterData);
      
        updateNews(filterData);
      
      
    }
    else {
      getNews().then(res => setNewsItems(res));
    }
    
  }, []);

  return (
    <section className='newsItems'>
      <SearchBar updateQuery={updateQuery} updateNews={updateNews} resetFilters={resetFilters}/>
      <Filter filter={updateFilters} savePreferences={savePreferences} resetPreferences={resetPreferences}/>
      <List newsItems={newsItems}/>
    </section>
  )
}

As you can see, I had to apply a setTimeout in updateNews just so it would update data when I recieve back the data, which is not good. Using Cookies is even worse because it wont send the request after filter has been set through it. Unless I make some change in a file and save it and then it starts working. Using setTimeout doesnt work with it either. I also removed setTimeout and checked if I still recieve new filtered newsItems in List which i did.

I tried looking for explanations, but couldnt figure it out so any help would be extremely helpful. I feel like I am breaking some React regulation and if you guys could help, that would be great.

Any help would be appreciated

Filter and getNews API calls:



export const getNews = async (newAPIParameters :DefaultAPIParas ={}, guardianNewsParameters :DefaultAPIParas  ={}, nYTAPIParameters :DefaultAPIParas  ={}, dontCallAPIs : DontCallAPIs = {}) => {
  const newsData: any = [];

  if(!dontCallAPIs?.news_api) {
  getNewsApiData(newAPIParameters).then((res: any) => {
    
      //console.log(res)
    for (let item of res) {
      const modifiedRes = {
        id: generateRandomString(),
        apiSource: "news_api",
        author: item.author,
        title: item.title,
        url: item.url,
        image: item.urlToImage,
        publishedAt: item.publishedAt,
        source: item.source?.name ?? "",
        description: item.content ?? item.description,
      };

      newsData.push(modifiedRes);
    }
  });
  }


  if(!dontCallAPIs?.guardian_api) {
    getGuardianNewsAPIData(guardianNewsParameters).then((res: any) => {
    
      //console.log(res)
        for (let item of res) {
          const modifiedRes = {
            id: generateRandomString(),
            apiSource: "guardian_news",
            author: item.tags[0]?.webTitle ?? 'Not Determined',
            title: item.webTitle,
            url: item.webUrl,
            image: item.fields.thumbnail,
            publishedAt: item.webPublicationDate,
            source: "The Guardian News",
            description: item.fields.trailText,
          };
    
          newsData.push(modifiedRes);
        }
      });
  }

  


  if(!dontCallAPIs?.nyt_api) {
    getNYTAPIData(nYTAPIParameters).then((res: any) => {
    
      //console.log(res)
        for (let item of res) {
          const modifiedRes = {
            id: generateRandomString(),
            apiSource: "nyt_api",
            author: item.byline.original?.replace('By ', '') ?? 'Not Determined',
            title: item.headline.main,
            url: item.web_url,
            image: "https://www.nytimes.com/"+item.multimedia[0]?.url ?? null,
            publishedAt: item.pub_date,
            source: "New York Times",
            description: item.abstract,
          };
    
          newsData.push(modifiedRes);
        }
      });
  }


  return await newsData;
};


export const filter = async (filterOptions : FilterOptions) => {

  const {query = null, category  = '', from  = null,to  = null,sources  = {}} = filterOptions
  const dontCallAPIs : DontCallAPIs = {}




  if (Object.keys(sources).length !== 0) {
  dontCallAPIs['news_api'] = sources.news_api && sources?.news_api?.length <= 0;
  dontCallAPIs['guardian_api'] = !sources.guardian_api;
  dontCallAPIs['nyt_api'] = !sources.nyt_api;
  }

  const newAPIParameters ={
    query : query ?? '',
    from : from ?? '',
    to : to ?? '',
    category : category ?? '',
    sources : sources.news_api?.join(', ')
    
  }
  const guardianNewsParameters = {
    query : query ?? '',
    from : from ?? '',
    to : to ?? '',
    category : category ?? '',
  }

  
  const nYTAPIParameters = {
    query : query ?? '',
    from : from ?? '',
    to : to ?? '',
    category : category ?? '',
  }
  


  return getNews(newAPIParameters, guardianNewsParameters, nYTAPIParameters, dontCallAPIs)
    
}


2

Answers


  1. Your filter method is returning a Promise of Promise. Remove async from it. getNews() is returning a Promise. Then your filter method will only have single Promise that can be resolved using then() whenever called.

    export const filter = (filterOptions : FilterOptions) // async removed
    

    Also no need of using await in return await newsData; as newsData is not a promise. You can just keep it as return newsData;

    Login or Signup to reply.
  2. Your getNews function has dangling promises in the middle of it which are neither awaited nor returned, so getNews’s promise resolves before the work is actually done:

    if(!dontCallAPIs?.news_api) {
      getNewsApiData(newAPIParameters).then((res: any) => {
       // ...
      });
    }
    

    In the above code, getNewsApiData creates a promise, and then .then creates a promise, but then you don’t do anything with the resulting promise. So as soon as the promises have been set up, this line of code moves on. It does not wait for the promises to resolve. getGuardianNewsAPIData and getNYTAPIData do the same.

    I also recommend that you don’t mix async/await with .then. Mixing them makes it much harder to figure out the sequence of events. So i recommend:

    export const getNews = async (/* ... */) => {
      const newsData: any = []
    
      if(!dontCallAPIs?.news_api) {
        const res: any = await getNewsApiData(newAPIParameters);
        for (let item of res) {
          const modifiedRes = {
            id: generateRandomString(),
            apiSource: "news_api",
            author: item.author,
            title: item.title,
            url: item.url,
            image: item.urlToImage,
            publishedAt: item.publishedAt,
            source: item.source?.name ?? "",
            description: item.content ?? item.description,
          };
      
          newsData.push(modifiedRes);
        }
      }
    
      if (!dontCallAPIs?.guardian_api) {
        const res: any = await getGuardianNewsAPIData(guardianNewsParameters);
        for (let item of res) {
          // ...
        } 
      }
      
      if (!dontCallAPIs?.nyt_api) {
        const res: any = await getNYTAPIData(nYTAPIParameters);
        for (let item of res) {
          // ...
        }
      }
      
      return newData
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search