How to display ActivityIndicator untill all elements are mapped - reactjs

I have this screen in which I want to see ActivityIndicator untill all devices are mapped (not fetched):
const MyScreen = () => {
const [devices, setDevices] = useState();
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
getDevices();
}, []);
const getDevices = async () => {
const pulledDevices = await fetchDevices();
setDevices(pulledDevices)
setIsLoading(false)
};
if (isLoading)
return (
<ActivityIndicator />
);
return (
<View >
{devices?.map((device) => {
return (
<View>
<Text>{device.name}</Text>
</View>
);
})}
</View>
);
};
Mapping these devices takes some time.
How could I implement here ActivityIndicator untill all devices are mapped.

I suggest you to use a bit more sophisticated async await hook to handle this.
useAsyncHook.js
const useAsync = asyncFunction => {
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const execute = useCallback(async () => {
setLoading(true);
setResult(null);
setError(null);
try {
const response = await asyncFunction();
setResult(response);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
}, [asyncFunction]);
useEffect(() => {
execute();
}, [execute]);
return { loading, result, error };
};
This is a raw use async hook that can be enhanced many way but it handles the loading state correctly in this state.
Usage:
const { loading, result, error } = useAsync(yourFunction);
if (loading) return null;
return <Component />;

Related

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;
};

Axios get request returns 404 when passed url path parameter

I have a React component "PostDetails" like this:
const PostDetails = () => {
const params = useParams();
const [post, setPost] = useState({});
const [fetchPostById, isLoading, error] = useFetching(async (id) => {
const response = await PostService.getById(id);
setPost(response.data);
})
useEffect(() => {
fetchPostById(params.id)
}, [])
return (
<div>
<h1>Post details page for ID = {params.id}</h1>
<div>{post.id}. {post.title}</div>
</div>
);
};
export default PostDetails;
Custom hook "useFetching" is implemented like this:
export const useFetching = (callback) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const fetching = async () => {
try {
setIsLoading(true);
await callback();
} catch (e) {
setError(e.message);
} finally {
setIsLoading(false);
}
}
return [fetching, isLoading, error];
}
Utility class "PostService" is implemented like this:
export default class PostService {
static async getById(id) {
const response = await axios.get("https://jsonplaceholder.typicode.com/posts/" + id);
return response;
};
}
In browser console I get the error for "GET" request like this:
GET https://jsonplaceholder.typicode.com/posts/undefined 404
I tried to reformat my URL like this:
https://jsonplaceholder.typicode.com/posts/${id}
But still get the same error.
Why does "params.id" convert into undefined when I call my axios fetching request? What am I doing wrong here?
hope my code would be useful.
CodeSandBox
const [id, setId] = useState(1)
const [data, setData] = useState([]);
useEffect(() => {
const res = axios
.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then((res) => setData(res.data));}, [id]);
return (
<>
<div>Fetched Title of data</div>
<div>{data.title}</div>
<button onClick={() => setId(id + 1)}>Click to increase id</button>
<button onClick={() => setId(id - 1)}>Click to decrease id</button>
</>);
can you try plz
useEffect(() => {
params?.id && fetchPostById(params.id)
}, [])
Try this. I have earned.
const PostDetails = () => {
const params = useParams();
const [post, setPost] = useState({});
const [fetchPostById, isLoading, error] = useFetching(async () => {
const response = await PostService.getById(params.id);
setPost(response.data);
})
useEffect(() => {
fetchPostById(params)
}, [])
return (
<div>
<h1>Post details page for ID = {params.id}</h1>
<div>{post.id}. {post.title}</div>
</div>
);
};
export default PostDetails;

Search is not being updated

I am using the meal database.The data from the link is not being updated after search. But if I console.log the search input, I can see the new link.
That's my API for searching:
API_URL_SEARCH="https://www.themealdb.com/api/json/v1/1/search.php?s="
Thats search page:
function Meals({ navigation}) {
const [searchInput, setSearchInput] = useState('');
const handleChange = (inputText) => {
setSearchInput(inputText);
};
const { loading, error, data } = useFetch(config.API_URL_SEARCH + searchInput);
const handleMealSelect = idMeal => {
navigation.navigate("MealDetail", {idMeal})
}
const renderMeals = ({item}) => <Meal meal={item} onSelect={() => handleMealSelect(item.idMeal)}/>
if(loading) {
return <Loading/>;
}
if(error) {
return <Error/>;
}
return(
<View>
<SearchBar
placeholder="Type Here..."
onChangeText={handleChange}
value={searchInput} />
<FlatList keyExtractor={(meals) => meals.id} data={data.meals} renderItem={renderMeals}/>
</View>
)
}
Thats meal component:
const Meal= ({meal, onSelect}) => {
return(
<TouchableOpacity style={styles.container} onPress={onSelect}>
<ImageBackground
style={styles.image}
source={{uri: meal.strMealThumb}}
imageStyle={{borderTopLeftRadius:10, borderTopRightRadius:10}} />
<Text style={styles.title}>{meal.strMeal}</Text>
</TouchableOpacity>
)
}
Here is useFetch for getting data and getting loading and error situations just in case of.
function useFetch(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
const fetchData = async () => {
try {
const {data: responseData} = await axios.get(url);
setData(responseData);
setLoading(false); }
catch (error) {
setError(error.message);
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
return {error, loading, data};
};

React. How to make function with state?

I'm learning React Native and can't understand how to make function or component instead of repeated part of my code:
export const PostScreen = () => {
const postId = 1
/* THIS PART IS REPEATED IN MANY FILES */
const [data, setData] = useState([]);
const [loader, setLoader] = useState(false);
const [error, setError] = useState(null);
const fetchData = async () => {
setError(null)
setLoader(true)
try {
const data = await Api.get(`http://localhost/api/posts/${postId}`)
if (data.success == 1) {
setData(data.data)
}
else {
setError(data.error)
}
}
catch(e) {
console.log(e)
setError('Some problems')
}
finally {
setLoader(false)
}
}
useEffect(() => {
fetchData()
}, [])
if (loader) {
return <Loader />
}
if (error) {
return <View><Text>{error}</Text></View>
}
/*END>>> THIS PART IS REPEATED IN MANY FILES */
return (
<View><Text>{data.title}</Text></View>
)
}
The problem is that fetchData is working with state. I found, how to do it with Context, but I don't wont to use it. Is there any way to do clear code without Context?
So, why not make your own hook, then?
Write the hook in a dedicated module:
function useMyOwnHook(props) {
const {
postId,
} = props;
const [data, setData] = useState([]);
const [loader, setLoader] = useState(false);
const [error, setError] = useState(null);
const fetchData = async () => {
setError(null)
setLoader(true)
try {
const data = await Api.get(`http://localhost/api/posts/${postId}`)
if (data.success == 1) {
setData(data.data)
}
else {
setError(data.error)
}
}
catch(e) {
console.log(e)
setError('Some problems')
}
finally {
setLoader(false)
}
}
useEffect(() => {
fetchData()
}, [])
const render = loader
? <Loader />
: error
? <View><Text>{error}</Text></View>
: null;
return {
data,
render,
}
}
At that point, the component will be written as follows:
export const PostScreen = () => {
const postId = 1
const {
render,
data,
} = useMyOwnHook({ postId });
return render ?? (
<View><Text>{data.title}</Text></View>
)
}

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

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]);

Resources