the result of the code is
- first call
- outside useEffect
- before return
- inside useEffect
- first call
- outside useEffect
- before return
- inside useEffect
- first call
- outside useEffect
- before return
but why?
- how inside useEffect be called twice
- how after outside useEffect there is no inside useEffect
- why it did not make infinite loop because data is being re-created every time so it should effect useApi
function useApi(data, initial){
const [name, setName] = useState(initial)
console.log("outside useEffect")
useEffect(() => {
console.log("inside useEffect")
fetch(data.name,{
method: data.method,
body: data.body
}).then((response) => response.json()).then((response) => {
setName(response.id)
})
}, [data])
return [name, setName]
}
function App() {
console.log("first call")
const data = {
name: "http://localhost:8000/api/kebap",
method: "GET"
}
const [info, setInfo] = useApi(data,
"10")
console.log("before return")
return <div> {info} </div>
}
CodePudding user response:
const data = {
name: 'http://localhost:8000/api/kebap',
method: 'GET',
};
This object, const data
is being created every render. It is also used as the a dependency for your useEffect
hook. This means that the useEffect
hook will be called every time you render, causing an infinite loop.
The solution, is to memoize the object, or in this case, simply move it outside of the component.
function useApi(data, initial) {
const [name, setName] = useState(initial);
console.log('outside useEffect');
useEffect(() => {
console.log('inside useEffect');
fetch(data.name, {
method: data.method,
body: data.body,
})
.then((response) => response.json())
.then((response) => {
setName(response.id);
});
}, [data]);
return [name, setName];
}
const data = {
name: 'http://localhost:8000/api/kebap',
method: 'GET',
};
function App() {
console.log('first call');
const [info, setInfo] = useApi(data, '10');
console.log('before return');
return <div> {info} </div>;
}
Another way, would be to use useMemo
. This would be useful, if your data
object would be dynamically created.
function App() {
console.log('first call');
const data = useMemo(() => ({
name: 'http://localhost:8000/api/kebap',
method: 'GET',
}),[]);
const [info, setInfo] = useApi(data, '10');
console.log('before return');
return <div> {info} </div>;
}
CodePudding user response:
Using a better custom hook, usePromise
, your App
would look like this. Remember to always handle the fetching
and error
states of your app.
function App() {
const [{ fetching, error, data = "10"}, retry] = usePromise(_ =>
fetch("http://localhost:8000/api/kebap")
.then(res => res.json())
)
return <div>
{ fetching
? <p>Fetching...</p>
: error
? <p>Error: {error.message}</p>
: <pre>{JSON.stringify(data, null, 2)}</pre>
}
</div>
}
Here is a minimal verifiable example of usePromise
.
- Input of
() => Promise
- Returns
[state, retry]
state
contains{fetching, error, data}
so you can know the status of the promiseretry
is a function that allows you to rerun the promise
function usePromise(thunk) {
const [state, setState] = React.useState({
fetching: true,
error: null,
data: null
})
const [timestamp, setTimestamp] = React.useState(Date.now())
React.useEffect(_ => {
setState({ ...state, fetching: true })
thunk()
.then(data => setState({ fetching: false, error: null, data }))
.catch(error => setState({ fetching: false, error, data: null }))
}, [timestamp])
return [state, _ => setTimestamp(Date.now())]
}
Here is a demo. Click the retry button several times to see the effect run each time.
function App() {
const [{fetching, error, data}, retry] = usePromise(_ =>
fetch("https://random-data-api.com/api/app/random_app").then(res => res.json())
)
return <div>
<button type="button" onClick={retry} children="retry" disabled={fetching} />
<pre>
{ fetching
? "Fetching..."
: error
? `Error: ${error.message}`
: JSON.stringify(data, null, 2)
}
</pre>
</div>
}
function usePromise(thunk) {
const [state, setState] = React.useState({
fetching: true,
error: null,
data: null
})
const [timestamp, setTimestamp] = React.useState(Date.now())
React.useEffect(_ => {
setState({ ...state, fetching: true })
thunk()
.then(data => setState({ fetching: false, error: null, data }))
.catch(error => setState({ fetching: false, error, data: null }))
}, [timestamp])
return [state, _ => setTimestamp(Date.now())]
}
ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Most likely retry
will not be implemented on a button like retry. Instead you can check for error
and call retry()
automatically in your codes.