How can I fetch data from Firebase into my React App - javascript

Please, I am very new to React.js and having this challenge of fetching dating from Firebase to populate this. I want to be able to fetch a single properties and not all of them - eg (title, body and price).
useFetch is a custom hook I created inside the project to handle the fetch from firebase.
What am I not getting right here please?
import { useParams } from 'react-router-dom';
import useFetch from '../UseFetch';
import '../courseDetails/CourseDetails.css';
import { useState, useEffect } from 'react';
import React from 'react'
const CourseDetails = () => {
const { id } = useParams();
const { data:details, isLoading } = useFetch('https://tutorialwebsite-460f1-default-rtdb.firebaseio.com/courses.json');
console.log(details);
return (
<div className="course-detail">
<h2>{ id }</h2>
<div>
<p>{details.description}</p>
</div>
</div>
)
}
export default CourseDetails
Here's the object loaded from the API:
{
"-MaebRkqKfjLG8heBbSu": {
"body": "qwerqwer",
"description": "wqerqwetqwe",
"imageUrl": "wdfwerw",
"price": "werqwertwer",
"title": "title 1"
}
}
How can I access the description?

Thank you everyone for your response but I was able to solve my problem. So what I did was changed into a used fake json api instead of firebase I was initially using. Then I was able to dynamically route to a specific page based of the unique id after fetching the data from firebase and then used conditional rendering to output the data on the page. However, I would have loved to know how to do this using using firebase.
const CourseDetails = () => {
const { id } = useParams();
const { data:details, isLoading } = useFetch('http://localhost:8000/courses/' + id);
console.log(details)
return (
<div className="course-detail">
{isLoading && <div>Loading...</div>}
{details && <div>{details.body}</div> }
</div>
)
}
export default CourseDetails

Related

There is a problem with the delete function of the react component implemented through the map method

I am developing using react.
It is in the process of fetching the information contained in the db and displaying it on the web page through the map method.
If you delete one piece of information using onclick or the onClose method provided by antd, the info is also deleted from the db.
in the db, the function worked successfully. but the information at the bottom is deleted, not the deleted information in the web page.
If I refresh website, it is displayed normally, but I don't want to use the window reload function.
I wonder why this is happening and what is the solution.
thank you!
AlertPage
import React, { useState } from "react";
import useSWR from "swr";
import axios from "axios";
import AlertComponent from "./Sections/AlertComponent";
const fetcher = async (url) =>
await axios.get(url).then((response) => JSON.parse(response.data.alerts));
function AlertPage() {
const { data = [], error } = useSWR("/api/streaming/getAlerts", fetcher, {
refreshInterval: 1000,
});
const onClose = (data) => {
axios.post(`/api/streaming/removeAlerts/${data._id.$oid}`).then(() => {
console.log(`${data._id.$oid} deleted`);
});
};
const renderAlerts = data.map((alert, index) => {
return (
<div key={index}>
<AlertComponent alert={alert} index={index} onClose={onClose} />
</div>
);
});
if (error) return <div>failed to load</div>;
if (data === []) return <div>loading...</div>;
return <div>{renderAlerts}</div>;
}
export default AlertPage;
AlertComponent
import React, { useState } from "react";
import { Alert } from "antd";
import Marquee from "react-fast-marquee";
function AlertComponent(props) {
const [alert, setalert] = useState(props.alert);
const [index, setindex] = useState(props.index);
return (
<div
className="alert"
key={index}
style={{ display: "flex" }}
onClick={() => {
props.onClose(alert);
}}
>
<Alert
message={`${alert.data.timestamp.$date.substr(0, 19)}`}
description={
<Marquee pauseOnHover speed={40} gradient={false}>
{`<${alert.data.location}> <${alert.data.name}> <${alert.data.contents}> detected`}
</Marquee>
}
banner
/>
</div>
);
}
export default AlertComponent;
This could be happening due the local cache maintained by swr and since you're not refetching the data after the deletion the changes are not reflected in the DOM.
One options is to trigger a manual refetch to retrieve the most up-to-date data. We could achieve that by changing the following lines:
const { data = [], error, mutate } = useSWR("/api/streaming/getAlerts", fetcher, {
refreshInterval: 1000
});
...
axios.post(`/api/streaming/removeAlerts/${data._id.$oid}`).then(() => {
mutate("/api/streaming/getAlerts");
});
another approach would be to rely on the optimistic update strategy from swr, there is an example here

react axios URL concatenation returns 404 not found

I am trying to display dynamic data based on record id coming from useParams hook variable id. But when I concatenated the id value, it returns not found 404 error. Although the id value is returned as valid id when I console it, the concatenation doesn't work.
Here is my code
import React, { useEffect, useRef, useState } from "react";
import SignaturePad from "react-signature-canvas";
import offer from "./assets/offer.PNG";
import { Document, Page } from "react-pdf";
// Import the main component
import { Viewer } from "#react-pdf-viewer/core"; // install this library
// Plugins
import { defaultLayoutPlugin } from "#react-pdf-viewer/default-layout"; // install this library
// Import the styles
import "#react-pdf-viewer/core/lib/styles/index.css";
import "#react-pdf-viewer/default-layout/lib/styles/index.css";
// Worker
import { Worker } from "#react-pdf-viewer/core"; // install this library
import axios from "axios";
import { useParams } from "react-router-dom";
const Signature = (props) => {
const id = useParams();
const [numPages, setNumPages] = useState(null);
const baseURL = "http://127.0.0.1:8000/rent/" + id;
const [datas, setData] = useState([]);
useEffect(() => {
axios
.get(baseURL)
.then((response) => {
setData(response.data);
})
.then(
(response) => {},
(err) => {
console.log("No Data To Show");
}
)
.catch((err) => {
return false;
});
}, []);
// Create new plugin instance
const defaultLayoutPluginInstance = defaultLayoutPlugin();
console.log(docId);
return (
<div className="p-10 flex flex-col space-y-24 font-serif justify-center items-center">
<img src={imgg} />
{datas?.file && (
<>
<Worker workerUrl="https://unpkg.com/pdfjs-dist#2.6.347/build/pdf.worker.min.js">
<Viewer
fileUrl={datas?.file}
plugins={[defaultLayoutPluginInstance]}
/>
</Worker>
</>
)}
</div>
);
};
export default Signature;
Here is the value of id which is dynamically changing.
But when I pass the value of id as follows it works fine.
const baseURL =
"http://127.0.0.1:8000/rent/ce779e1d-3afb-4aa7-82e8-5bf74c4af0a7";
But when I concatenate the id variable it returns 404 not found error.
const baseURL =
"http://127.0.0.1:8000/rent/"+id;
What's my mistake here?
useParams hook of React Router returns an object with params.
You should to use something like that:
const { id } = useParams();
in the case if your params is called id.
More you can see here, in the documentation: https://v5.reactrouter.com/web/api/Hooks/useparams

Why does router.query return an empty object in NextJS on first render?

My url is: http://localhost:3000/company/60050bd166cb770942b1dadd
I want to get the value of the id by using router.query. However when I console log router.query, it returns an empty object first and then return the object with data. This results in bugs in other parts of my code as I need the value of the id to fetch other data.
This is my code:
import { useRouter } from 'next/router';
import styles from './CompanyId.module.css';
import { useQuery } from '#apollo/client';
import { COMPANY_DETAILS } from '../../queries/company';
const CompanyDetails = () => {
const router = useRouter();
console.log(router.query);
const { loading, data } = useQuery(COMPANY_DETAILS, {
variables: { _id: companyId },
});
return (
<div className={styles.container}>
{loading ? <h1>Loading</h1> : <h1>{data.company.name}</h1>}
</div>
);
};
export default CompanyDetails;
My program is crashing right now because the companyId variable is empty on the first render. Is there anyway to go around this problem?
In Next.js:
Pages that are statically optimized by Automatic Static Optimization will be hydrated without their route parameters provided, i.e query will be an empty object ({}).
After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.
I solved it by using useLazyQuery instead of useQuery, and wrapped the function inside useEffect.
The problem was that NextJS's router.query returns an empty object on the first render and the actual object containing the query comes in at the second render.
This code works:
import React, { useEffect } from 'react';
import { useRouter } from 'next/router';
import styles from './CompanyId.module.css';
import { useLazyQuery } from '#apollo/client';
import { COMPANY_DETAILS } from '../../queries/company';
const CompanyDetails = () => {
const router = useRouter();
const [getCompany, { loading, data }] = useLazyQuery(COMPANY_DETAILS);
useEffect(() => {
if (router.query.companyId) {
getCompany({ variables: { _id: router.query.companyId } });
}
}, [router.query]);
if (loading) return <h1>Loading....</h1>;
return (
<div className={styles.container}>
{data && <h1>{data.company.name}</h1>}
</div>
);
};
export default CompanyDetails;

Rest API response not updating until page is refreshed

I've got component that displays contact information from a dealer as chosen by a user. To be more specific, a user selects their location, setting a cookie which then is used to define the API call. I pull in the contact information of the dealer in that location using Axios, store it in a context, and then display the information as necessary through several components: the header, a "current location" component etc.
The problem that I'm currently running into is that the contact information, as displayed in the Header for example, doesn't update until a user performs a hard refresh of the page, so, assuming the default text of the button is something like "Find A Dealer", once a dealer is selected, the button label should say the name of the dealer the user has selected. At present, it isn't working that way. Below is the code for the Header component, and my ApiContext.
ApiContext.tsx
import React, { createContext } from 'react';
import axios from 'axios';
import { makeUseAxios } from 'axios-hooks';
import { useCookie } from 'hooks/use-cookie';
const contextObject = {} as any;
export const context = createContext(contextObject);
const useAxios = makeUseAxios({
axios: axios.create({ baseURL: process.env.GATSBY_API_ENDPOINT }),
});
export const ApiContext = ({ children }: any) => {
const [cookie] = useCookie('one-day-location', '1');
const [{ data }] = useAxios(`${cookie}`);
const { Provider } = context;
return <Provider value={data}>{children}</Provider>;
};
Header.tsx
import React, { ReactNode, useContext, useEffect, useState } from 'react';
import Logo from 'assets/svg/logo.svg';
import css from 'classnames';
import { Button } from 'components/button/Button';
import { Link } from 'components/link/Link';
import { MenuIcon } from 'components/menu-icon/MenuIcon';
import { context } from 'contexts/ApiContext';
import { NotificationBar } from '../notification-bar/NotificationBar';
import s from './Header.scss';
import { MainNav } from './navigation/MainNav';
interface HeaderProps {
navigationContent: ReactNode;
}
export const Header = ({ navigationContent }: HeaderProps) => {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
const data = useContext(context);
const buttonLabel = data ? data.name : 'Find a Dealer';
const buttonLink = data ? `tel:${data.phone}` : '/find-a-dealer';
useEffect(() => {
const handleScroll = () => {
const isScrolled = window.scrollY > 10;
if (isScrolled !== scrolled) {
setScrolled(!scrolled);
}
};
document.addEventListener('scroll', handleScroll, { passive: true });
return () => {
document.removeEventListener('scroll', handleScroll);
};
}, [scrolled]);
return (
<>
<NotificationBar notificationContent={navigationContent} />
<header className={scrolled ? css(s.header, s.header__scrolled) : s.header}>
<nav className={s.header__navigation}>
<ul className={s.header__container}>
<li className={s.header__logo}>
<Link to="/" className={s.header__link}>
<Logo />
</Link>
</li>
<li className={s.header__primary}>
<MainNav navigationItems={navigationContent} />
</li>
<li className={s.header__utility}>
<Button href={buttonLink}>{buttonLabel}</Button>
</li>
<li className={s.header__icon}>
<MenuIcon onClick={() => setOpen(!open)} />
</li>
</ul>
</nav>
</header>
</>
);
};
Here is a screenshot of my console logs, where I'm logging what is returned from data in the ApiContext.
Any suggestions on this would be greatly appreciated, even if it means completely refactoring the way that I'm using this. Thanks!
You are almost there, your ApiContext looks good, it retrieves the information and populates the context, however, what you are missing is a useState to trigger an update to force the re-hydration of your buttons.
What is happening is that your context never updates the data constant. At the first rendering is empty, once your request is done and the context is full but your button is never being updated. Something like this may work for you:
const data = useContext(context);
const [newData, setNewData] = useState(data);
const buttonLabel = newData? newData.name : 'Find a Dealer';
const buttonLink = newData? `tel:${newData.phone}` : '/find-a-dealer';
You may need to adapt the code a bit to fit your requirements, nevertheless, you may keep the idea, which is creating a state with your retrieved data.
You can create a useEffect to control when the data changes and populate the state if you wish:
useEffect(()=>{
setNewData(data)
}, [data])
After a lot of digging, I was able to figure this out myself.
Using the recommendations from Ferran as a base, I decided that it would be best to rehydrate the components displaying the contact info from a state, but as I'm using this context in multiple components, I needed to have the state update globally. I moved away from makeUseAxios, to a traditional axios call. The dealer ID is then stored in the state and used in the call. I also created the changeDealer const, which I can pass through the context, and which updates the state:
ApiContext.tsx
import React, { createContext, useEffect, useState } from 'react';
import axios from 'axios';
const contextObject = {} as any;
export const context = createContext(contextObject);
export const ApiContext = ({ children }: any) => {
const [dealerId, setDealerId] = useState(`1`);
useEffect(() => {
axios.get(`${process.env.GATSBY_API_ENDPOINT}/${dealerId}`).then((res) => setDealerId(res.data));
}, [dealerId]);
const changeDealer = (value: any) => {
setDealerId(value);
};
const { Provider } = context;
return <Provider value={{ data: dealerId, changeDealer: changeDealer }}>{children}</Provider>;
};
Then if, for example, I have a button that updates the dealer info, I import the context to the component and pass changeDealer through the it:
import { context } from 'contexts/ApiContext';
const { changeDealer } = useContext(context);
I can then attach it to a button like so:
<Link to="/" onClick={() => changeDealer(dealer.id)}>
Set Location
</Link>
This updates the state globally, changing the contact information across all the components that display it. I will be storing the data in a localStorage item, allowing the data to persist after a page refresh.

Test conditionals and useEffect in React with Jest

I need to write a test with the following steps:
get user data on mount
get project details if it has selectedProject and clientId when they change
get pages details if it has selectedProject, clientId, and selectedPages when they change
render Content inside Switch
if doesn't have clientId, Content should return null
if doesn't have selectedProject, Content should return Projects
if doesn't have selectedPages, Content should return Pages
else Content should render Artboard
And the component looks like this:
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getUserData } from "../../firebase/user";
import { selectProject } from "../../actions/projects";
import { getItem } from "../../tools/localStorage";
import { getProjectDetails } from "../../firebase/projects";
import { selectPages } from "../../actions/pages";
import Pages from "../Pages";
import Projects from "../Projects";
import Artboard from "../Artboard";
import Switch from "../Transitions/Switch";
import { getUserId, getClientId } from "../../selectors/user";
import { getSelectedProject } from "../../selectors/projects";
import { getSelectedPages, getPagesWithDetails } from "../../selectors/pages";
import { getPagesDetails } from "../../firebase/pages";
const cachedProject = JSON.parse(getItem("selectedProject"));
const cachedPages = JSON.parse(getItem("selectedPages"));
const Dashboard = () => {
const dispatch = useDispatch();
const userId = useSelector(getUserId);
const clientId = useSelector(getClientId);
const selectedProject = useSelector(getSelectedProject) || cachedProject;
const selectedPages = useSelector(getSelectedPages) || cachedPages;
const pagesWithDetails = useSelector(getPagesWithDetails);
useEffect(() => {
dispatch(
getUserData(userId)
);
cachedProject && selectProject(cachedProject);
cachedPages && selectPages(cachedPages);
}, []);
useEffect(() => {
if (selectedProject && clientId) {
dispatch(
getProjectDetails(
clientId,
selectedProject
)
);
}
}, [selectedProject, clientId]);
useEffect(() => {
if (selectedPages && selectedProject && clientId) {
const pagesWithoutDetails = selectedPages.filter(pageId => (
!Object.keys(pagesWithDetails).includes(pageId)
));
dispatch(
getPagesDetails(
selectedProject,
pagesWithoutDetails
)
);
}
}, [selectedPages, selectedProject, clientId]);
const Content = () => {
if (!clientId) return null;
if (!selectedProject) {
return <Projects key="projects" />;
}
if (!selectedPages) {
return <Pages key="pages" />;
}
return <Artboard key="artboard" />;
};
console.log("Update Dashboard")
return (
<Switch>
{Content()}
</Switch>
);
};
Where I use some functions to fetch data from firebase, some to dispatch actions, and some conditionals.
I'm trying to get deep into testing with Jest and Enzyme. When I was searching for testing approaches, testing useEffect, variables, and conditions, I haven't found anything. All I saw is testing if a text changes, if a button has get clicked, etc. but what about testing components which aren't really changing anything in the DOM, just loading data, and depending on that data, renders a component?
What's the question here? What have you tried? To me it seems pretty straightforward to test:
Use Enzymes mount or shallow to render the component and assign that to a variable and wrap it in a store provider so it has access to a redux store.
Use jest.mock to mock things you don't want to actually want to happen (like the dispatching of actions) or use something like redux-mock-store.
Use that component ".find" to get the actual button you want.
Assert that, given a specific redux state, it renders correctly.
Assert that actions are dispatched with the proper type and payload at the proper times.
You may need to call component.update() to force it to rerender within the enzyme test.
Let me know if you have more specific issues.
Good luck!

Categories