Home > database >  How to use the custom hook on click event which contains useEffect and useReducer?
How to use the custom hook on click event which contains useEffect and useReducer?

Time:03-27

I am using custom hook which contains useEffect and useReducer for API calls and I call this custom hook on button click and I got this error (React Hook "useAuth" is called in function "handleClick" that is neither a React function component nor a custom React Hook function). Code is below

useAuth.js

import axios from 'axios'
import {useEffect,useReducer} from 'react'

const ACTION = {
 LOADING:'loading',
 SUCCESS:'success',
 ERROR:'error'
}

function reducer(state,action) {
 switch (action) {
    case ACTION.LOADING:
        return {loading:true,data:[]}
    case ACTION.SUCCESS:
        return {...state,loading:false,data:action.payload.data}
    case ACTION.ERROR:
        return {...state,loading:false,data:[],error:action.payload.error}
    default:
        break;
  }
}   
function useAuth (data) {
 const [state, dispatch] = useReducer(reducer, {data:[],loading:true})

 useEffect(() => {
    dispatch({type:ACTION.LOADING})
    const getData = async ()=>{
      try {
          const response = await axios.post('https://expamle.com',data)
          dispatch({type:ACTION.SUCCESS,payload:{data:response.data.data}})
      } catch (error) {
          dispatch({type:ACTION.ERROR,payload:{data:error.response}})
      }
    }
  getData()
 }, [])
 return state
}
export default useAuth

app.js

import logo from './logo.svg';
import './App.css';
import useAuth from './useAuth'

function App() {
 // const {loading,data,error} = useAuth()

 const handleClick = () => {
   const {loading,data,error} = useAuth() // how to use custom hook on click event
 }

 return (
  <div className="App">
    <button onClick={handleClick}></button>
  </div>
 );
}

export default App;

CodePudding user response:

Ideally, it should be written like this

import logo from './logo.svg';
import './App.css';
import useAuth from './useAuth'

function App() {
 const { loading , data ,error, dispatch} = useAuth()

 const handleClick = () => {
   dispatch({type:'USER_CLICKED'})
   console.log('check for your data', data)
 }

 return (
  <div className="App">
    <button onClick={handleClick}></button>
  </div>
 );
}

In your useAuth hook you should have a flag that becomes true upon button click

const ACTION = {
 LOADING:'loading',
 SUCCESS:'success',
 ERROR:'error'
}

function reducer(state,action) {
 switch (action) {
    case ACTION.USER_CLICK:
        return {...state, userClicked: true}
    case ACTION.LOADING:
        return {loading:true,data:[]}
    case ACTION.SUCCESS:
        return {...state,loading:false,data:action.payload.data}
    case ACTION.ERROR:
        return {...state,loading:false,data:[],error:action.payload.error}
    default:
        break;
  }
}   

function useAuth(data) {
  const [state, dispatch] = useReducer(reducer, { data: [], loading: true, userClicked: false });
 
  useEffect(() => {
    if (state.userClicked) {
      dispatch({ type: ACTION.LOADING });
      const getData = async () => {
        try {
          const response = await axios.post("https://expamle.com", data);
          dispatch({
            type: ACTION.SUCCESS,
            payload: { data: response.data.data },
          });
        } catch (error) {
          dispatch({ type: ACTION.ERROR, payload: { data: error.response } });
        }
      };
      getData();
    }
  }, [userClicked]);
  return { state, dispatch }; 
}
export default useAuth;

CodePudding user response:

useReducer seems too much for this. I would suggest a simple useAsync,

function useAsync(f, deps) {
  const [state, setState] = React.useState({fetching: false})
  const [ts, setTs] = React.useState(null)
  React.useEffect(_ => {
    ts && f()
      .then(data => setState({fetching: false, data}))
      .catch(error => setState({fetching: false, error}))
  }, [...deps, ts])
  return [
    state,
    _ => {
      setState({fetching: true, error: null, data: null})
      setTs(Date.now())
    }
  ]
}

You can use useAsync for any asynchronous behavior. Now you can write useAuth,

function useAuth(payload) {
  return useAsync(_ => { // ✅ useAsync
    return axios.post("https://example.com", payload) // ✅ auth request
  }, [payload.username, payload.password])
}

To use it in your App,

function App() {
  const [username, setUsername] = useState("tommy")
  const [password, setPassword] = useState("pickle$")
  const [{fetching, error, data}, execute] = useAuth({ username, password }) // ✅ useAuth
  if (fetching) return <pre>Loading...</pre>
  if (error) return <pre>Error: {error.message}</pre>
  return <div>
    <input value={username} onChange={e => setUsername(e.target.value)} />
    <input value={password} onChange={e => setPassword(e.target.value)} />
    <button onClick={execute} children="login" /> // ✅ execute
    <pre>{JSON.stringify(data, null, 2)}</pre>
  </div>
}

See a full demo of useAsync in this Q&A.

  • Related