Getting undefined from state while using useEffect and useState - javascript

My issue here is that sometimes my projects state is returning undefined sometimes. I am not sure why. As you can see, in the useEffect I have a function that gets project data from an API call to my backend server. This will then return an array of projects, which I then planned to see in the dom in the return statement. However, for whatever reason, upon the initial render it gives me an undefined and the screen goes white.
Strangely, enough, if I change the return statement to just display a regular string, let's say "hello" for example, save, and then change it back to {projects[0].name} it will then work. Yet on initial render I am getting a Uncaught TypeError: Cannot read properties of undefined (reading 'name');
I will add that I am getting a 304 status from my server in the console but that is because the data has not changed and thus I am receiving the previous UI from local storage if I remember correctly. This is not an issue with other parts of my application so I do not know why it would be an issue here.
import { useEffect, useState } from "react";
import { fetchPage } from "./../store/actions"
import { connect } from "react-redux"
/*import { ProjectCard } from "./../components/ProjectCard"*/
import API from './../api/API'
const Projects = ({ fetchPage }) => {
const [projects, setProjects] = useState([])
useEffect(() => {
const getProjectData = async () => {
try {
const { data } = await API.getAllProjects()
setProjects(data.data)
} catch (err) {
console.log(err);
}
}
fetchPage('Projects', "Here are your projects")
getProjectData()
}, [fetchPage])
return (<div>
{projects[0].name}
</div>)
}
export default connect(null, { fetchPage })(Projects);
Here is a different part of my application that works more or less the same way
const [users, setUsers] = useState([])
useEffect(() => {
const getUserData = async () => {
const { data } = await axios.get('/api/v1/users', {
headers: {
'Content-type': 'application/json'
}
})
setUsers(data.data.data)
}
fetchPage("TEAM", "Here is your team");
getUserData();
}, [fetchPage])
I tried removing the action creator which I expected did not work

Related

Async function in UseEffect

I am trying to get data from AsyncStorage and eventually map this data to a list of buttons on my home screen. The data is saved to AsyncStorage from an API call that is made upon login.
In my async function, I am able to successfully retreive the data from AsyncStorage and parse it into JSON format, and then log it to the console. It looks like this:
{
1 : {title:"Timesheet",component_name:"Timesheet"}
2 : {title:"Personal Info",component_name:"PersonalInfo"}
3 : {title:"Employee Directory",component_name:"EmployeeListing"}
}
The problem I am running into is that I can't save this data to my useState variable and then render it into the component after useState is updated by my async function. Every time I try to access this data, I either get null or a Promise object. How can I access the data after useState is updated? Do I need to use a different React hook to call the Async function?
Here is the code that I am using:
import { Text, View, StyleSheet } from 'react-native';
import { useState, useEffect } from 'react';
import AsyncStorage from '#react-native-async-storage/async-storage';
export default function HomeScreen() {
const [buttonData, setButtonData] = useState(null);
useEffect (() => {
const loadHomeScreenButtons = async () => {
try {
const buttons = await AsyncStorage.getItem('app_screens').then(screens => {
// Parse the JSON data from its stored string format into an object.
let app_screens_json = JSON.parse(screens);
let app_screens_list = app_screens_json.app_screens;
console.log(app_screens_list); // This outputs the data to the console.
setButtonData(app_screens_list); // Trying to set the button data in useState.
return app_screens_list;
});
}
catch (e) {
console.log(e)
}
}
loadHomeScreenButtons();
}, [])
return (
<View style={home_styles.container}>
<Text>{buttonData[1]["title"]}</Text>
</View>
);
}
You just need to render a loading component until your data is fetched.
{ buttonData?.length?
<Text>{buttonData[1]["title"]}</Text> : <Text>...loading</Text>
}
You are getting an error as you are trying to access a property that does not exist at the render.
Try this way
const loadHomeScreenButtons = useCallback(async () => {
try {
const screens = await AsyncStorage.getItem('app_screens')
const app_screens_json = JSON.parse(screens)
setButtonData(app_screens_json.app_screens)
}
catch (e) {
console.log(e)
}
}, []);
useEffect(() => {
loadHomeScreenButtons();
}, [loadHomeScreenButtons]);

Custom API data fetching hook keeps rerendering

I've created a custom hook which I want to use to fetch data with. Now I've come a long way with some help from several blog articles, but there's just one thing I want to improve on. I have a custom hook which fetches data using a useEffect hook. This way the data is fetched upon render, and when for example query params change. Now the useEffect has a caveat. When I include a dependency array with anything in it, it's all fine, but I get a warning that the hook is dependent on a value. I don't like warnings so I add the value to the dependency array, but for some reason then it just keeps rerendering. Below is my useApi hook:
import { useState, useEffect } from "react";
import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
import api from "./config/api-config";
const useApi = (axiosParams: AxiosRequestConfig) => {
const [response, setResponse] = useState<AxiosResponse>();
const [error, setError] = useState<AxiosError>();
const [loading, setLoading] = useState(axiosParams.method === "GET");
const fetchData = async () => {
try {
const result = await api.request(axiosParams);
setResponse(result);
} catch (err: any) {
setError(err);
} finally {
setLoading(false);
}
};
useEffect(() => {
console.log("Render useApi");
axiosParams.method === "GET" && fetchData();
}, [axiosParams.method, fetchData]);
return { response, error, loading, fetchData };
};
export default useApi;
And this is where I'm using it:
import { FC, useState } from "react";
import { Wrapper } from "./home.style";
import { HomeProps } from "./home.types";
import useApi from "../../../api/useApi";
const Home: FC<HomeProps> = () => {
const [query, setQuery] = useState<String>();
const { response, loading, error, fetchData } = useApi({
method: "GET",
url: "/books/v1/volumes",
params: {
q: "",
},
});
return <Wrapper></Wrapper>;
};
export default Home;
I've tried using a callback hook for the fetchData function, but then the issue with the dependency array moves from the useEffect to the useCallback. Does anyone know how I should handle this?
To answer as an answer:
Infinite reexecution of useApi hook is happening due to the object passed as a parameter is recreated on each component rerendering.
const { ... } = useApi({
method: "GET",
url: "/books/v1/volumes",
params: {
q: "",
},
});
It will work fine in case a parameter is a plain string or a number. But in case of normal object or array, for example, you need to preserve a reference to them. You can either move this config out of functional component scope, i.e. just place it above it (if it is not meant to be modified), either preserve it with useMemo or useState hook.
// const useApiConfig = useMemo<AxiosRequestConfig>(() => {
const useApiConfig = useMemo(() => {
return {
method: "GET",
url: "/books/v1/volumes",
params: { q: "" }
};
}, [])
/* ... */
const { response, loading, error, fetchData } = useApi(useApiConfig);
Additionaly, due to fetchData is in depsArray of useEffect hook - it is important to wrap fetchData into useCallback due to without it fetchData will be recreated on each rerender and useEffect will be triggered. And due to reexport of the fetchData from the hook - component that is using it will also have an issues with rerender.
Usually (when reexport is not needed) method like fetchData is just placed inside of the useEffech hook itself (as a const function).
const useApi = (axiosParams: AxiosRequestConfig) => {
const [response, setResponse] = useState<AxiosResponse>();
const [error, setError] = useState<AxiosError>();
const [loading, setLoading] = useState(axiosParams.method === "GET");
const fetchData = useCallback(async () => {
try {
const result = await api.request(axiosParams);
setResponse(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}, [axiosParams]);
useEffect(() => {
console.log("Render useApi");
axiosParams.method === "GET" && fetchData();
}, [axiosParams.method, fetchData]);
return { response, error, loading, fetchData };
};

React native, state not defined in useSelector after fetch

I am trying to load data for my component in useEffect. How can I ensure the state is defined before I go to the next screen?
const [rescueMeContent, setRescueMeContent] = useState({});
useEffect(() => {
async function fetchData() {
const { payload } = await get('xxx');
setRescueMeContent(payload);
}
fetchData();
});
When I first run the app, on the next screen when I try and access the state like this I get an error saying its undefined:
const {
content: {
splashScreen: {
backgroundImage: { url },
buttonText,
informationText,
title: pageTitle,
},
},
} = useSelector((state) => state);
useState works in inside only component. You can't reach that from other component. You must use react redux for to use useSelector. You must assign your fetch data to redux state with action. Then you can get that data with useSelector.

Axios request keeps returning twice undefined and twice the data

I'm trying to fetch an api on a custom reactjs hook using Axios. I keep getting twice the response as undefined and after that twice as a successful fetch with the data. The undefined breaks my app.
Btw I'm fetching from the randomuser api.
import axios from "axios";
import { useState, useEffect } from "react"
export const useFetch = (url) => {
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState('')
const getData = () => {
setLoading(true)
try {
axios.get(url)
.then(response => setData(response.data));
setLoading(false)
} catch (error) {
setError(error)
}
};
useEffect(() => {
getData()
}, [url])
return {loading, data, error}
}
Trying to use it here and map over it
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useFetch } from '../custom_hooks/useFetch';
const PersonDetails = () => {
const { loading, data , error } = useFetch('https://randomuser.me/api?results=20');
const { results } = data;
const { id } = useParams();
const [person, setPerson] = useState({})
useEffect(() => {
const newPerson = results?.find(person => person.login.uuid === parseInt(id))
setPerson(newPerson)
console.log(newPerson)
}, [])
return (
<div>
{person.name.first}
</div>
)
}
export default PersonDetails
This is the thing I actually Im trying to do, but now because it is undefined, I get that cannot read properties of undefined...
When the effect runs you:
setLoading(true)
Send the Ajax request
setLoading(false)
Later, then the Ajax response arrives you:
setData(response.data)
Since you depend on loading to determine if data is set or not, it breaks.
There are two things you could do:
Move setLoading(false) inside the then callback so it doesn't get set until after you have setData(response.data)
Get rid of loading entirely and base your logic off data being undefined or having a different value.
you should define the getData function inside the useeffect or pass it in dependency array and wrap the function by usecallback to avoid unnecessary rerenders.
you should use abortcontroller in case of cancelations and to have cleanup function in useeffect. (in this case it's better to define getdata body in useeffect)
useEffect(() => {
const controller = new AbortController();
const getData = async () => {
setLoading(true)
try {
await axios.get(url, {signal: controller.signal})
.then(response => setData(response.data));
} catch (error) {
setError(error)
}
}
getData()
return()=>controller.abort()
},[url]}
you can read more about fetching data with hooks in following url and where to setloading and other needed states.
https://www.robinwieruch.de/react-hooks-fetch-data/
Just in case, this solution helped me : https://github.com/axios/axios/issues/2825#issuecomment-883635938
"The problem in my case was caused by React development server.
The strict mode in react caused the issue!
I had to remove the strict mode
This solved the problem of sending double requests!
The strict mode checks are only run in development mode.
Doc: https://reactjs.org/docs/strict-mode.html
"

How to fetch repeatedly and render updated api data in reactjs?

I am trying to render time data from API endpoint http://worldclockapi.com/api/json/utc/now
The "currentFileTime" property is constantly changing but renders once on load.
I tried setInterval method to update state but it doesn't work. May be I am making some mistake?
This is App.js:
import React , { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = { data: []};
}
async componentDidMount(){
this.fetchData();
}
async fetchData() {
try {
const response = await fetch('http://worldclockapi.com/api/json/utc/now');
if (!response.ok) {throw Error(response.statusText);}
const json = await response.json();
this.setState({ data: json});
console.log(json);
}
catch (error) {console.log(error);}
}
render() {return (<div><h1>worldclockapi.com data (edit App.js)</h1>
<li>currentFileTime: {this.state.data.currentFileTime }</li>
</div> );
}
}
export default App;
How to render and update currentFileTime continuously in react component?
the problem is componentDidMount executed only once, after component mounted for the first time, for example if your state changes componentDidMount is not gonna execute again.
in your case i think it's better to use websockets but if u wanna keep useing this api u can use useEffect hook like below:
const [temp, setTemp] = useState(0)
useEffect(()=>{
setIterval(()=>{
setTemp((prevTemp)=>prevTemp+1)
}, 2000)
}, [])
useEffect(()=>{
fetchData()
}, [temp])
in the above code we have a temp variable and it's value update every 2 second and every time it gets updated the second useEffect run and the data will fetch and as a result the state's gonna change and element gets updated.
Try Calling fecthData recursively upon successful data retrieval like below.
And you don't need to put "async" in front of componentDidMount cause you ain't awaiting anything in the method call.
async fetchData() {
try {
const response = await fetch('http://worldclockapi.com/api/json/utc/now');
if (!response.ok) {throw Error(response.statusText);}
const json = await response.json();
this.setState({ data: json});
console.log(json);
// set the time below to how frequently you wanna update
setTimeout(() => this.fetchData(), 5000);
//
}
catch (error) {console.log(error);}
}
This is using the new hooks. This should solve the problems
import React, {useEffect, useState} from 'react';
import logo from './logo.svg';
import './App.css';
const App = () => {
const [state, setState] = useState({data: []});
useEffect(()=>{
fetchData();
}, [state]);
const fetchData = async () => {
try {
const response = await fetch('http://worldclockapi.com/api/json/utc/now');
if (!response.ok) {throw Error(response.statusText);}
const json = await response.json();
setState({ data: json});
console.log(json);
}
catch (error) {console.log(error);}
}
return (<div><h1>worldclockapi.com data (edit App.js)</h1>
<li>currentFileTime: {state.data.currentFileTime }</li>
</div> );
}
export default App;

Categories

Resources