0

N.B. I got my answer here but it is not the duplicate question of this thread

I am trying to fetch data from a reusable function that has an API. Here is my code

usaData.js in another page

const useData = () => {
  const [data, setData] = useState([]);
  const fetchData = async (url, query, variable) => {
    const queryResult = await axios.post(url, {
      query: query,
      variables: variable,
    });
    setData(queryResult.data.data);
  };
  
  return {data, fetchData}
};

I am retrieving data from this MainPage.js file

export const MainPage = props => {

    const [state, setState] = useState({
        pharam: 'Yes',
        value: '',
    });

    const [field, setField] = useState([])

    const {data, fetchData} = useData()

    const onClick = (event) => {
        setState({ ...state, pharam: '', value: event });

        fetchData(url, query, event)
        setField(data)
    }

    return (
        <div>
          ...
          <Select
              placeholder='select'
          >
              {field.map(item => (
                  <Select.Option key={item.name}>
                      {item.name}
                  </Select.Option>
              ))}
          </Select>
          <Button onClick={onClick}>Change Select</Button>
          ...
        </div>
    )
}

The problem is setField(data) within onClick function is not updating immediately as it is a async call. Hence I tried to use a function as a second argument

    ...
    setField(data, () => {
        console.log(data)
    })
    ...

It is returning the following warning in red color but the behavior is similar to earlier, not updating data immediately.

Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().

As per the warning then I tried to use useEffect() within the onClick function

...
    const onClick = (event) => {
        setState({ ...state, pharam: '', value: event });

        useEffect(() => {
          fetchData(url, query, event)
          setField(data)
        }, [data])
    }
...

which is returning an error

React Hook "useEffect" is called in function "onClick" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"

Where do I have to make changes? How can I get expected behavior as the setField will update the field immediately?

Devil's Dream
  • 655
  • 3
  • 15
  • 38

3 Answers3

1

The problem in your case is that setField gets calls before your data is fetched.

So, you can have a useEffect which gets executed every time the data gets changed.

useEffect(() => {
  if(data.length > 0) {
    setField(data);
  }
}, [data])


const onClick = (event) => {
        setState(prev => ({ ...prev, pharam: '', value: event }));
        fetchData(url, query, event);
}
Shri Hari L
  • 4,551
  • 2
  • 6
  • 18
  • Actually, I wanted to use `fetchData()` in multiple click such as `anotherClick()` event and update `anotherField` by `setAnotherField()`. Then every click will affect the `useeffect` and it will update only the `setField` data. – Devil's Dream Sep 20 '22 at 05:50
1

As far I know, React sets its state asynchronously. So, in order to update the state Field, you need an useEffect hook. Your approch with useEffect is correct, except it neeed to be placed outside onClick (directly in the component function).

export const MainPage = () => {
...
    useEffect(() => {
        setField(data)
    },[data])
...
}
BlueBeret
  • 474
  • 3
  • 23
  • but I need to call it after triggering `onClick` function – Devil's Dream Sep 20 '22 at 05:31
  • @Devil'sDream it should update everytime the state `data` changes including when you click the button. – BlueBeret Sep 20 '22 at 05:41
  • Actually, I wanted to use `fetchData()` in multiple click such as `anotherClick()` event and update `anotherField` by `setAnotherField()`. Then every click will affect the `useeffect` and it will update only the `setField` data. – Devil's Dream Sep 20 '22 at 05:51
  • @Devil'sDream how about using multiple `useData` hook? or maybe create a `fetchData` function which return the data instead of using state for storing the data. – BlueBeret Sep 20 '22 at 05:59
1

My suggestion would be to not setState in your custom hook rather than return promise.

usaData.js

const useData = () => {
  const fetchData = async (url, query, variable) => {
    return await axios.post(url, {
      query: query,
      variables: variable,
    });
  };

  return { fetchData };
};

In MainPage.js

Now when you trigger your onClick function just call your fetchData function with await or then syntax and after successfully api call you'll get back the result in the newData variable which you can use it to update your state.

Note: this will save you an extra useEffect.

 export const MainPage = (props) => {
  const [state, setState] = useState({
    pharam: "Yes",
    value: "",
  });

  const [field, setField] = useState([]);

  const { fetchData } = useData();

  const onClick = async (event) => {
    setState({ ...state, pharam: "", value: event });

    let newData = await fetchData(url, query, event);
    console.log("===>", newData.data.data);
    setField(newData.data.data);
  };

  return (
    <div>
      ...
      <Select placeholder="select">
        {field.map((item) => (
          <Select.Option key={item.name}>{item.name}</Select.Option>
        ))}
      </Select>
      <Button onClick={onClick}>Change Select</Button>
      ...
    </div>
  );
};
Alpha
  • 1,413
  • 3
  • 9
  • 23