Search code examples
reactjsasynchronousfetchdestructuring

Destructurize fetched data react


Context

All of my components need to fetch data.

How I fetch

Therefore I use a custom hook which fetches the data using the useEffect hook and axios. The hook returns data or if loading or on error false. The data is an object with mostly an array of objects.

How I render

I render my data conditional with an ternary (?) or the use of the short circuit (&&) operator.

Question

How can I destructure my data dependent if my useFetch hook is returning false or the data in a way i can reuse the logic or an minimal implementation to the receiving component?

What I have tried

  • moving the destructuring assignment into an if statement like in return. Issue: "undefined" errors => data was not available yet
  • moving attempt 1 to function. Issue: function does not return variables (return statement does not work either)
//issue
fuction Receiver() {
    const query = headerQuery();
    const data = useFetch(query);
    const loaded = data.data //either ```false``` or object with ```data```
/*
The following part should be in an easy condition or passed to an combined logic but I just dont get it
destructuring assignment varies from component to component

ex:
    const {
        site,
        data: {
            subMenu: {
                description,
                article_galleries,
                image: {
                    caption,
                    image: [{url}],
                },
            },
        },
    } = data;
*/
return loaded?(
    <RichLink
        title={title}
        text={teaserForText}
        link={link}
        key={id}
    ></RichLink>):<Loading />

(

//for context
import axios from "axios";
import {
  useHistory
} from "react-router-dom";
import {
  useEffect,
  useState
} from "react";

function useFetch(query) {
  const [data, setData] = useState(false);
  const [site, setSite] = useState(""); // = title
  const history = useHistory();

  useEffect(() => {
    axios({
        url: "http://localhost:1337/graphql",
        method: "post",
        data: {
          query: query,
        },
      })
      .then((res) => {
        const result = res.data.data;
        setData(result);

        if (result === null) {
          history.push("/Error404");
        }
        setSite(Object.keys(result)[0]);
      })
      .catch((error) => {
        console.log(error, "error");
        history.push("/Error");
      });
  }, [query, history, setData, setSite]);


  return {
    data: data,
    site: site
  };
}

export default useFetch;

)


Solution

  • You can return the error, data and your loading states from your hook. Then the component implementing the hooks can destructure all of these and do things depending upon the result. Example:

    const useAsync = () => {
     // I prefer status to be idle, pending, resolved and rejected.
     // where pending status is loading.
      const [status, setStatus] = useState('idle')
      const [data, setData] = useState([])
      const [error, setError] = useState(null)
      
      useEffect(() => {
       setStatus('pending')
    
       axios.get('/').then(resp => {
         setStatus('resolved')
         setData(resp.data)
       }).catch(err => {
         setStatus('rejected') // you can handle error boundary
         setError(err)
       })
      }, []}
    
     return {status, data, error}
    }
    
    

    Component implementing this hook

    const App = () => {
      const {data, status, error} = useAsync()
      
      if(status === 'idle'){
        // do something
      }else if(status === 'pending'){
        return <Loader />
      }else if(status === 'resolved'){
        return <YourComponent data ={data} />
      }else{
        return <div role='alert'>something went wrong {error.message}</div>
      } 
    
    
    }
    

    the hooks can be enhanced more with the use of dynamic api functions.