How to Transform data from the backend with react? - javascript

I have react native app. And I am calling a api url. But I don't want to have all the data from the api call. But some specific data. Like name and image.
So I have a service:
export const fetchCategoryData = async () => {
try {
const response = await fetch("http://10.14.220.60:8000/animal/categories/main_groups/", {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
console.log("RESSPONSE", response);
return await response.json();
} catch (error) {
console.error("There was a problem with the fetch operation:", error);
throw error;
}
};
export const categoryTransform = ({ results = [] }) => {
const mappedResults = results.map((categoryList) => {
//categoryList.images = categoryList.images;
return {
...categoryList,
};
});
console.log("MAPPEDRESULT", fetchCategoryData.response());
return mappedResults;
};
and data context:
import { Children, createContext, useEffect, useState } from "react";
import { categoryTransform, fetchCategoryData } from "./category.service";
export const CategoryContext = createContext();
export const CategoryContextProvider = ({ children }) => {
const [categoryList, setCategoryList] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const retrieveCategories = () => {
setLoading(true);
setTimeout(() => {
fetchCategoryData()
.then(categoryTransform)
.then((results) => {
setLoading(false);
setCategoryList(results);
})
.catch((err) => {
setLoading(false);
setError(err);
});
}, 200);
};
useEffect(() => {
retrieveCategories();
}, []);
console.log(categoryList);
return (
<CategoryContext.Provider
value={{
categoryList,
loading,
error,
}}>
{children}
</CategoryContext.Provider>
);
};
and component dat injects the data context:
import { FlatList, SafeAreaView, StatusBar } from "react-native";
import React, { useContext } from "react";
import { CategoryContext } from "../../../services/category/category.context";
import { CategoryInfoCard } from "../components/category-info-card.component";
import { Searchbar } from "react-native-paper";
import { Spacer } from "../../../components/spacer/spacer.component";
import styled from "styled-components/native";
const SafeArea = styled(SafeAreaView)`
flex: 1;
${StatusBar.currentHeight && `margin-top: ${StatusBar.currentHeight}px`};
`;
const SearchContainer = styled.View`
padding: ${(props) => props.theme.space[3]};
`;
const CategoryList = styled(FlatList).attrs({
contentContainerStyle: {
padding: 16,
},
})``;
export const CategoryScreen = () => {
const { categoryList } = useContext(CategoryContext);
return (
<SafeArea>
<SearchContainer>
<Searchbar />
</SearchContainer>
<CategoryList
data={categoryList}
renderItem={({ item }) => {
console.log("ITEMs", item);
return (
<Spacer position="bottom" size="large">
<CategoryInfoCard categoryList={item} />
</Spacer>
);
}}
keyExtractor={(item) => item.name}
/>
</SafeArea>
);
};
And this is a deault component with hard coded data:
/* eslint-disable prettier/prettier */
import { React, useEffect, useState } from "react";
import { Card } from "react-native-paper";
import { Spacer } from "../../../components/spacer/spacer.component";
import { Text } from "../../../components/typography/text.component";
import styled from "styled-components/native";
const CategoryCard = styled(Card)`
background-color: ${(props) => props.theme.colors.bg.primary};
`;
const CategoryGroupCardCover = styled(Card.Cover)`
padding: ${(props) => props.theme.space[3]};
background-color: ${(props) => props.theme.colors.bg.primary};
`;
export const CategoryInfoCard = ({ categoryList = {} }) => {
const {
name = "Zoogdieren",
images = "https://cdn.pixabay.com/photo/2017/02/20/18/03/cat-2083492_960_720.jpg",
} = categoryList;
return (
<CategoryCard
elevation={5}
style={{
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
}}>
<Text center variant="h1" style={{ left: 100 }}>
{name}
</Text>
<CategoryGroupCardCover source={{ uri: images }} />
</CategoryCard>
);
};
So it is about the categoryTransform in the service.
Because when I am calling this function it doesn't work. I get empty array back.
But if I comment the categoryTransform method in the categoryContext. Like:
const retrieveCategories = () => {
setLoading(true);
setTimeout(() => {
fetchCategoryData()
//.then(categoryTransform)
.then((results) => {
setLoading(false);
setCategoryList(results);
})
.catch((err) => {
setLoading(false);
setError(err);
});
}, 200);
};
Then it works.
And the api call looks like:
ITEMs Object {
"animals": Array [],
"category": null,
"description": "zoogdieren",
"eaza": "",
"id": 1,
"images": "http://10.14.220.60:8000/media/photos/categories/mammal.jpg",
"legislation": "",
"name": "zoogdieren",
"review": "",
}
Question: how to fix the categoryTransform method, so that it will filter the specif data: name and images?

Related

Component rendering before finishing the useEffect

I have a component (ownPrescriptionsPanel) inside which I'm rendering another component (PrescriptionsList). Inside the parent component, I have a useEffect hook to fetch data using Axios for the child component (PrescriptionsList). The problem is no matter what I try, the PrescriptionsList is always empty and only gets populated when I refresh. I have three child components (all are PrescriptionsList components) but I've shown only one in the below code.
import React, { useEffect, useState } from "react";
import Axios from "axios";
import { PrescriptionsList } from "../../components/prescriptionsList/prescriptionsList";
import "./ownPrescriptionsPanelStyles.css";
export const OwnPrescriptionsPanel = () => {
const [pastPrescriptions, setPastPrescriptions] = useState([]);
const [openPrescriptions, setOpenPrescriptions] = useState([]);
const [readyPrescriptions, setReadyPrescriptions] = useState([]);
const [isBusy1, setIsBusy1] = useState(true);
useEffect(() => {
Axios.post(
"http://localhost:3001/getpatientprescriptions",
{
id: sessionStorage.getItem("id"),
},
{
headers: {
"Content-Type": "application/json",
},
}
).then((response) => {
console.log("getpatientprescriptions", response.data);
var resArr = []; //getting rid of the duplicates
response.data.filter(function (item) {
var i = resArr.findIndex(
(x) => x.prescriptionId === item.prescriptionId
);
if (i <= -1) {
resArr.push(item);
}
return null;
});
setPastPrescriptions(resArr);
setIsBusy1(false);
});
}, []);
if (isBusy1) {
return <div>loading</div>;
}
return (
<>
<PrescriptionsList
pastPrescriptions={pastPrescriptions}
heading="All prescriptions"
viewOnly={true}
prescriptionStatusOpen={false}
showPharmacy={false}
/>
</>
);
};
Edit: Given below is the code for PrescriptionList component
import React, { useState } from "react";
import Axios from "axios";
import DescriptionTwoToneIcon from "#mui/icons-material/DescriptionTwoTone";
import PresciptionModal from "../prescriptionModal/prescriptionModal";
import "./prescriptionsListStyles.css";
export const PrescriptionsList = ({
pastPrescriptions,
heading,
viewOnly,
showPharmacy,
}) => {
const [prescriptionDetails, setprescriptionDetails] = useState([]);
const [prescriptionDrugList, setPrescriptionDrugList] = useState([]);
const [open, setOpen] = useState(false);
const handleClose = () => {
console.log("close");
setOpen(false);
};
console.log("pastPrescriptions", pastPrescriptions);
const getPrescriptionDrugDetails = async (prescriptionId) => {
await Axios.post(
"http://localhost:3001/prescriptionDrugDetails",
{
prescriptionId: prescriptionId,
},
{
headers: {
"Content-Type": "application/json",
},
}
).then((response) => {
console.log("prescriptionDrugDetails", response.data);
setPrescriptionDrugList(response.data);
});
};
const handlePrescriptionClick = async (prescriptionDetails) => {
console.log("prescriptionDetails", prescriptionDetails);
setprescriptionDetails(prescriptionDetails);
await getPrescriptionDrugDetails(prescriptionDetails.prescriptionId);
setOpen(true);
};
const pastPrescriptionsList = pastPrescriptions.map((d) => (
<div
value={d}
onClick={() => handlePrescriptionClick(d)}
key={d.drugId}
className="prescriptionListItem"
>
<div style={{ width: "30px" }}>
<DescriptionTwoToneIcon fontSize="small" />
</div>
{d.prescriptionId}
</div>
));
const markPrescriptionComplete = async (d) => {
await Axios.post(
"http://localhost:3001/markcomplete",
{
prescriptionId: d.prescriptionDetails.prescriptionId,
pharmacyId: d.prescriptionDetails.pharmacyId,
},
{
headers: {
"Content-Type": "application/json",
},
}
);
console.log(
"prescriptionId, pharmacyId",
d.prescriptionDetails.prescriptionId,
d.prescriptionDetails.pharmacyId
);
window.location.reload(true);
};
return (
<div className="prescriptionsListContainer">
<div className="viewPrescriptionsLabel">{heading}</div>
<div className="prescriptionsContainer">{pastPrescriptionsList}</div>
{open && (
<PresciptionModal
open={open}
onClose={handleClose}
prescriptionDetails={prescriptionDetails}
prescriptionDrugList={prescriptionDrugList}
viewOnly={viewOnly}
// prescriptionStatusOpen={false}
markprescriptioncomplete={markPrescriptionComplete}
showPharmacy={showPharmacy}
/>
)}
</div>
);
};
I tried solution 1, solution 2 and the code shown above is using solution from geeksforgeeks. None seem to be working

How can i fix this error? ERROR TypeError: null is not an object (evaluating 'userdata.user')

How can i fix this error in my project? the error is ERROR TypeError: null is not an object (evaluating 'userdata.user') i am using react native expo after logging into my app and entering mainpage its giving me this error can anyone can help me to fix this error? i today send 8 hours fixing this error but i can't able to fix it can anyone can help me with that?
import { StyleSheet, Text, View, StatusBar } from 'react-native'
import React, { useEffect, useState } from 'react'
import { containerFull } from '../../CommonCss/pagecss'
import { formHead } from '../../CommonCss/formcss'
import Bottomnavbar from '../../Components/Bottomnavbar'
import TopNavbar from '../../Components/TopNavbar'
import FollowersRandomPost from '../../Components/FollowersRandomPost'
import AsyncStorage from '#react-native-async-storage/async-storage';
import * as Location from 'expo-location';
const Mainpage = ({ navigation }) => {
const [userdata, setUserdata] = React.useState(null)
const [location, setLocation] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
const [city, setCity] = useState(null);
const [data, setData] = useState(null)
useEffect(() => {
AsyncStorage.getItem('user')
.then(data => {
// console.log('async userdata ', data)
setUserdata(JSON.parse(data))
})
.catch(err => alert(err))
}, [])
console.log('userdata ', userdata)
useEffect(() => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
}
let location = await Location.getCurrentPositionAsync({});
setLocation(location);
let city = await Location.reverseGeocodeAsync(location.coords);
setCity(city[0].city);
})();
}, []);
const sendCity = () => {
setCity(city);
fetch('http://192.168.1.52:3000/updateCity', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
city: city,
username: userdata.user.username
}),
})
.then((response) => response.json())
.then((data) => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
};
useEffect(() => {
sendCity();
}, [])
return (
<View style={styles.container}>
<StatusBar />
<TopNavbar navigation={navigation} page={"MainPage"} />
<Bottomnavbar navigation={navigation} page={"MainPage"} />
<FollowersRandomPost />
</View>
)
}
export default Mainpage
const styles = StyleSheet.create({
container: {
width: '100%',
height: '100%',
backgroundColor: 'black',
paddingVertical: 50,
}
})
And why something city value becomes null and something new york which is correct how to fix null?
import { StyleSheet, Text, View, StatusBar } from 'react-native'
import React, { useEffect, useState } from 'react'
import { containerFull } from '../../CommonCss/pagecss'
import { formHead } from '../../CommonCss/formcss'
import Bottomnavbar from '../../Components/Bottomnavbar'
import TopNavbar from '../../Components/TopNavbar'
import FollowersRandomPost from '../../Components/FollowersRandomPost'
import AsyncStorage from '#react-native-async-storage/async-storage';
import * as Location from 'expo-location';
const Mainpage = ({ navigation }) => {
const [userdata, setUserdata] = useState(null);
const [location, setLocation] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
const [city, setCity] = useState(null);
const [data, setData] = useState(null);
useEffect(() => {
async function getUserData() {
try {
const userDataString = await AsyncStorage.getItem('user');
const userData = JSON.parse(userDataString);
setUserdata(userData);
} catch (err) {
alert(err);
}
}
getUserData();
}, []);
useEffect(() => {
async function getLocation() {
try {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
}
let location = await Location.getCurrentPositionAsync({});
setLocation(location);
let city = await Location.reverseGeocodeAsync(location.coords);
setCity(city[0].city);
} catch (err) {
console.error(err);
}
}
getLocation();
}, []);
useEffect(() => {
async function sendCity() {
try {
setCity(city);
const response = await fetch('http://192.168.1.52:3000/updateCity', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
city: city,
username: userdata.user.username
}),
});
const data = await response.json();
console.log('Success:', data);
} catch (err) {
console.error('Error:', err);
}
}
if (userdata) {
sendCity();
}
}, [userdata]);
console.log(city)
return (
<View style={styles.container}>
<StatusBar />
<TopNavbar navigation={navigation} page={"MainPage"} />
<Bottomnavbar navigation={navigation} page={"MainPage"} />
</View>
);
}
export default Mainpage
You need to wait AsyncStorage to finish his task before using the new data, how about using top lvl await like :
useEffect(() => {
await AsyncStorage.getItem('user')
.then(data => {
// console.log('async userdata ', data)
setUserdata(JSON.parse(data))
})
.catch(err => alert(err))
}, [])
console.log('userdata ', userdata)
for the 2nd question if you set city state inside the useEffect and with userdata dependency this will trigger the function when userdata state change, and since city initial state is null, and you can't immediatly get the new state from getLocation neither in sendCity, without rerender your component, i think you need to add one more condition before running the function, but this might have some side effect from the useEffect..
useEffect(() => {
async function sendCity() {
try {
const response = await fetch('http://192.168.1.52:3000/updateCity', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
city: city,
username: userdata.user.username
}),
});
const data = await response.json();
console.log('Success:', data);
} catch (err) {
console.error('Error:', err);
}
}
if (userdata && city) {
sendCity();
}
}, [userdata, city]);

TypeError: undefined is not a function. What could be this cause

I'm using redux to save the name of the cities as i fetch them from the json api. I don't know if the problem is from the way the state is initialized or if i have a exported as a function instead of a const. I have also tried changing reducer to a const but it still didn't work
ScreenA
import AsyncStorage from '#react-native-async-storage/async-storage';
import React, { useState, useEffect } from 'react';
import {
StyleSheet,
View,
Text,
Alert,
TextInput,
FlatList,
} from 'react-native';
import SQLite from 'react-native-sqlite-storage';
import { useSelector, useDispatch } from 'react-redux';
import { setName, setAge, increaseAge, getCities } from './Actions';
const db = SQLite.openDatabase(
{
name: 'MainDB',
location: 'default',
},
() => { },
error => { console.log(error) }
);
export default function ScreenA({ navigation}) {
const { name, age, cities } = useSelector(state => state.userReducer);
const dispatch = useDispatch();
useEffect(() => {
getData();
dispatch(getCities());
}, []);
const getData = () => {
try {
db.transaction((tx) => {
tx.executeSql(
"SELECT Name, Age FROM Users",
[],
(tx, results) => {
var len = results.rows.length;
if (len > 0) {
var userName = results.rows.item(0).Name;
var userAge = results.rows.item(0).Age;
dispatch(setName(userName));
dispatch(setAge(userAge));
}
}
)
})
} catch (error) {
console.log(error);
}
}
const updateData = async () => {
if (name.length == 0) {
Alert.alert('Warning!', 'Please write your data.')
} else {
try {
db.transaction((tx) => {
tx.executeSql(
"UPDATE Users SET Name=?",
[name],
() => { Alert.alert('Success!', 'Your data has been updated.') },
error => { console.log(error) }
)
})
} catch (error) {
console.log(error);
}
}
}
const removeData = async () => {
try {
db.transaction((tx) => {
tx.executeSql(
"DELETE FROM Users",
[],
() => { navigation.navigate('Login') },
error => { console.log(error) }
)
})
} catch (error) {
console.log(error);
}
}
return (
<View style={styles.body}>
<Text>
Welcome {name} !
</Text>
<FlatList
data={cities}
renderItem={({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.country}</Text>
<Text style={styles.subtitle}>{item.city}</Text>
</View>
)}
keyExtractor={(item, index) => index.toString()}
/>
{/* <Text style={[
GlobalStyle.CustomFont,
styles.text
]}>
Your age is {age}
</Text>
<TextInput
style={styles.input}
placeholder='Enter your name'
value={name}
onChangeText={(value) => dispatch(setName(value))}
/>
<CustomButton
title='Update'
color='#ff7f00'
onPressFunction={updateData}
/>
<CustomButton
title='Remove'
color='#f40100'
onPressFunction={removeData}
/>
<CustomButton
title='Increase Age'
color='#0080ff'
onPressFunction={()=>{dispatch(increaseAge())}}
/> */}
</View>
)
}
Store
import { configureStore, combineReducers, applyMiddleware } from 'redux'
import {userReducer} from './Reducer'
import thunk from 'redux-thunk'
const rootreducer=combineReducers({userReducer})
const store = configureStore(rootreducer, applyMiddleware(thunk))
export default store
Reducer
import { SET_USER_NAME, SET_USER_AGE, INCREASE_AGE, GET_CITIES } from './Actions';
const initialState = {
name: '',
age: 0,
cities:[]
}
export function userReducer(state = initialState, action) {
switch (action.type) {
case SET_USER_NAME:
return { ...state, name: action.payload };
case SET_USER_AGE:
return { ...state, age: action.payload };
case INCREASE_AGE:
return { ...state, age: state.age + 1 };
case GET_CITIES:
return { ...state, cities: action.payload };
default:
return state;
}
}
Actions
export const SET_USER_NAME = 'SET_USER_NAME';
export const SET_USER_AGE = 'SET_USER_AGE';
export const INCREASE_AGE = 'INCREASE_AGE';
export const GET_CITIES = 'GET_CITIES';
const API_URL = 'https://mocki.io/v1/48419bdb-1d76-45a1-89cb-3ac3fcc7f6ca';
export const getCities = () => {
try {
return async dispatch => {
const result = await fetch(API_URL, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const json = await result.json();
if (json) {
dispatch({
type: GET_CITIES,
payload: json
});
} else {
console.log('Unable to fetch!');
}
}
} catch (error) {
console.log(error);
}
}
export const setName = name => dispatch => {
dispatch({
type: SET_USER_NAME,
payload: name,
});
};
export const setAge = age => dispatch => {
dispatch({
type: SET_USER_AGE,
payload: age,
});
};
export const increaseAge = age => dispatch => {
dispatch({
type: INCREASE_AGE,
payload: age,
});
};

Firebase reading data two times from firestore

I am trying to read the data from a Firebase Firestore collection called 'posts'. Its having few documents in it. When I am using the following code to read data, I am able to read it but two times:
code in posts.jsx file:
import React, { useEffect, useState } from "react";
import '../index.css';
import '../../node_modules/antd/dist/antd.min.css';
import PostSnippet from './PostSnippet';
import _ from 'lodash';
import { PageHeader } from "antd";
import { db } from '../firebase.js';
import { collection, getDocs } from "firebase/firestore";
function Posts(props) {
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchData = async () => {
const postRef = collection(db, 'posts');
const postSnap = await getDocs(postRef);
postSnap.forEach(doc => {
let data = doc.data()
let { id } = doc
let payload = {
id,
...data,
}
setPosts((posts) => [...posts, payload])
})
}
fetchData()
.catch(console.error);
}, [])
return (
<div className="posts_container">
<div className="page_header_container">
<PageHeader
style={{
border: '5px solid rgb(235, 237, 240)',
fontSize: '25px',
margin: '40px',
}}
title="Post"
/>
</div>
<div className="articles_container">
{
_.map(posts, (article, idx) => {
return (
<PostSnippet
key={idx}
id={article.id}
title={article.title}
content={article.content.substring(0, 300)} />
)
})
}
</div>
</div>
)
}
export default Posts;
Code in PostSnippet.jsx file which is used to give the view to individual cards:
import React from "react";
import { Card } from "antd";
import { Link } from "react-router-dom";
const PostSnippet = (props) => {
return (
<>
<div className="post_snippet_container" style={{ margin: "40px" }}>
<Card
type="inner"
title={props.title}
extra={
<Link to={`/post/${props.id}`}>
Refer the Article
</Link>}
>
<p className="article_content">
{
props.content.split('\n').map((paragraph, idx) => {
return <p key={idx}>{paragraph}</p>
})
}
</p>
</Card>
</div>
</>
)
}
export default PostSnippet;
Actual data in Firestore:
Retried data from the firestore:
setPosts((posts) => [...posts, payload])
You only ever add to the array, so when data is fetched for the second time, you grow your array to twice the size. Instead, replace the array with the new data. That way the second fetch will overwrite the first:
const fetchData = async () => {
const postRef = collection(db, 'posts');
const postSnap = await getDocs(postRef);
const newPosts = postSnap.docs.map(doc => {
return {
id: doc.id,
...doc.data(),
}
});
setPosts(newPosts);
}

React loader won't stay visible during 'pending' axios requests

I'm using react context & axios interceptors to hide/show a loading spinner. While it does hide and show correctly when requests and responses are fired, my loading spinner is only showing at the start of a network request. The request fires and then goes into a 'pending' status. During the 'pending' status, the response interceptor is fired and hides the loading spinner.
How can I make sure the loading spinner stays visible during the pending requests?
I tried adding some console logs to fire when the requests, responses, and errors were returned including the count, and it showed it (for two requests) to successfully go from 0 - 1 - 2 - 1 - 0, but in the chrome devtools network tab showed as pending even though all requests were returned.
EDIT: thought I had it working after some refactor but it was a no-go. Added updated code
import React, { useReducer, useRef, useEffect, useCallback } from "react";
import { api } from "api/api";
import LoadingReducer from "reducer/LoadingReducer";
const LoadingContext = React.createContext();
export const LoadingProvider = ({ children }) => {
const [loader, dispatch] = useReducer(LoadingReducer, {
loading: false,
count: 0,
});
const loaderKeepAlive = useRef(null),
showLoader = useRef(null);
const showLoading = useCallback(() => {
dispatch({
type: "SHOW_LOADING",
});
}, [dispatch]);
const hideLoading = useCallback(() => {
loaderKeepAlive.current = setTimeout(() => {
dispatch({
type: "HIDE_LOADING",
});
}, 3000);
return clearTimeout(loaderKeepAlive.current);
}, [dispatch]);
const requestHandler = useCallback(
(request) => {
dispatch({ type: "SET_COUNT", count: 1 });
return Promise.resolve({ ...request });
},
[dispatch]
);
const errorHandler = useCallback(
(error) => {
dispatch({ type: "SET_COUNT", count: -1 });
return Promise.reject({ ...error });
},
[dispatch]
);
const successHandler = useCallback(
(response) => {
dispatch({ type: "SET_COUNT", count: -1 });
return Promise.resolve({ ...response });
},
[dispatch]
);
useEffect(() => {
if (loader.count === 0) {
hideLoading();
clearTimeout(showLoader.current);
} else {
showLoader.current = setTimeout(() => {
showLoading();
}, 1000);
}
}, [showLoader, showLoading, hideLoading, loader.count]);
useEffect(() => {
if (!api.interceptors.request.handlers[0]) {
api.interceptors.request.use(
(request) => requestHandler(request),
(error) => errorHandler(error)
);
}
if (!api.interceptors.response.handlers[0]) {
api.interceptors.response.use(
(response) => successHandler(response),
(error) => errorHandler(error)
);
}
return () => {
clearTimeout(showLoader.current);
};
}, [errorHandler, requestHandler, successHandler, showLoader]);
return (
<LoadingContext.Provider
value={{
loader,
}}
>
{children}
</LoadingContext.Provider>
);
};
export default LoadingContext;
I think a more standard approach would be to just utilize the loading state to conditionally render the Spinner and the result of the promise to remove it from the DOM (see demo below). Typically, the interceptors are used for returning the error from an API response, since axios defaults to the status error (for example, 404 - not found).
For example, a custom axios interceptor to display an API error:
import get from "lodash.get";
import axios from "axios";
const { baseURL } = process.env;
export const app = axios.create({
baseURL
});
app.interceptors.response.use(
response => response,
error => {
const err = get(error, ["response", "data", "err"]);
return Promise.reject(err || error.message);
}
);
export default app;
Demo
Code
App.js
import React, { useEffect, useCallback, useState } from "react";
import fakeApi from "./api";
import { useAppContext } from "./AppContext";
import Spinner from "./Spinner";
const App = () => {
const { isLoading, error, dispatch } = useAppContext();
const [data, setData] = useState({});
const fetchData = useCallback(async () => {
try {
// this example uses a fake api
// if you want to trigger an error, then pass a status code other than 200
const res = await fakeApi.get(200);
setData(res.data);
dispatch({ type: "loaded" });
} catch (error) {
dispatch({ type: "error", payload: error.toString() });
}
}, [dispatch]);
const reloadData = useCallback(() => {
dispatch({ type: "reset" });
fetchData();
}, [dispatch, fetchData]);
useEffect(() => {
fetchData();
// optionally reset context state on unmount
return () => {
dispatch({ type: "reset" });
};
}, [dispatch, fetchData]);
if (isLoading) return <Spinner />;
if (error) return <p style={{ color: "red" }}>{error}</p>;
return (
<div style={{ textAlign: "center" }}>
<pre
style={{
background: "#ebebeb",
margin: "0 auto 20px",
textAlign: "left",
width: 600
}}
>
<code>{JSON.stringify(data, null, 4)}</code>
</pre>
<button type="button" onClick={reloadData}>
Reload
</button>
</div>
);
};
export default App;
AppContext.js
import React, { createContext, useContext, useReducer } from "react";
const AppContext = createContext();
const initialReducerState = {
isLoading: true,
error: ""
};
const handleLoading = (state, { type, payload }) => {
switch (type) {
case "loaded":
return { isLoading: false, error: "" };
case "error":
return { isLoading: false, error: payload };
case "reset":
return initialReducerState;
default:
return state;
}
};
export const AppContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(handleLoading, initialReducerState);
return (
<AppContext.Provider
value={{
...state,
dispatch
}}
>
{children}
</AppContext.Provider>
);
};
export const useAppContext = () => useContext(AppContext);
export default AppContextProvider;
Spinner.js
import React from "react";
const Spinner = () => <div className="loader">Loading...</div>;
export default Spinner;
fakeApi.js
const data = [{ id: "1", name: "Bob" }];
export const fakeApi = {
get: (status) =>
new Promise((resolve, reject) => {
setTimeout(() => {
status === 200
? resolve({ data })
: reject(new Error("Unable to locate data."));
}, 2000);
})
};
export default fakeApi;
index.js
import React from "react";
import ReactDOM from "react-dom";
import AppContextProvider from "./AppContext";
import App from "./App";
import "./styles.css";
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<AppContextProvider>
<App />
</AppContextProvider>
</React.StrictMode>,
rootElement
);

Categories

Resources