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.