2

I have component structure like this

src
 --App
   --DataTableComponent
   --ButtonComponnet
axios
 --useAxios

On app load I am calling Axios library in DataTableComponent (through custom hook useAxios) to fetch some data, then later when user clicks a button in ButtonComponent I would like Axios to load data again in DataTableComponent, but I am not sure how as the components are siblings.

koubin
  • 579
  • 5
  • 9
  • 30
  • bit curious if my answer below helped? – Sangeet Agarwal Dec 26 '21 at 14:40
  • @SangeetAgarwal sending setters through components did work. Thanks for your solution and a huge effort in writing answer. I waited with answer because I wanted to go through all possibilities like Redux. – koubin Dec 27 '21 at 19:59

3 Answers3

1

In your situation what you'll want to do is lift state up. Here a link to the official react documentation

In addition, what I've done is created a code sandbox sample for your particular situation that demonstrates how you could do some thing similar in your app. Here's the link to that code sandbox https://codesandbox.io/s/romantic-brook-sfvpd?file=/src/App.tsx

The principle behind lifting state up is that if you have 2 or more components that must share information then lift that information one level up to their parent. So, what I do, as you see in the code sandbox is that the app component now gets the information and pushes it down to your data table component.

So, you'll want your useAxios hook to live in your parent app component. The button components job is to simply trigger the fetch.

Once the fetch is triggered, new data is returned by the useAxios hook.

The fetch also causes the App's useEffect hook to run and this updates the state and pushes the data to the data table component.

So, in your case you'll probably want to wrap your useAxios hook in your own custom hook so you can pass parameters which in turn your useAxios hook can use to fetch data from your API.

Continue to click on the fetch data button and each time I return a random number of items so you'll see your data components data getting updated. Remember to open the console to see the useAxios hook getting called followed by the data table contents being updated.

I have used a similar approach in some of my production apps and in those apps I've created similar custom wrapper around useSWR hooks

Using redux or some thing similar is a good idea if you have data that must be shared across the application i.e. global data but data that is specific to a few components doesn't need that approach and one should then go with the "lift state up" way of doing things.

For completeness the code is also given below.

import { useEffect, useState } from "react";
import "./styles.css";

// useAxios hook
const useAxios = (_: boolean) => {
  console.log("useAxios");

  // mock data
  const arr = [
    {
      name: "Bianca Paul",
      phone: "1-453-676-9140",
      email: "mollis.integer@google.ca",
      address: "221-3571 Nam Street",
      id: 8
    },
    {
      name: "Hadley Gordon",
      phone: "1-235-486-3229",
      email: "adipiscing.mauris@icloud.com",
      address: "3255 Nec, Road",
      id: 3
    },
    {
      name: "Irma Bryan",
      phone: "1-818-417-5465",
      email: "ornare.in@icloud.net",
      address: "136-222 Facilisis Rd.",
      id: 2
    },
    {
      name: "Simon Nash",
      phone: "1-872-216-6482",
      email: "enim.nec@aol.couk",
      address: "Ap #873-5860 Erat St.",
      id: 101
    },
    {
      name: "Ursula Fleming",
      phone: "(998) 407-7291",
      email: "semper.erat@protonmail.com",
      address: "110-1550 Phasellus Ave",
      id: 43
    }
  ];

  // Randomize the data
  function getRandomItem() {
    // get random index value
    let randomIndex = Math.floor(Math.random() * arr.length);
    if (randomIndex === 0) randomIndex = 1;
    // get random items
    const item = arr.slice(0, randomIndex);

    return item;
  }

  // return a promise
  const data = new Promise<any>((resolve, reject) => {
    setTimeout(() => {
      return resolve(getRandomItem());
    }, 1000);
  });

  return { data };
};

// Button component
const ButtonComponent = (props: { clickCallback: () => void }) => {
  return (
    <>
      <button onClick={props.clickCallback}>fetch data</button>
    </>
  );
};

// DataComponent
const DataTableComponent = ({
  data
}: {
  data:
    | [
        {
          name: string;
          phone: string;
          email: string;
          address: string;
          id: string;
        }
      ]
    | null;
}) => {
  return (
    <>
      {data ? (
        data.map((v) => (
          <div key={v.id}>
            {v.name},{v.address}, {v.phone}
          </div>
        ))
      ) : (
        <span>loading</span>
      )}
    </>
  );
};

// App Parent component
export default function App(): JSX.Element {
  const [fetch, setFetch] = useState(true);
  const [returnedData, setReturnedData] = useState<
    | [
        {
          name: string;
          phone: string;
          email: string;
          address: string;
          id: string;
        }
      ]
    | null
  >(null);
  const { data } = useAxios(fetch);
  const buttonClicked = () => {
    setFetch(true);
  };

  useEffect(() => {
    let isMounted = true;
    if (fetch) {
      (async () => {
        if (isMounted) setReturnedData(await data);
        if (isMounted) setFetch(false);
        console.log(await data);
      })();
    }
    return function () {
      isMounted = false;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [fetch]);

  return (
    <div className="App">
      <h1>Lift state up</h1>
      <div>
        <DataTableComponent data={returnedData} />
      </div>
      <div>
        <ButtonComponent clickCallback={buttonClicked} />
      </div>
    </div>
  );
}
Sangeet Agarwal
  • 1,674
  • 16
  • 25
0

You can just send some prop var or function that will return true or something when button is clicked. Do you call ButtonComponnent in DataTableComponent or somewhere else ?

0

What you're trying to do is get two sibling components to communicate and call functions from each other, if I'm understanding correctly. If they're siblings, I'd recommend using a third-party library like Redux or EventHub

If you have Component A with the function "updateData", then you can call it in Component B using Redux by setting up a reducer called "updateData", and having DataTableComponent subscribe to the "data"

Redux file:

import { createStore, combineReducers } from "redux";

const reducer = (state = {}, action) => {
  ... {axios fetch here}
};

const store = createStore(reducer);
export default store;

DataTableComponent.jsx:

import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import axios from "axios";
import { updateData } from "../redux/actions";

 const DataTableComponent = () => {

  const data = useSelector(state => state.data);

  const dispatch = useDispatch();

  const fetchData = () => dispatch(updateData());

  return (
    <div>
      <button onClick={fetchData}>Fetch Data</button>

      <table>{data.map((item, index) => <tr key={index}><td>{item.id}</td><td>{item.name}</td></tr>)}</table>
    </div>
  );
};

ButtonComponent.jsx:

import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import axios from "axios";
import { updateData } from "../redux/actions";

 const ButtonComponent = () => {

  const data = useSelector(state => state.data);

  const dispatch = useDispatch();

  const fetchData = () => dispatch(updateData());

  return (
    <div>
      <button onClick={fetchData}>Fetch Data</button>

      <table>{data.map((item, index) => <tr key={index}><td>{item.id}</td><td>{item.name}</td></tr>)}</table>
    </div>
  );
};
Aman Jha
  • 138
  • 10