Data from function is null on first load - reactjs

I'm using a function from another file to get data from a Firebase. However, the first time I load the app, I don't get the data. Data pulls it out fine. When I write to the console right after adding it to a variable, I see them. However, Return does not return them and returns only an empty field.
If I edit and save the file after loading, the application is refreshed and the data is loaded. Is this solution correct? Then I want to have more functions there.
const Page = () => {
const [categories, setCategories] = useState([]);
const fetchMainCategories = async () => {
const results = placesB.getMainCategories();
setCategories(results);
};
useEffect(() => {
fetchMainCategories();
}, []);
}
export default Page;
class Categories {
getMainCategories(){
let val = [];
const reference = query(ref(db, 'categories/1/'));
onValue(reference, (snapshot) => {
val = snapshot.val();
console.log(val); // It always lists the data here
});
return val; // HERE IS THE PROBLEM. On the first load is this empty!
}
}
const cats = new Categories();
export default cats;
Is here anyone who can help me please?

The onValue() returns data asynchronously and hence the array is always returned empty. Since you need to fetch data only once, use get() instead of onValue():
class Categories {
// async
async getMainCategories() {
const reference = query(ref(db, 'categories/1/'));
const snap = await get(reference);
return snap.val();
}
}
Then you call this method as shown below:
const fetchMainCategories = async () => {
// await here
const results = await placesB.getMainCategories();
console.log(results);
setCategories(results);
};

Try to put a condition, as if(val) then return val....

Related

How to display custom Nextjs error page when api call fails?

I created a custom Nextjs error page that I would like to display when the api call fails. What is currently happening is even if the api call fails, it still displays the same page as a successful route. For example, I have a route that is companies/neimans that pulls data from an api to display certain text on the page. If I type, companies/neiman I want my custom error page to show, but it is displaying the same page as if going to companies/neimans just without the data coming from the api. I do get a 404 in the console when visiting a url that is invalid but it doesn't display the custom error page or the default next js 404 page.
In my file system I have a pages directory and inside that a directory called companies with a file [companydata].tsx and one called 404.tsx. [companydata].tsx is the page that dynamically displays information about the company from the api.
This is what my api call currently looks like:
export const getCompanies = async (routeData: string): Promise<Company> => {
const client = getApiClient();
const response = await client.get<Company>(`api/companies/${routeData}`);
if (response) {
return response.data;
}
return {} as Company;
In the [companydata].tsx, I tried to do a check if the object was empty to then redirect to companies/404 but doing so makes it always display the 404 page.
if (Object.keys(company).length === 0) {
return <Redirect to="/company/404"/>;
}
If I console.log the company, it is rendering multiple times. The first 6 times, it is an empty array so that would explain why the 404 page is always showing. The data doesn't come through until after the 6th render. I am not sure why that is.
I am calling getCompanies inside another function,
export const getData = async (companyName: string): Promise<[Company, Sales]> => {
if (companyName) {
return (await Promise.all([getCompanies(companyName), getSales()])) as [
Company,
Sales
];
}
return [{} as Company, {} as Sales];
};
I am calling getData inside a useEffect within [companydata].tsx.
const Company: NextPage = (): JSX.Element => {
const [selectedCompany, setSelectedCompany] = useState<Company>({} as Company);
const [salesAvailable, setSalesAvailable] = useState<boolean>(false);
const [sales, setSales] = useState<Sales>({} as Sales);
const router = useRouter();
const {companydata} = router.query;
useEffect(() => {
const init = async (companyName: string) => {
const [companyData, salesData] = await getData(companyName);
if (companyData) {
setSelectedCompany(companyData);
}
if (salesData) {
setSalesAvailable(true);
setSales(salesData);
} else {
setSalesAvailable(false);
}
}
};
init(companydata as string);
};
}, [companydata]);
// returning company page here
You currently do not have a method to check the status of the API call. There are four possible outcomes of most API calls - data, no data, error, and loading. You should add the status checks in your API calls
Below are two examples of how this can be achieved.
get companies hook
export const useGetCompanies = async (path: string) => {
const [data, setData] = useState<Company>();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
try {
setError(false);
setLoading(true);
const client = getApiClient();
const response = await client.get(`api/companies/${path}`);
setData(response.data);
} catch (error) {
setError(true);
} finally {
setLoading(false);
}
return {data, error, loading};
};
Since your data isn't related you also do a generic API fetch call something like
export async function useFetchData<T>(path:string){
const [data, setData] = useState<T>();
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
try {
setError(false);
setLoading(true);
const client = getAPIClient();
const response = await client.get<{ data: T }>(path);
if(response) setData(response.data);
} catch (error) {
setError(true);
} finally {
setLoading(false);
}
return { data, error, loading };
};
Example use.
const Company = async () =>{
const { query } = useRouter();
const company = await useFetchData<Company>(`api/companies/${query.companydata}`);
const sales = await useFetchData<Sales>(`api/companies/${query.companydata}/sales`);
if (company.loading || sales.loading) return <p>Loading...</p>;
if (company.error || sales.error) return <p>Error or could show a not found</p>;
if (!company.data || !sales.data) return <Redirect to="/company/404"/>;
return "your page";
}
It would be best to render the data independently of each other on the page and do the if checks there. This is beneficial because you don't have to wait for both calls to complete before showing the page.
I'd create two separate components (company and sales) and place the corresponding API call in each.
Typically assigning empty objects ({} as Company or {} as Sales) to defined types is bad practice because it makes TS think the object's values are defined when they are not - defeating the purpose of using TS.
They should be left undefined, and there should be a check to see if they are defined.
Lastly, I can't test the code because I don't have access to the original code base so there might be bugs, but you should get a pretty good idea of what's happening.

How to use API Route in next js?

I am learning how to design API and at the same time how to use next.js API route.
I have set my first route api/property/[propertyId] that returns the specific property detail.
Now I am trying to set a dynamic route for the specific property id in the page folder page/property/[propertyId]. My issue is when I am getting directed on the specific page the data is not there as expected. I am receiving a response for error message.
Can someone point out what I did wrong, please?
pages>api>property>[propertyId]
export default function propertyHandler ({query : {propertyId} } ,res) {
var parser = new xml2js.Parser({explicitArray : false});
const data = fs.readFileSync(path.join(process.cwd(),'listing.xml'))
parser.parseString(data,function (err, results){
results = results.client.secondhandListing.property
const filteredProp = results.filter((property) => property.id === propertyId)
filteredProp.length > 0 ? res.status(200).json(filteredProp[0]) : res.status(404).json({message: `Property with id: ${propertyId} not found.` })
})
}
pages>property>[id].js
export const getDetails = async() => {
const res = await fetch(`${baseUrl}/api/property/[property.Id]}`)
const data = res.json()
return data
}
export async function getServerSideProps({params: { id } }) {
const data = await getDetails(`${baseUrl}/api/property/${id}`)
return {
props: {
propertyDetails: data
}
}
}
I got the answer to my mistake from somewhere else. It was my getdetails function that was wrong.
I have amended it to:
export const getDetails = async(baseUrl)=>{
const res = await fetch(baseUrl)
const data = await res.json()
return data
};
and it worked.

React component does't display options after setting state, firestore returning empty array with object in it? I'm confused

Have this problem going on where I fetch data from firestore, then setState with that data, for some reason my component won't re render to then display that data. It'll just be blank. fetchData is my firebase function, it returns an empty array which my console is showing as empty, but there's objects in there which is confusing.
photo of empty array with objects from firestore
const dataFetching = (data) => {
const x = fetchData(data);
setData(x);
};
// fetchData firestore function
export const fetchData = (applicants) => {
const applicantData = [];
for (let i = 0; i < applicants.length; i++) {
const x = firestore.collection("users").doc(applicants[i]).get();
x.then((user) => applicantData.push(user.data()));
}
return applicantData;
};
firestore.collection("users").doc(applicants[i]).get() returns a promise that doesn't get awaited. The for loop therefore finishes iterating before the promises get resolved. At the time of the return applicantData; the array is still empty so setData is receiving an empty array.
I would change the fetchData function to something like this:
export const fetchData = async (applicants) => {
const applicantData = [];
for (let i = 0; i < applicants.length; i++) {
const user = await firestore.collection("users").doc(applicants[i]).get();
applicantData.push(user.data())
}
return applicantData;
};
And the dataFetching function to something like this:
const dataFetching = async (data) => {
const x = await fetchData(data);
setData(x);
};

How to display all successful and unsuccessful request to an API in Next.js?

I have an input in which I write the names of the characters through a comma Ricky, Marty, etc.
Accordingly, on each of the heroes, I make requests in a database and show results.
How do I display a list of successful and unsuccessful requests if the hero is not found?
export const getServerSideProps: GetServerSideProps = async (context) => {
const { name } = context.query;
const nameArray = (name as string).split(',');
const allRequest = nameArray.map((el) => axios.get(`https://rickandmortyapi.com/api/character/?name=${el}`));
const charactersList = await axios.all(allRequest)
.then(axios.spread((...response) => response.map((e) => e.data.results)));
return ({
props: {
charactersList,
},
});
};
With this code, I just get the data from the database. And I need it
Ricky (data from input) --- data from database Morty (data from input --- data from database)
, etc and the list of which was not found.
You probably want to use Promise.allSettled() to wait for all promises to either resolve or reject (and avoid rejecting everything if one of them rejects).
export const getServerSideProps: GetServerSideProps = async (context) => {
const { name } = context.query;
const nameArray = Array.isArray(name) ? name : [name];
const allRequest = nameArray.map((el) =>
axios.get(`https://rickandmortyapi.com/api/character/?name=${el}`)
);
const charactersList = await Promise.allSettled(allRequest).then((res) => {
// Iterate over all results, both successful or unsuccessful
return res.map((result) => {
// Returns response data if successful, or `undefined` otherwise
// Handle this however you like
return result.value?.data.results;
});
});
//...
}
Note that you should avoid using axios.all/axios.spread as they've been deprecated.

String useState Value Is Empty after Storing a Value in an API Response to It

What I am trying to do here is to extract the id (Number) from an API response, parse it into String, and set it to its state variable. However, when I console log it, it gives me an empty string, which is the default value of it. If anyone could give me a suggestion or tell me what's wrong, that would be greatly appreciated. My briefly reproduced code snippet is down below:
const [userId, setUserId] = useState<string>("");
const getMyUserId = async () => {
const { data: objects } = await axios.get("/");
const user_id = objects.objects[0].id.toString();
setUserId(user_id);
console.log("userId", userId); <- output is empty string
};
const getMyCalendarId = async () => {
const url = `/${userId}/cal/calendars`;
const { data: objects } = await axios.get(`${url}`);
const calendar_id = objects.objects[0].id.toString();
setCalendarId(calendar_id);
};
useEffect(() => {
getMyUserId(); <- render every time page is loaded
getMyCalendarId
}, []);
To retrieve the user id you should access data, instead of objects. Like this:
const user_id = data.objects[0].id.toString();
objects is the typing of data, it is not the actual property.

Resources