2 axios request problem rendering before geting data from request - reactjs

I am building a weather app similar to AccuWeather in React.js.
I have a problem with axios.get request. I need to get data from 2 URLs. The second request (forecast) is bigger. Because of this when I want to get given city weather forecast in a certain parts of the app, DOM is rendered before I get data from Axios request.
I was trying to use async and await in my code but without success. Below you can find function getCityWeather which contain those 2 Axios request. Any idea?
getCityWeather = event => {
axios
.get(
"https://api.openweathermap.org/data/2.5/weather?q=" +
this.state.city +
"&units=metric&appid=" +
this.state.appiID
)
.then(weather => {
console.log(weather);
this.setState({
currentCityWeather: weather.data.weather,
currentCityName: weather.data.name,
currentCityCountry: weather.data.sys.country,
CityWeatherMain: weather.data.main,
CityWeatherWind: weather.data.wind,
CityWeatherClound: weather.data.clouds,
cityLoaded: true
});
})
.catch(error => {
console.log(error);
});
axios
.get(
"https://api.openweathermap.org/data/2.5/forecast?q=" +
this.state.city +
"&units=metric&appid=" +
this.state.appiID
)
.then(forecast => {
console.log(forecast);
this.setState({
CityForecastDataList: forecast.data.list,
cityLoaded: true
});
})
.catch(error => {
console.log(error);
});
event.preventDefault();
};

Try code bellow.
const baseUrl = 'https://api.openweathermap.org/data/2.5';
const getCityWeather = (event) => {
event.preventDefault();
const { city, appiID } = this.state;
const urls = [
`${baseUrl}/weather?q=${city}&units=metric&appid=${appiID}`,
`${baseUrl}/forecast?q=${city}&units=metric&appid=${appiID}`,
];
const promises = urls.map(s => axios.get(s));
Promise.all(promises)
.then(([weatherResponse, forecastResponse]) => {
const {
date: {
weather,
name,
sys: { country },
main,
wind,
clouds,
},
} = weatherResponse;
const {
data: { list },
} = forecastResponse;
this.setState({
currentCityWeather: weather,
currentCityName: name,
currentCityCountry: country,
CityWeatherMain: main,
CityWeatherWind: wind,
CityWeatherClound: clouds,
CityForecastDataList: list,
cityLoaded: true,
});
})
.catch(({ message }) => {
console.error(message);
});
};

Thanks for answer Kirill,
I tried to implement your code but get
ReferenceError: Cannot access 'promises' before initialization
getCityWeather
src/containers/WeatherData/WeatherData.js:39
36 | `${baseUrl}/weather?q=${city}&units=metric&appid=${appiID}`,
37 | `${baseUrl}/forecast?q=${city}&units=metric&appid=${appiID}`
38 | ];
> 39 | const promises = urls.map(s => axios.get(s),
| ^ 40 |
41 | Promise.all(promises)
42 | .then(([weatherResponse, forecastResponse]) => {
Below please find my implementation to class based component which I use
getCityWeather = (event) => {
event.preventDefault();
const baseUrl = 'https://api.openweathermap.org/data/2.5';
const { city, appiID } = this.state;
const urls = [
`${baseUrl}/weather?q=${city}&units=metric&appid=${appiID}`,
`${baseUrl}/forecast?q=${city}&units=metric&appid=${appiID}`
];
const promises = urls.map(s => axios.get(s));
Promise.all(promises)
.then(([weatherResponse, forecastResponse]) => {
const {
date: {
weather,
name,
sys: { country },
main,
wind,
clouds
},
} = weatherResponse;
const {
data: { list },
} = forecastResponse;
this.setState({
currentCityWeather: weather,
currentCityName: name,
currentCityCountry: country,
CityWeatherMain: main,
CityWeatherWind: wind,
CityWeatherClound: clouds,
CityForecastDataList: list,
cityLoaded: true,
});
})
.catch(({ message }) => {
console.error(message);
})
)}

Related

Socket works the for a few seconds then fails

I've managed to establish a connection using socket. It works great for the first few seconds after that it runs super slow takes like almost 2-3 mins to complete a request. And then it produces this error continuously. The app doesn't crash it just runs slowly with the error displaying countless times.
Firefox can’t establish a connection to the server at ws://localhost:5000/socket.io/?EIO=4&transport=websocket&sid=9S6kqHJdHHXQgrobAAHp..
Error on console.
Main.js
function Home(props) {
const [Username , setUsername] = useState("")
const [SearchedData, setSearchedData] = useState()
const [Data, setData] = useState()
const socket = io('http://localhost:5000')
React.useEffect(() => {
// socket.current = io('http://localhost:5000')
socket.emit("content","yada")
socket.on("get-data", data => {
setData(data)
})
})
function NavBar(props){
const handleClick = (e) => {
const {id} = e.target
if(id === "Post-btn"){
if(Content.length > 0){
let data = {
Username: "yada", Content
}
props.socket.emit("store-data", data)
}
}
return(
Tags....
)}
function Content (props) {
const onLike = (e) => {
const { id } = e.target.dataset
const data = {
username: "yada",
id : id
}
// console.log(data)
props.socket.emit("like", data)
}
return(
Tags.....
)
}
server.js
mongoose.connect(process.env.MongoDB,
{ useNewUrlParser: true, useUnifiedTopology: true }).then(() => {
console.log("Database Connected")
}).catch(err => {
console.log(err)
});
const server = app.listen(process.env.Port, () => {
console.log("Connected on " + process.env.Port)
})
const io = socket(server, {
cors:{
origin: "http://localhost:3000",
credential: true,
}
})
let cuid;
io.on("connection", (socket) => {
socket.on("content", username => {
Comments.find({},(err, data) => {
if(!err)
socket.emit("get-data", data)
})
})
socket.on("store-data", data => {
const {Username, Content} = data
const newdata = new Comments({
userName: Username,
content: Content,
createdAt: new Date().toDateString(),
replies: []
})
newdata.save().then(data => {
for(const d in data)
if(d === "_id"){
Users.findOneAndUpdate({username: Username}, {$push: {UserContent: data[d]}}, {new: true}, (err, save) => {
if(err)
console.log(err)
else
console.log(save)
})
}
})
})
socket.on("like", data => {
const {username, id} = data
Users.findOne({username:username}, (err, data) => {
if(!err){
cuid = data['id']
console.log(cuid)
Comments.findByIdAndUpdate(id, {$set: {score: data['_id']}}, {upsert: true}, (err, d) => {
if(!err){
console.log(d)
}
})
}
})
})
})
Looking at the code provided, I noticed there is an useEffect without params. This may be causing a loop until the application crashes.
React.useEffect(() => {
// socket.current = io('http://localhost:5000')
socket.emit("content","yada")
socket.on("get-data", data => {
setData(data)
})
socket.on("Updated", data => {
setData(data)
})
}, []); <- this is missing
This empty array indicates that the content inside the useEffect will only run once.
More about this https://reactjs.org/docs/hooks-intro.html

Mocked axios "TypeError: Cannot read properties of undefined (reading 'then')"

I developing my final project using React with TypeScript and I'm trying to test a mocked response using Jest but I just get this error:
TypeError: Cannot read properties of undefined (reading 'then')
25 | }
26 |
> 27 | axios(requestConfig)
| ^
28 | .then(res => res.data)
29 | .then(data => {
30 | if(mounted) {
This is the code of the testing:
jest.mock('axios');
describe("MyMethodsPage tests", () => {
test("Methods loaded correctly", async () => {
const mockedAxios = axios as jest.Mocked<typeof axios>;
const mockedResponse: AxiosResponse = {
data: [mockedMethodsList[0]],
status: 200,
headers: {},
config: {},
statusText: 'OK'
};
mockedAxios.get.mockResolvedValueOnce(mockedResponse);
render(
<BrowserRouter>
<MyMethodsPage />
</BrowserRouter>
);
// Before fetching
expect(screen.getByText(/Mis métodos/)).toBeInTheDocument();
expect(screen.getByText(/Cargando.../)).toBeInTheDocument();
// After fetching
expect(await screen.findByText(/Test/)).toBeInTheDocument();
});
});
And this is the part of the code where the error appears:
export function useAuthFetch<T>(url: string, token: string, method: Method = "get", body?: T) {
const [data, setData] = useState<T>();
const [isPending, setPending] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
let mounted = true;
let requestConfig: AxiosRequestConfig = {
headers: {
Authorization: `Bearer ${token}`
},
method: method,
url: `${process.env.REACT_APP_API_URL}/${url}`,
data: body
}
axios(requestConfig)
.then(res => res.data)
.then(data => {
if(mounted) {
setData(data);
setError("");
setPending(false);
}
})
.catch(error => {
if(mounted) {
if(axios.isAxiosError(error)) {
setPending(false);
setError(error.response?.data.detail);
} else {
setPending(false);
setError('Algo ha ido mal, inténtelo de nuevo más tarde');
}
}
})
return function cleanup() {
mounted = false;
};
}, [body, method, token, url]);
return {data, isPending, error}
}
If I do not add jest.mock and all the mocked part it works but I would like to not be calling the API while testing. Also it works in the "normal" environment, it is just in this case when it fails. I have tried adding a auth token manually but it still fails so I just have run out of ideas.
I have more tests with the same structure and all of them pass.
If you need more information I would give it to you (this is my first question in stackoverflow), thank you for your help.

pass dynamic values in API params coming from navigation in react native

I got following 2 values in id and company variable by navigating the screen.
useEffect(() => {
if (props.route && props.route.params) {
console.log("id-->", props.route.params.oved);
console.log("company-->", props.route.params.company);
}
});
e,g i got 2 values like this
id--> 31
company--> 465
I want to pass the id and company value in API params.
api.js : -
const AllFormCardAPI = () => {
const [formAllAPIData, setAllFormAPIData] = useState("");
//NOTE: retrieving loginAuthToken from store
const loginAuthToken = useSelector(
(state) => state.loginAuthTokenReducer.loginAuthToken
);
useEffect(() => {
axios
.get(GET_ALL_FORM, {
//TODO: take parameters from user data currently parameters are static
params: {
company: "984",
employee: "38887683",
DisplayRow: "123456",
},
headers: {
Authorization: `Bearer ${loginAuthToken}`,
},
})
.then((response) => response.data)
.then((data) => setAllFormAPIData(data))
.catch((error) => {
if (error.status === 401) {
//NOTE: handling token expire
return ExpireAlertRestart();
} else {
Alert.alert(error.message);
}
})
.finally(() => console.log("finally block all form api", formAllAPIData));
}, []);
};
i want to pass those 2 values i,e id and company from navigation which I mentioned above and those has to be passed as string to following in API params.
My new API params should look like this. The id value should replace in employee params and company value should replace in company params.
params: {
company: "465",
employee: "31",
action.js:--
import { CHANGE_SELECTED_COMPANY } from "./action-constants";
export const changeCompany = (updatedCompany, updatedId) => {
return {
type: CHANGE_SELECTED_COMPANY,
updatedCompany,
updatedId,
};
};
reducer.js:--
import { CHANGE_SELECTED_COMPANY } from "../actions/action-constants";
const initialState = {
company: "",
id: "",
};
const changeCompanyReducer = (state = initialState, action) => {
switch (action.type) {
case CHANGE_SELECTED_COMPANY:
return {
company: {
company: action.updatedCompany,
id: action.updatedId,
},
};
}
return state;
};
export default changeCompanyReducer;
congigure-store.js:--
import changeCompanyReducer from "./reducers/change-company-reducer";
const rootReducer = combineReducers({changeCompanyReducer});
How can i store the update values getting from navigation in Redux?
could you please write code for redux??
const AllFormCardAPI = (props) => {
//New lines
const id = props?.route?.params?.oved;
const company = props?.route?.params?.company;
//New lines end
const [formAllAPIData, setAllFormAPIData] = useState("");
//NOTE: retrieving loginAuthToken from store
const loginAuthToken = useSelector(
(state) => state.loginAuthTokenReducer.loginAuthToken
);
useEffect(() => {
axios
.get(GET_ALL_FORM, {
//TODO: take parameters from user data currently parameters are static
params: {
company: company,
employee: id,
DisplayRow: "123456",
},
headers: {
Authorization: `Bearer ${loginAuthToken}`,
},
})
.then((response) => response.data)
.then((data) => setAllFormAPIData(data))
.catch((error) => {
if (error.status === 401) {
//NOTE: handling token expire
return ExpireAlertRestart();
} else {
Alert.alert(error.message);
}
})
.finally(() => console.log("finally block all form api", formAllAPIData));
}, []);
};

Reactjs - Firebase : Cancel Old Requests

I'm new to Firebase Realtime Database, and i'm trying to implement a search field that allow users to search for other users and view their profiles.
The Problem Is:
I want to make the search realTime(on each input change).but whenever a new request's sent, the old request is still working in the backend which's causing unexpected behavior,i've wrapped this functionality in a useEffect Hook,old sideEffects has to be cleaned up to make the query results predictable,how can i abort the previous request.
useSearchOwner Custom Hook:
const useSearchOwner = () => {
const [{ SearchValue, SearchResult, Search }, dispatch] = useReducer(
reducer,
{
SearchValue: "",
SearchResult: "",
Search: false,
}
);
const isFirstRender = useRef(true);
const onChangeHandler = (e) =>
dispatch({
type: ACTIONS.UPDATE_SEARCH_VALUE,
payload: { searchValue: e.target.value },
});
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
dispatch({ type: ACTIONS.START_SEARCHING });
const DispatchQueryByResult = async () => {
const ArrayOfOwners = await FirebaseUtilityInstance.SearchOwnerResult(
SearchValue
);
dispatch({
type: ACTIONS.UPDATE_SEARCH_RESULT,
payload: { searchResult: ArrayOfOwners },
});
dispatch({ type: ACTIONS.STOP_SEARCHING });
return () => {
FirebaseUtilityInstance.SearchOwnerCleanup();
};
};
DispatchQueryByResult();
}, [SearchValue]);
useEffect(() => {
console.log(SearchResult);
}, [SearchResult]);
return {
onChangeHandler: onChangeHandler,
Query: SearchValue,
QueryResult: SearchResult,
isSearching: Search,
};
};
Firebase Method To Do Query:
SearchOwnerResult = async (Query) => {
const { firstName, lastName } = getFirstNameAndLastName(Query);
let ArrayOfOwners = [];
await this.Database()
.ref("users")
.orderByChild("UserType")
.equalTo("owner")
.once("value", (snapshot) => {
const OwnersContainer = snapshot.val();
const keys = Object.keys(OwnersContainer);
for (let i = 0; i < keys.length; i++) {
const CurrentOwner = OwnersContainer[keys[i]];
if (
CurrentOwner.FirstName === firstName ||
CurrentOwner.LastName === lastName
) {
ArrayOfOwners.push(OwnersContainer[keys[i]]);
}
}
});
return ArrayOfOwners;
};

How to make Async Await Function in React Native?

I want to create a function that is about uploading photo to Firebase Storage with react-native-fetch-blob. I'm using Redux and you can find action functions below:
My problem is that uploadImage function is not running like asynchronous. Firebase function is running before uploadImage, so application give me an error.
I think i can't make a asynchronous function. How can i fix it ?
uploadImage() function:
const uploadImage = async (imageSource, whereToUpload) => {
let imageURL = '';
const mime = 'image/jpg';
const { Blob } = RNFetchBlob.polyfill;
const { fs } = RNFetchBlob;
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
window.Blob = Blob;
console.log('URI =>', imageSource.uri);
let imgUri = imageSource.uri;
let uploadBlob = null;
const imageRef = firebase.storage().ref(whereToUpload + '/' + imageSource.fileName);
const uploadUri = Platform.OS === 'ios' ? imgUri.replace('file://', '') : imgUri;
await fs.readFile(uploadUri, 'base64')
.then((data) => Blob.build(data, { type: `${mime};BASE64` }))
.then((blob) => {
uploadBlob = blob;
return imageRef.put(blob, { contentType: mime });
})
.then(() => {
uploadBlob.close();
// eslint-disable-next-line no-return-assign
return imageURL = imageRef.getDownloadURL();
})
.catch((error) => {
console.log(error);
});
return imageURL;
};
and the main action is:
export const addProjectGroup = (
myUser,
groupName,
groupDescription,
groupProfilePic,
) => dispatch => {
const groupProfileFinalPic = async () => {
let finalGroupPicture = { landscape: '' };
if (_.isEmpty(groupProfilePic.src)) {
await uploadImage(groupProfilePic, 'groupPictures').then((imageURL) => {
console.log('İŞLEM TAMAM!');
console.log('SELECTED IMAGE URL =>', imageURL);
finalGroupPicture.landscape = imageURL;
});
} else {
finalGroupPicture.landscape = groupProfilePic.src.landscape;
}
return finalGroupPicture;
};
console.log("final group profile pic =>", groupProfileFinalPic());
// Önce grubu yaratalım..
// eslint-disable-next-line prefer-destructuring
const key = firebase
.database()
.ref()
.child('groups')
.push().key;
firebase
.database()
.ref('/groups/' + key)
.set({
admin: {
email: myUser.email,
name: myUser.name,
uid: myUser.uid,
},
groupName,
groupDescription,
groupProfilePic: groupProfileFinalPic(),
projects: '',
})
.then(() => {
console.log('Groups oluşturuldu.');
})
.catch(e => {
Alert.alert('Hata', 'Beklenmedik bir hata meydana geldi.');
console.log(e.message);
});
dispatch({
type: ADD_PROJECT_GROUP,
});
};
You are not awaiting groupProfileFinalPic(). This should be done before creating the action you want to dispatch.
groupProfileFinalPic().then(groupProfilePic => {
return firebase
.database()
.ref("/groups/" + key)
.set({
admin: {
email: myUser.email,
name: myUser.name,
uid: myUser.uid
},
groupName,
groupDescription,
groupProfilePic,
projects: ""
})
.then(() => {
console.log("Groups oluşturuldu.");
})
.catch(e => {
Alert.alert("Hata", "Beklenmedik bir hata meydana geldi.");
console.log(e.message);
});
});
I have no clue what the last dispatch is for, you might want to do that in one of the callbacks. Your code is to verbose for an SO question, but I hope this helps anyways.
You are using both await and then on the same call. To use await, you can arrange it something like
const uploadImage = async (imageSource, whereToUpload) => {
...
try {
let data = await RNFS.fs.readFile(uploadUri, 'base64')
let uploadBlob = await Blob.build(data, { type: `${mime};BASE64` }))
...etc...
return finalResult
catch (e) {
// handle error
}
}

Resources