skip to Main Content

I created a Higher Order Component (HOC) or Container component from where I am fetching data and sending to child component. I am also using React-Router-DOM library for routing.

I am using HOC component like this:

<BrowserRouter>
  <Routes>
    <Route path={'/'} element={<HOC Component={App} />} />
  </Routes>
</BrowserRouter>

I am sending data to child component like this:

export const HOC = ({ Component }) => {
  const fetchData = () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const data = {
          message:
            'Data fetched successfully' + Math.floor(Math.random() * 100),
        };
        return resolve(data);
      }, 2000); // Simulating a delay of 2 seconds
    });
  };

  return (
    <div>
      <If condition={fetchData()}>
        <Fallback>Loading data ...</Fallback>
        <Then>
          {(data) => (
            <>
              <span>parent component your data:{JSON.stringify(data)}</span>
              <Component data={data} />
            </>
          )}
        </Then>
        <Else>{(error) => <span>Failed to load data because </span>}</Else>
      </If>
    </div>
  );
};

Now issue is that there is an CTA in my child component, on click of that I want to fetch again data. Is there any way using React-Router-DOM? I want to re-render HOC so that it fetch again and send new data to child component.

Here is my code:

https://stackblitz.com/edit/vitejs-vite-gtdrmg?file=src%2FApp.tsx,src%2Findex.css,src%2Fmain.tsx,src%2Fhoc.tsx,src%2FApp.css&terminal=dev

One way I am think to pass fetchData to child component and do the manipulation but I don’t think parent component have correct data.

2

Answers


  1. // hoc.tsx file

    import React, { useState, useEffect } from 'react';
    import { If, Fallback, Then, Else } from 'react-if';
    
    interface Props {
      Component: React.ComponentType<any>;
    }
    
    export const HOC: React.FC<Props> = ({ Component }) => {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(true);
    
      const fetchData = () => {
        setLoading(true);
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            const newData = {
              message:
                'Data fetched successfully ===>' + Math.floor(Math.random() * 100),
            };
            setData(newData); // Update the data state with the new data
            setLoading(false);
            return resolve(newData);
          }, 2000);
        });
      };
    
      useEffect(() => {
        fetchData();
      }, []); // Fetch data when component mounts
    
      return (
        <div>
          <If condition={!loading}>
            <Then>
              <Component data={data} fetchAgain={fetchData} />
            </Then>
            <Else>
              <Fallback>Loading data ...</Fallback>
            </Else>
          </If>
        </div>
      );
    };
    

    // App.tsx file

    import React from 'react';
    import { useState } from 'react';
    import './App.css';
    
    function App({ data, fetchAgain }) {
      const handleClick = () => {
        console.log(fetchAgain)
        fetchAgain();
      };
      
      return (
        <>
          <h1>Child component {JSON.stringify(data)}</h1>
          <button onClick={handleClick}>Again fetch</button>
        </>
      );
    }
    
    export default App;
    
    
    Try this if this help you, give me up..
    
    Login or Signup to reply.
  2. There is an CTA in my child component, on click of that I want to
    fetch again data. Is there any way using React-Router-DOM? I want to
    re-render HOC so that it fetch again and send new data to child
    component.

    You might be able to accomplish this via the RRD router, but you’d need to use one of the Data routers and a route loader/action and you’d likely need to refactor much of your logic to work with the loader/action. This is probably a much heavier lift than it needs to be or for what you are looking to do. My suggestion would be to just pass fetchData down as a prop along with the fetched data, to the component you are decorating.

    Update your "HOC" to be a true Higher Order Component, and decorate the App component export.

    hoc.tsx

    • Create an interface for the props you wish in inject into the decorated components.
    • Create state to hold the fetched data and a loading state to indicate when actively fetching.
    • Fetch data when the component mounts, passing down the current data and the fetchData callback as props.
    • If you like, you can catch/handle fetching errors separately as state and conditionally render what you need. This would likely be a nested IfThenElse in the non-loading branch.
    import { useEffect, useState } from 'react';
    import { If, Then, Else } from 'react-if';
    
    interface Data {
      message: string;
    }
    
    export interface WithFetchDataProps {
      data: Data | null;
      fetchData: () => void;
    }
    
    export const withFetchData =
      <P extends object>(Component: React.ComponentType<P>) =>
      (props: Omit<P, keyof WithFetchDataProps>) => {
        const [data, setData] = useState<Data | null>(null);
        const [isLoading, setIsLoading] = useState(false);
    
        const fetchData = () => {
          setIsLoading(true);
          new Promise<Data>((resolve, reject) => {
            setTimeout(() => {
              const data = {
                message:
                  'Data fetched successfully' + Math.floor(Math.random() * 100),
              };
              return resolve(data);
            }, 2000); // Simulating a delay of 2 seconds
          })
            .then(setData)
            .finally(() => {
              setIsLoading(false);
            });
        };
    
        useEffect(() => {
          fetchData();
        }, []);
    
        return (
          <div>
            <If condition={isLoading}>
              <Then>Loading data ...</Then>
              <Else>
                <>
                  <span>
                    parent component your data:{JSON.stringify(data)}
                  </span>
                  <Component
                    data={data}
                    fetchData={fetchData}
                    {...(props as P)}
                  />
                </>
              </Else>
            </If>
          </div>
        );
      };
    

    App.tsx

    • Decorate and export the App component.
    import './App.css';
    import { withFetchData, WithFetchDataProps } from './hoc.tsx';
    
    interface AppProps extends WithFetchDataProps {};
    
    function App({ data, fetchData }: AppProps) {
      const handleClick = () => {
        console.log('00000');
        fetchData()
      };
      
      return (
        <>
          <h1>Child component: {data?.message}</h1>
    
          <button onClick={handleClick}>Again fetch</button>
        </>
      );
    }
    
    export default withFetchData(App);
    

    main.tsx

    Render the App component normally on a route.

    ReactDOM.createRoot(document.getElementById('root')!).render(
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<App />} />
        </Routes>
      </BrowserRouter>
    );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search