-1

I have some hard-coded data in my React app which I would now like to fetch from an API. I would like to preserve the array-like format because that is how the data is used down the road.

My original component:

import moment from 'moment';

const INITIAL_STATE = {
  articles:
    [
      {
        title: 'Guerrilla Public Service Redux (2017)',
        tag0: 'story',
        points: 421,
        created_at: moment('2020-05-27T16:05:32.000Z'),
        author: 'DerWOK',
      },
      {
        title: 'Build Yourself a Redux',
        tag0: 'story',
        points: 395,
        created_at: moment('2020-05-27T16:05:32.000Z'),
        author: 'jdeal',
      },
    ],
};

function Articles (state = INITIAL_STATE) {
      return state;
}

export default Articles;

This is how I imagine it to work but what should be in the return() below is a mystery to me:

import moment from 'moment';

function Articles() {
  const [error, setError] = useState(null);
  const [isLoaded, setIsLoaded] = useState(false);
  const [items, setItems] = useState([]);

  useEffect(() => {
    fetch("https://hn.algolia.com/api/v1/search?query=redux")
      .then(res => res.json())
      .then(
        (result) => {
          setIsLoaded(true);
          setItems(result.items);
        },
        (error) => {
          setIsLoaded(true);
          setError(error);
        }
      )
  }, [])

  if (error) {
    return <div>Error: {error.message}</div>;
  } else if (!isLoaded) {
    return <div>Loading...</div>;
  } else {
    return (

        {items.map(item => (
         ???)
    );
  }
}

export default Articles;

Edit: I do have the list component to render the data. I just need to return the data in acceptable form.

10
  • Does this answer your question? React.js create loop through Array Commented Jun 9, 2020 at 20:33
  • 1
    You aren't trying to make your hook return an array, Fetch (in your code) is a FunctionalComponent, not a hook. If your problem is the ??? then you are just trying to render an an array as JSX elements. Please see the suggested duplicate. Commented Jun 9, 2020 at 20:34
  • Your result.item maintains the array-like structure. If you are talking about passing the array structure down to children, you could simply do this return (<SomeComponent itemList={items} />) without using items.map Commented Jun 9, 2020 at 20:42
  • But what you are doing in not a hook, like @zero298 mentioned. Your return statement with ??? is the return for the render, not any other values. Commented Jun 9, 2020 at 20:52
  • 1
    @habr so your approach is wrong, as you are returning JSX on other if blocks. The main question is why would you want a hook to do this, cuz i am not sure. If you can answer that, we can help you create a proper hook (useGetArticles) Commented Jun 9, 2020 at 21:07

2 Answers 2

0

This looks good to me except for not returning JSX from the component In the last use case and in the other cases you are. Try this

 return (
         <ul>
        {items.map((item, index) => <li key={index}>{item.title} </li> )}
         </ul>
    );

Note if your items have unique ids it is better practice to use those as the key instead of the index.

EDIT : Here is an example of how you could make a hook to use in articles

function useGetArticles() {
  const [error, setError] = useState(null);
  const [isLoaded, setIsLoaded] = useState(false);
  const [items, setItems] = useState([]);

  useEffect(() => {
    fetch('https://hn.algolia.com/api/v1/search?query=redux')
      .then((res) => res.json())
      .then(
        (result) => {
          setIsLoaded(true);
          setItems(result.items);
        },
        (error) => {
          setIsLoaded(true);
          setError(error);
        }
      );
  }, []);

  return [items, isLoaded, error];
}

function Articles() {
  const [items, isLoaded, error] = useGetArticles();

  if (error) {
    return <div>Error: {error.message}</div>;
  } else if (!isLoaded) {
    return <div>Loading...</div>;
  } else {
    return (
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item.title} </li>
        ))}
      </ul>
    );
  }
}
Sign up to request clarification or add additional context in comments.

4 Comments

@habr your initial code doesnt have imports for the react hooks. May want to add that too.
I do not want to return list, only the data for the list.
@habr I think you need to rethink what you are returning in different cases. It is kind of confusing to return JSX in some cases and an array of objects in other cases. You could make it work but I don't think it is vert manageable. Sorry if I missed the point of the quest
ConnorRobertson and @dabishan , thanks a lot for your time and help. Based on your advice I decided to re-think the whole thing and decided to go with redux. You can see the code here: stackoverflow.com/questions/62503354/…
0

If all you are trying to do is maintain Articles as a function, you cannot without making it a promise(which is redundant as fetch is doing what you need to already).

And you can write custom hook as you are trying to, but then Articles (must be named useArticles as react needs it to) cant be a function the way you want and you will be complicating for no useful reason.

My suggestion would be to simply move the useEffect, and other state hooks to your parent component like this (Note App is your parent component not Articles):

import React, {useState, useEffect} from 'react';

const App = () => {
  const [error, setError] = useState(null);
  const [isLoaded, setIsLoaded] = useState(false);
  const [items, setItems] = useState([]);

  useEffect(() => {
    Articles()
        .then(res => {
        setIsLoaded(true);
        setItems(res.hits);
        setError(false);
      })
      .catch(error => {
        setIsLoaded(true);
        setError(true);
      })
    // you are repeating then/catch again even though you were doing it already on fetch. Only good reason to do this is if you are reusing the api calls in other places.
  }, [])

   return (
    <React.Fragment>
      Test
      <ul>
       {items.map((item, index) => <li key={index}>{item.title} </li> )}
     </ul>
    </React.Fragment>

    );

}

function Articles() {
  // too much code just to hide it inside a function
    return new Promise((resolve, reject) =>
    fetch("https://hn.algolia.com/api/v1/search?query=redux")
    .then(res => resolve(res.json()))
    .catch(error => reject(error))
  );
}

export default App;

Here is the hooks way to reformat your code. But that itself doesn't mean its better:

import React, {useState, useEffect} from 'react';

const App = () => {
  // getting all useful state from the hook
  const {error, isLoaded, items, fetchItems} = useGetArticles();

  useEffect(() => {
    fetchItems(); // calling the method that ask for new data and loads item in the hook's state
  })

   return (
    <React.Fragment>
      <ul>
       {items.map((item, index) => <li key={index}>{item.title} </li> )}
     </ul>
    </React.Fragment>

    );

}
const useGetArticles = () => {
  const [error, setError] = useState(null);
  const [isLoaded, setIsLoaded] = useState(false);
  const [items, setItems] = useState([]);

  const fetchItems = () => {
    fetch("https://hn.algolia.com/api/v1/search?query=redux")
    .then(res => res.json())
    .then(res => {
      setIsLoaded(true);
      setItems(res.hits);
      setError(false);
    })
    .catch(error => {
      setIsLoaded(true);
      setError(true);
    })
  };

  // only return useful states and functions that main component can use
  return {error, isLoaded, items, fetchItems}
}

export default App;

1 Comment

Thanks a lot @dabishan. I think either I manage to return the array here or would need to go with Redux. The first option sounds simpler. If it does not bother you I would appreciate if you could add the hooks version.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.