React hooks - fetching data from api and passing to a component - reactjs

So basically, I'm trying to fetch data from api and pass it to Component.
I create usePosition hook to get my positon from browser, and then get response from api. I really don't know how to wait with useEffect for my position, when i'm executing this code now I'm getting always log 'no position'.
const usePosition = () => {
const [error, setError] = useState(null);
const [position, setPosition] = useState();
useEffect(() => {
const geo = navigator.geolocation;
if(!geo) {
setError('Geolocation is not supported.');
return;
}
const handleSuccess = position => {
const { latitude, longitude } = position.coords;
setPosition({
latitude,
longitude
});
};
const handleError = error => {
setError(error.message);
};
geo.getCurrentPosition(handleSuccess, handleError);
}, []);
return { position, error };
}
function App() {
const {position, error} = usePositon();
const [weather, setWeather] = useState([]);
useEffect(() => {
if(position) {
const URL = `https://api.openweathermap.org/data/2.5/onecall?lat=${position.latitude}&lon=${position.longitude}&exclude=current,minutely,daily&units=metric&lang=pl&appid=${API_KEY}`;
const fetchData = async () => {
const result = await fetch(URL)
.then(res => res.json())
.then(data => data);
setWeather(result.hourly);
}
fetchData();
} else {
console.log('no position');
}
}, []);
return (
<div className="App">
<div>
<Swiper weather={weather}/>
</div>
</div>
)
}

It's all because of [] empty dependencies list down in App's useEffect. It runs exactly once on mount, when usePosition has not requested anything yet. And once it successes later and returns different { error, position } App does not react.
How to solve? Provide things as dependencies:
useEffect(() => {
if(position) {
const URL = `https://api.openweathermap.org/data/2.5/onecall?lat=${position.latitude}&lon=${position.longitude}&exclude=current,minutely,daily&units=metric&lang=pl&appid=${API_KEY}`;
const fetchData = async () => {
const result = await fetch(URL)
.then(res => res.json())
.then(data => data);
setWeather(result.hourly);
}
fetchData();
} else {
console.log('no position');
}
}, [position, error]);

Related

How to effectively refresh the data in a custom hook using react-infinite-scroll-component when an item is deleted?

I am using a custom hook useInfiniteFetchSearch to fetch and search data for a infinite scroll component built using react-infinite-scroll-component.
The hook makes an API call and sets the data in the state using setData. Currently, I am using refreshData() method to refresh the data again when an item is deleted from the list.
However, I am not satisfied with this solution as it calls the API again even though I already have the data. Is there a more efficient way to refresh the data and update the infinite scroll component without making another API call?
Here is my custom hook implementation:
import { useState, useEffect, useRef } from "react";
import axios from "axios";
const useInfiniteFetchSearch = (api, resultsPerPage, sort = null) => {
const [data, setData] = useState([]);
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(2);
const [loading, setLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const searchTermRef = useRef(null);
useEffect(() => {
const searchData = async () => {
try {
setLoading(true);
let query = `${api}${
searchTerm === "" ? `?` : `?search=${searchTerm}&`
}page=1`;
query = sort ? `${query}&sort=${sort}` : query;
const result = await axios.post(query);
const fetchedData = result.data;
setData(fetchedData);
setPage(2);
setHasMore(fetchedData.length === resultsPerPage);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
searchData();
}, [searchTerm, api, resultsPerPage, sort]);
const refreshData = async () => {
try {
setLoading(true);
let query = `${api}${
searchTerm === "" ? `?` : `?search=${searchTerm}&`
}page=1`;
query = sort ? `${query}&sort=${sort}` : query;
const result = await axios.post(query);
const fetchedData = result.data;
setData(fetchedData);
setPage(2);
setHasMore(fetchedData.length === resultsPerPage);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const fetchMore = async () => {
try {
setLoading(true);
let query = `${api}?search=${searchTerm}&page=${page}`;
query = sort ? `${query}&sort=${sort}` : query;
const result = await axios.post(query);
const newData = result.data;
setData((prev) => [...prev, ...newData]);
setPage(page + 1);
setHasMore(newData.length === resultsPerPage);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const handleSearch = async (e) => {
e.preventDefault();
setSearchTerm(searchTermRef.current.value);
};
const handleDelete = async (e, itemId) => {
try {
await axios.delete(`${api}${itemId}`);
setData((prevData) => prevData.filter((item) => item.id !== itemId));
refreshData();
} catch (error) {
console.log(error);
} finally {
}
};
return {
state: { data, hasMore, loading, searchTermRef, searchTerm },
handlers: {
fetchMore,
setSearchTerm,
handleSearch,
handleDelete,
},
};
};
export default useInfiniteFetchSearch;
I am using this hook in my component:
const { state, handlers } = useInfiniteFetchSearch("/api/guides/search", 5);
const { data, hasMore, loading, searchTermRef, searchTerm } = state;
const { fetchMore, handleSearch, setSearchTerm, handleDelete } = handlers;
....
<InfiniteScroll
dataLength={data.length}
next={fetchMore}
hasMore={hasMore}
scrollableTarget="scrollableDiv"
loader={
<div className="flex justify-center items-center mx-auto">
<Loader />
</div>
}
>
<div className="space-y-1">
{data &&
data.map((item, index) => (
<GuidesItem
key={index}
guide={item}
handleDelete={handleDelete}
/>
))}
</div>
</InfiniteScroll>
I would appreciate any suggestions or solutions to this problem, thank you!

Stop invoking custom hook on first render

I started having fun with custom hooks recently. I am mostly using them to fetch from api. The thing is that since I cannot really put useFetchLink inside of functions or useEffect i dont know how to prevent it from fetching after website first render. I could put some ifs in the hook but isn't there any other way?
***component***
export default function LinkShortener({ setLinkArr }) {
const [nextLink, setNextLink] = useState();
const inputRef = useRef(null);
const handleClick = () => {
setNextLink(inputRef.current.value);
};
const { shortLink, loading, error } = useFetchLink(nextLink);
useEffect(() => {
setLinkArr((prev) => [
...prev,
{
id: prev.length === 0 ? 1 : prev[prev.length - 1].id + 1,
long: nextLink,
short: shortLink,
},
]);
inputRef.current.value = "";
}, [shortLink, error]);
return (
<LinkShortenerContainer>
<InputContainer>
<LinkInput ref={inputRef} type="text" />
</InputContainer>
<Button
size={buttonSize.medium}
text={
loading ? (
<Loader />
) : (
<FormattedMessage
id="linkShortener.shortenItBtn"
defaultMessage="Shorten It !"
/>
)
}
onClick={handleClick}
></Button>
</LinkShortenerContainer>
);
}
***hook***
const useFetchLink = (linkToShorten) => {
const [shortLink, setShortLink] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const fetchLink = async () => {
setLoading(true);
try {
const response = await fetch(
`https://api.shrtco.de/v2/shorten?url=${linkToShorten}`
);
if (response.ok) {
const data = await response.json();
setShortLink(data.result.short_link);
} else {
throw response.status;
}
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchLink(linkToShorten);
}, [linkToShorten]);
const value = { shortLink, loading, error };
return value;
};```
Why not using directly fetchLink function and calling it whenever you need inside the component? I would change the hook in this way without useEffect inside
const useFetchLink = (linkToShorten) => {
const [shortLink, setShortLink] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const fetchLink = async () => {
setLoading(true);
try {
const response = await fetch(
`https://api.shrtco.de/v2/shorten?url=${linkToShorten}`
);
if (response.ok) {
const data = await response.json();
setShortLink(data.result.short_link);
} else {
throw response.status;
}
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
const value = { shortLink, loading, error, fetchLink };
return value;
};
Generally speaking - the standard way to avoid useEffect from running of 1st render is to use a boolean ref initialized with false, and toggled to true after first render - see this answer.
However, in your case, you don't want to call the function if linkToShorten is empty, even if it's not the 1st render, so use an if inside useEffect.
const useFetchLink = (linkToShorten) => {
const [shortLink, setShortLink] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const fetchLink = useCallback(async (linkToShorten) => {
setLoading(true);
try {
const response = await fetch(
`https://api.shrtco.de/v2/shorten?url=${linkToShorten}`
);
if (response.ok) {
const data = await response.json();
setShortLink(data.result.short_link);
} else {
throw response.status;
}
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
if(linkToShorten) fetchLink(linkToShorten);
}, [fetchLink, linkToShorten]);
const value = { shortLink, loading, error };
return value;
};

React Fetching Request returns null after refresh

So I'm working on a school project right now and I've created a backend using express and nodejs. I want to retrieve data and only get the questions that are associated with the current category. After retrieving the data it gives the data I want but then when I refresh the page it only gets null. What am I doing wrong?
Fetch Hook
import axios from 'axios';
export default function useFetch(name) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
(async function () {
try {
setLoading(true);
const response = await axios
.get('http://localhost:3001/api/getQuestions')
.then((res) => {
const dataArray = res.data;
const questionArray = dataArray.filter((question) => {
return question.questionCategory === 'installation';
});
setData(questionArray);
});
console.log(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
})();
}, [name]);
return { data, error, loading };
}
Quiz Component
import fetchQuestion from '../../../hooks/fetchQuestion';
const InstallationsQuiz = () => {
const { data, loading, error } = fetchQuestion('installation');
useEffect(() => {
data.map((item) => {
console.log(item);
});
}, [data]);

How to update the content when is deleted

I simply can't get this thing working for me. I have an item that is being deleted on a backend side but it is not rendering immediately on a front. You need to refresh the whole page to make it work. I saw similar questions asked here, but I couldn't make it work on my example.
So here in my component I'm getting the data from backend:
const AdminMessages = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const getData = async () => {
try {
const response = await axios.get(
`http://localhost:5000/contact/message/`
);
setData(response.data);
setError(null);
} catch (err) {
setError(err.message);
setData(null);
} finally {
setLoading(false);
}
};
getData();
}, []);
return (
{data.map((item) => (
<h3 className="bold-text">{item.title}</h3>
<div
name="trash-outline"
id="approve-icon"
onClick={() => dispatch(deleteContactMessage(item._id))}
></div>
))}
)
);
};
And here is my controller for delete:
export const deleteContactMessage = async (req, res) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send("No post with that id");
await ContactMessage.findByIdAndRemove(id);
res.json({ message: "Post deleted successfully" });
};

React Hook useEffect has a missing dependency: 'fetchUser'. useEffect problem?

I'm new to react and I'm learning how to use useEffect. I encountered this warning in my react app. I tried out some solutions on SO but the warning still remains. Both fetchUser and fetchPosts trigger this warning. Can anyone enlighten me what is the problem and what does the warning mean?
App.js
useEffect(() => {
setLoading(true)
const getUser = async () => {
const userFromServer = await fetchUser()
if (userFromServer) {
setUser(userFromServer)
setLoading(false)
} else {
console.log("error")
}
}
getUser()
}, [userId])
useEffect(() => {
const getPosts = async () => {
const postsFromServer = await fetchPosts()
setPosts(postsFromServer)
}
getPosts()
}, [userId])
useEffect(() => {
const getUserList = async () => {
const userListFromServer = await fetchUserList()
setUserList(userListFromServer)
}
getUserList()
}, [])
// Fetch user
const fetchUser = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
const data = await res.json()
return data
}
// Fetch posts
const fetchPosts = async () => {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
const data = await res.json()
return data
}
// Fetch list of users
const fetchUserList = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users/')
const data = await res.json()
return data
}
If you are using any function or state which has been declared outside the useEffect then you need to pass it in the dependency array like this:
const someFunctionA = () => {
....
}
const someFunctionB = () => {
....
}
useEffect(() => {
....
}, [someFunctionA, someFunctionB])
You can read more about it here in case you want to know how it will be rendered: React useEffect - passing a function in the dependency array

Resources