Infinite loop after adding a dependency - reactjs

When I'm adding dependency fetchData, my app becomes an infinite loop.
What am I doing wrong?
React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array
const [films, setFilms] = useState([]);
const [page, setPage] = useState(1);
const [isLoad, setIsLoad] = useState(false);
const incrementPage = () => {
setPage(page + 1);
};
const fetchData = async () => {
setIsLoad(true);
const response = await fetch(
`${baseURL}movie/popular?api_key=${API_KEY}&language=en-US&page=${page}`
).then((res) => res.json());
setFilms([...films, ...response.results]);
setIsLoad(false);
incrementPage();
};
useEffect(() => {
fetchData();
}, []);

I would place the contents of fetchData into the useEffect instead.
const [films, setFilms] = useState([]);
const [page, setPage] = useState(1);
const [isLoad, setIsLoad] = useState(false);
const incrementPage = () => {
setPage(page + 1);
};
useEffect(() => {
setIsLoad(true);
const response = await fetch(
`${baseURL}movie/popular?api_key=${API_KEY}&language=en-US&page=${page}`
).then((res) => res.json());
setFilms([...films, ...response.results]);
setIsLoad(false);
incrementPage();
}, [setIsLoad, page, setFilms, setIsLoad, incrementPage]);
Then it will automatically fetch new data if 'page' is changed.

Related

useState isn't updating

I'm fetching an api and want to change useState when the api is returned. However, it simply isn't updating.
Any suggestions?
const fictionApi = "http://localhost:3000/fiction"
const nonFictionApi = "http://localhost:3000/non_fiction"
const [fictionData, setFictionData] = useState(null)
const db = async (url) => {
const res = await fetch(url)
const data = await res.json()
setFictionData(data)
console.log(data.genre)
}
useEffect(() => {
const db = async (url) => {
const res = await fetch(url)
const data = await res.json()
setFictionData(data)
console.log(fictionData)
}
db(fictionApi)
}, [])
I think there is something strange with your syntax.
Something like this should work :
const fictionApi = "http://localhost:3000/fiction"
const nonFictionApi = "http://localhost:3000/non_fiction"
export default function Page () {
const [fictionData, setFictionData] = useState(null);
const [url, setUrl] = useState(fictionApi); // ';' very important
useEffect(() => {
(async () => {
const res = await fetch(url)
const data = await res.json()
setFictionData(data)
})() //Self calling async function
}, [])
}
Moreover, setState is an async process so :
const [fictionData, setFictionData] = useState(null);
setFictionData(true)
console.log(fictionData) //null
So you can use a useEffect to check state :
const [fictionData, setFictionData] = useState(null);
useEffect(()=>{
console.log(fictionData) //true
},[fictionData])
setFictionData(true)

Multiple requests with useFetch

Im sending 2 requests at the same time using useFetch, on Safari the responses are getting mixed
const [entries,setEntries] = useState([]);
const [categories,setCategories] = useState([]);
const { get, response} = useFetch('https://api.publicapis.org');
const getData = useCallback( async()=>{
const entriesRes = await get('/entries?category=animals&https=true')
if(response.ok)
setEntries(entriesRes.entries)
},[])
const getCategories = useCallback( async()=>{
const categoriesRes = await get('/categories')
if(response.ok)
setCategories(categoriesRes)
},[])
useEffect(()=>{
getData();
getCategories();
},[])
if the getCategories returns first the response go to entriesRes instand of categoriesRes this happens only on safari
Try renaming the useFetch results:
const [entries,setEntries] = useState([]);
const [categories,setCategories] = useState([]);
const categoryFetch = useFetch('https://api.publicapis.org');
const dataFetch = useFetch('https://api.publicapis.org');
const getData = useCallback( async()=>{
const entriesRes = await dataFetch.get('/entries?category=animals&https=true')
if(response.ok)
setEntries(entriesRes.entries)
},[])
const getCategories = useCallback( async()=>{
const categoriesRes = await categoryFetch.get('/categories')
if(response.ok)
setCategories(categoriesRes)
},[])
useEffect(()=>{
getData();
getCategories();
},[])

How to avoid triggering useEffect() from another useEffect()?

I'm implementing pagination with React. It generally works well, except one issue.
When I'm changing sorting criteria, I want to reset page to 1st. The problem is, changing the page number triggers data fetch again. So whenever I'm on page 2 or above and change sorting criteria, the data is being fetched twice. Once for the fact of changing the criteria (which trigger resetting the page to 1) and then again, as the page changed to one. Is there any clean way to avoid this clash and make the fetch only happen once?
Here's my simplified code:
import { useState, useEffect } from 'react';
export default function MyComponent() {
const [items, setItems] = useState([]);
const [column, setColumn] = useState();
const [direction, setDirection] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(10);
useEffect(
() => (async () => {
const response = await fetch('...');
const { items } = await response.json();
setItems(items);
})(),
[column, direction, currentPage, perPage]
);
useEffect(
() => setCurrentPage(1), // This triggers the useEffect() above
[column, direction, perPage]
);
return (
// Template code
);
}
How would a React guru do this?
You can add a state like shouldFetch that can be used to conditionally fetch and avoid multiple calls.
const [column, setColumn] = useState();
const [direction, setDirection] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(10);
const [shouldFetch, setShouldFetch] = useState(true);
useEffect(() => {
(async () => {
if (shouldFetch) {
const response = await sleep(1000);
console.log(response);
// prevent fetch as we want to allow it later
setShouldFetch(false);
}
})();
}, [column, direction, currentPage, perPage, shouldFetch]);
useEffect(() => {
setCurrentPage(1);
// allow fetch
setShouldFetch(true);
}, [column, direction, perPage]);
const changeColumn = () => {
setColumn("new-col");
};
const changeCurrentPage = () => {
setCurrentPage(2);
// to fetch when currentPage changes
// this should not be added to other handlers as it is also present in the second useEffect that gets triggered when other params change
setShouldFetch(true);
};
const changePerPage = () => {
setPerPage(20);
};
const changeDirection = () => {
setDirection("descending");
};
Alternative:
To avoid unnecessary fetching and to make sure that items are fetched using the updated state values, you can remove the second useEffect and reset currentPage when you update other params.
This will only trigger the useEffect once because React will perform both state updates (setColumn and setCurrentPage) at once.
const sleep = (ms) => new Promise((res) => setTimeout(() => res("Hi Mom"), ms));
export default function App() {
// const [items, setItems] = useState([]);
const [column, setColumn] = useState();
const [direction, setDirection] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(10);
useEffect(() => {
(async () => {
const response = await sleep(1000);
console.log(response);
})();
}, [column, direction, currentPage, perPage]);
// remove this effect
// useEffect(() => setCurrentPage(1), [column, direction, perPage]);
const changeColumn = () => {
setColumn("new-col");
setCurrentPage(1);
};
const changeCurrentPage = () => {
setCurrentPage(2);
};
const changePerPage = () => {
setPerPage(20);
setCurrentPage(1);
};
const changeDirection = () => {
setDirection("descending");
setCurrentPage(1);
};
return (
<>
<button onClick={changeColumn}>change column</button>
<button onClick={changeDirection}>change direction</button>
<button onClick={changeCurrentPage}>change page</button>
<button onClick={changePerPage}>change perPage</button>
</>
);
}

How to pass variable to hook in React?

I am trying to pass some custom metadata to my firebase firestore, I believe I must pass the metadata I grabbed in my component up to the hook but am unsure how to do so,
my component:
const UploadForm = () => {
const [file, setFile] = useState(null);
const [error, setError] = useState(null);
const [metadata, setMetadata] = useState(null);
const types = ['image/png', 'image/jpeg'];
const changeHandler = (e) => {
let selected = e.target.files[0];
if (selected && types.includes(selected.type)) {
setFile(selected);
setError('');
const pieceName = document.getElementById("pieceName").value;
const pieceDescription = document.getElementById("pieceDescription").value;
const newMetadata = {
customMetaData: {
artName: pieceName,
artDescription: pieceDescription
}
};
setMetadata(newMetadata);
...
export default UploadForm;
& my hook:
const useStorage = (file, metadata) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState(null);
const [url, setUrl] = useState(null);
useEffect(() => {
// references
const storageRef = projectStorage.ref(file.name);
const collectionRef = projectFirestore.collection('images');
storageRef.put(file).on('state_changed', (snap) => {
let percentage = (snap.bytesTransferred / snap.totalBytes) * 100;
setProgress(percentage);
}, (err) => {
setError(err);
}, async () => {
const url = await storageRef.getDownloadURL();
const createdAt = timestamp();
collectionRef.add({ url, createdAt, metadata });
setUrl(url);
});
}, [file, metadata]);
return { progress, url, error };
}
export default useStorage;
I am able to upload to Firebase Storage/firestore no problem but don't know how to feed this extra metadata.
To change the metada just call the updateMetadata on the ref:
const useStorage = (file, metadata) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState(null);
const [url, setUrl] = useState(null);
useEffect(() => {
// references
const storageRef = projectStorage.ref(file.name);
const collectionRef = projectFirestore.collection('images');
storageRef.put(file).on('state_changed', (snap) => {
let percentage = (snap.bytesTransferred / snap.totalBytes) * 100;
setProgress(percentage);
}, (err) => {
setError(err);
}, async () => {
await storageRef.updateMetadata(metadata)
const url = await storageRef.getDownloadURL();
const createdAt = timestamp();
collectionRef.add({ url, createdAt, metadata });
setUrl(url);
});
}, [file, metadata]);
return { progress, url, error };
}
export default useStorage;
You can read more about it here.

How to call custom hook inside of form submit button?

I am create custom hook that fetch requests network.I want to call custom hook when form submit button clicked but depending on hook rules i can't do that. how to can implement this scenario?
this custom hook:
const useRequest = (url, method, dependencies, data = null) => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const res = await request[method](url, data);
setResponse(res);
setLoading(false);
} catch (e) {
setError(e);
setLoading(false);
}
};
fetchData();
}, dependencies);
return { response, error, loading };
};
Move fetchData function out of useEffect and export it:
const useRequest = (url, method, dependencies, data = null) => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const fetchData = async () => {
setLoading(true);
try {
const res = await request[method](url, data);
setResponse(res);
setLoading(false);
} catch (e) {
setError(e);
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, dependencies);
return { response, error, loading, fetchData };
};
Than when you can call it anywhere in your code.

Resources