How to render all component after an async call? - javascript

I'm new to React and I'm currently setup my first project using Gatsby. Essentially I'm creating a website that use an API created with Strapi. So far, I would like to load the navbar items using an API call like that: http://localhost:3001/sections, where for sections, I mean the items of the navbar.
For doing so, I have defined an Index page like that:
import React from "react"
import Layout from "../components/layout/layout"
import SEO from "../components/layout/seo"
import BGTState from "../context/bgt/bgtState"
import "../styles/css/begreentannery.css"
const IndexPage = () => {
return (
<BGTState>
<Layout>
<SEO title="Home" />
</Layout>
</BGTState>
)
}
export default IndexPage
the BGTState contains the getSections() method that is used inside Layout:
import React, { useContext, useEffect } from "react"
import PropTypes from "prop-types"
import { injectIntl } from "gatsby-plugin-intl"
import BGTContext from "../../context/bgt/bgtContext"
import { Spinner } from "react-bootstrap"
import Footer from "./footer"
import SearchState from "../../context/search/SearchState"
import Search from "../../components/search"
import NavbarMobile from "../../components/layout/navbarMobile"
import NavbarDesktop from "../../components/layout/navbarDesktop"
const Layout = ({ children, intl }) => {
const bgtContext = useContext(BGTContext)
const { loading, getSections, sections } = bgtContext
useEffect(() => {
getSections()
//eslint-disable-next-line
}, [])
return !loading ? (
<>
<NavbarMobile sections={sections} />
<NavbarDesktop sections={sections} />
<SearchState>
<Search />
<div className="container-fluid">
<div className="main">
{children}
<Footer />
</div>
</div>
</SearchState>
</>
) : (
<div className="container" style={{ height: "100vh" }}>
<div className="row h-100 justify-content-center align-items-center">
<Spinner animation="grow" />
</div>
</div>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default injectIntl(Layout)
the problem is in the code above, essentially I call useEffect hook which grab the sections from the API. So, until the sections are downloaded, I stop the code like so:
return !loading ? (
this is the getSections() method inside BGTState:
const getSections = async () => {
try {
setLoading()
const res = await axios.get(
`${process.env.API_URL}/sections?_sort=order:ASC`
)
dispatch({
type: GET_SECTIONS,
payload: res.data,
})
} catch (err) {
dispatch({
type: GET_SECTIONS,
payload: err.response.msg,
})
}
}
in the Index page all works fine, but the problem is in the CollectionsPage, infact I have this structure:
import React from "react"
import { injectIntl } from "gatsby-plugin-intl"
import Layout from "../components/layout/layout"
import SEO from "../components/layout/seo"
import BGTState from "../context/bgt/bgtState"
import CollectionState from "../context/collection/collectionState"
import Collection from "../components/collection"
const CollectionsPage = ({ intl }) => {
return (
<BGTState>
<Layout>
<SEO
lang={intl.locale}
title={`${intl.formatMessage({ id: "collections" })}`}
/>
<CollectionState>
<Collection id={1} />
</CollectionState>
</Layout>
</BGTState>
)
}
export default injectIntl(CollectionsPage)
essentially, the component <CollectionState> isn't mounting 'cause in Layout there is the async call on getSections().
So in Collection component, I have:
import React, { useContext, useEffect } from "react"
import CollectionContext from "../context/collection/collectionContext"
import { Link } from "gatsby"
const Collection = ({ id }) => {
const collectionContext = useContext(CollectionContext)
const { loading, collection, getCollection } = collectionContext
useEffect(() => {
getCollection(id)
}, [])
if (loading) return React.Fragment
return (
<div className="container">
<div className="row">
{/*
<img
src={`${process.env.API_URL}${collection.feature_media.url}`}
className="w-100 mt-2 mb-2"
alt={""}
/>*/}
<Link to="#" className="bg-caption bg-no-underline">
fall/winter 20/21
</Link>
</div>
</div>
)
}
export default Collection
which generate that error:
and of course getCollection is not called and will generate other errors in the Collection component
How can I revisit this mechanism? Essentially I have to:
Load all the sections
Load all the components

Related

ReactJs: How to pass api data to components?

I call api in Home.js file with componentdidmount in class component and i want to render this data in child components with functional components.when i call api in every each child component, its work but when i try to call with props coming only empty array by console.log please help.
import React,{Component} from 'react'
import '../styles/home.css'
import axios from 'axios';
import Teaser from './Teaser'
import Second from './Second'
import Opening from './Opening'
import Menu from './Menu'
export default class Home extends React.Component {
state = {
posts: []
}
componentDidMount() {
axios.get("https://graph.instagram.com/me/media?fields=id,caption,media_url,permalink,username&access_token=IG")
.then(res => {
const posts = res.data.data;
this.setState({ posts });
})
}
render() {
return (
<>
<Teaser/>
<Second/>
<Opening/>
<Menu posts = {this.state.posts}/>
</>
)
}
}
import React from 'react'
import axios from 'axios';
function Menu(props) {
const {posts} = props;
console.log(props);
return (
<>
{posts && posts.map(
(post) =>
post.caption.includes('#apegustosa_menu') &&
post.children.data.map((x) => (
<div className="menu_item" key={x.id}>
<img className="menu_img" src={x.media_url} alt="image" />
</div>
)),
)}
</>
)
}
export default Menu
Here you go:
return (
<>
{
posts && posts.map(post => (
post.caption.includes("#apegustosa_menu") && post.children.data.map(x => {
return <img src={x.media_url} alt="img"></img>
})
))
}
</>
)

React components spams read on firebase collection

So recently i tried making a chat app using React and firebase, to learn these tools. It goes very well, but for some reason it seems that whenever the database is active it spams read on the collection, even with no changes. I suspect some element is rerendering in an infinite loop, but i can't seem to fix it. Any suggestions for how i can display messages and only update whenever a new change is made to the collection?
import React, { useRef, useState } from 'react';
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
import { useAuthState } from 'react-firebase-hooks/auth';
import { useCollectionData } from 'react-firebase-hooks/firestore';
import './App.css';
import Navbar from './Navbar';
firebase.initializeApp({
// My configs are here
})
const auth = firebase.auth();
const firestore = firebase.firestore();
function App() {
const [user] = useAuthState(auth);
return (
<>
<div className="App">
<Navbar />
</div>
<section className="place-content-end">
{user ? <ChatRoom /> : <SignIn />}
<SignOut />
</section>
</>
);
}
function SignIn() {
const signInWithGoogle = () => {
const provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider);
}
return (
<>
<button className="sign-in ml-20 pl-5" onClick={signInWithGoogle}>Sign in with Google to join! </button>
<p className="ml-20 pl-5">Welcome to the chat!</p>
</>
)
}
function SignOut() {
return auth.currentUser && (
<button className="sign-out ml-20 pl-5" onClick={() => auth.signOut()}>Sign Out</button>
)
}
function ChatRoom() {
const messagesRef = firestore.collection('messages');
const query = messagesRef.orderBy('createdAt').limit(50);
const [messages] = useCollectionData(query, { idField: 'id' });
return (
<>
<div className="App ml-20 pl-5">
<h1>HELLO WORLD!</h1>
<main>
{messages && messages.map(msg => <DisplayMessage key={msg.id} message={msg} />)}
</main>
</div>
</>
);
}
function DisplayMessage(props) {
const { text, uid } = props.message;
return (<>
<div>
<p>{text}</p>
</div>
</>)
}
export default App;
And my firebase looks like this after a few minutes online on my local server
Try this and see if it works:
import React, { useRef, useState, useEffect } from "react";
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import "firebase/compat/firestore";
import { useAuthState } from "react-firebase-hooks/auth";
import { useCollectionData } from "react-firebase-hooks/firestore";
import "./App.css";
import Navbar from "./Navbar";
firebase.initializeApp({
// My configs are here
});
const auth = firebase.auth();
const firestore = firebase.firestore();
function App() {
const [user] = useAuthState(auth);
return (
<>
<div className="App">
<Navbar />
</div>
<section className="place-content-end">
{user ? <ChatRoom /> : <SignIn />}
<SignOut />
</section>
</>
);
}
function SignIn() {
const signInWithGoogle = () => {
const provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider);
};
return (
<>
<button className="sign-in ml-20 pl-5" onClick={signInWithGoogle}>
Sign in with Google to join!{" "}
</button>
<p className="ml-20 pl-5">Welcome to the chat!</p>
</>
);
}
function SignOut() {
return (
auth.currentUser && (
<button className="sign-out ml-20 pl-5" onClick={() => auth.signOut()}>
Sign Out
</button>
)
);
}
function ChatRoom() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const messagesRef = firestore.collection("messages");
const query = messagesRef.orderBy("createdAt").limit(50);
const [data] = useCollectionData(query, { idField: "id" });
setMessages(data);
}, []);
return (
<>
<div className="App ml-20 pl-5">
<h1>HELLO WORLD!</h1>
<main>
{messages.length > 0 &&
messages.map((msg) => (
<DisplayMessage key={msg.id} message={msg} />
))}
</main>
</div>
</>
);
}
function DisplayMessage(props) {
const { text, uid } = props.message;
return (
<>
<div>
<p>{text}</p>
</div>
</>
);
}
export default App;
I added useEffect in line 1. And changed your ChatRoom component.
Quick explanation
useEffect is part of the lifecycle of a Function Component. You give it a function to execute whenever the component mounts or props or state of the component changes. You decide this by what you add as the second argument of the useEffect call. If you give it an [] (empty array), it will only execute when the component is mounted. If you give, for example, [messages] (which a state created by useState) then the function will execute on component mount AND whenever messages change. React keeps track of this for you, this is why React is called react, if data changes it reacts to it and renders accordingly.
The above explanation is the basics of it. But there is also one more cool thing it can do:
If you return a function from inside of useEffect, this function will execute when the component is UNmounted. Like so:
useState(() => {
console.log("Component mounted");
return () => {
console.log("Component unmounted");
};
},[]);
In Class Components we have similar functions, they are called: componentDidMount, componentWillUnmount and others.You can read about it here React State & Lifecyle

React Router - The Address Bar Is Changing But Not Rendering The Component

I Am Making A Search Functionality On My Website But Whenever I Type Something And Hit The Search Button The Address ( URL ) Changes But The Component Stays The Same And When I Refresh The Page After That The Component Appears On The Screen.
Here's A Picture Of What I Meant :
URL CHANGES BUT COMPONENT IS SAME
Here's A Picture After The Reload :
Should Be Like This When I Hit The Search
There Was A Solution I Found That Changing From Browser Router To Router Works But That Seems To Give The Following Error :
Uncaught TypeError: Cannot read properties of undefined (reading 'pathname')
App.js
import "./App.css";
import {
BrowserRouter as Router,
Route,
Switch,
Routes,
} from "react-router-dom";
import Header from "./Components/Layouts/Header";
import Footer from "./Components/Layouts/Footer";
import Home from "./Components/Home";
import ProductDetails from "./Components/Product/ProductDetails";
function App() {
return (
<Router>
<div className="App">
<Header />
<div className="container container-fluid">
<Routes>
<Route path="/" element={<Home />} exact />
<Route path="/search/:keyword" element={<Home />} />
<Route path="/product/:id" element={<ProductDetails />} />
</Routes>
</div>
<Footer />
</div>
</Router>
);
}
export default App;
History.js
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
export default history;
Header Component
import React, { Fragment } from "react";
import { Route } from "react-router-dom";
import history from "./history";
import Search from "./Search";
const Header = () => {
return (
<Fragment>
<nav className="navbar row">
<div className="col-12 col-md-3">
<div className="navbar-brand">
<img src="./images/logo.png" />
</div>
</div>
<div className="col-12 col-md-6 mt-2 mt-md-0">
<Search history={history} />
</div>
<div className="col-12 col-md-3 mt-4 mt-md-0 text-center">
<button className="btn" id="login_btn">
Login
</button>
<span id="cart" className="ml-3">
Cart
</span>
<span className="ml-1" id="cart_count">
2
</span>
</div>
</nav>
</Fragment>
);
};
export default Header;
Search Component
import React, { useState } from "react";
const Search = ({ history }) => {
const [keyword, setKeyword] = useState("");
const searchHandler = (e) => {
e.preventDefault();
if (keyword.trim()) {
history.push(`/search/${keyword}`);
} else {
history.push("/");
}
};
return (
<form onSubmit={searchHandler}>
<div className="input-group">
<input
type="text"
id="search_field"
className="form-control"
placeholder="Enter Product Name ..."
onChange={(e) => setKeyword(e.target.value)}
/>
<div className="input-group-append">
<button id="search_btn" className="btn">
<i className="fa fa-search" aria-hidden="true"></i>
</button>
</div>
</div>
</form>
);
};
export default Search;
Home Component
import React, { Fragment, useState } from "react";
import { useParams } from "react-router-dom";
import Pagination from "react-js-pagination";
import MetaData from "./Layouts/MetaData";
import { useDispatch, useSelector } from "react-redux";
import { getAllProducts } from "../Actions/productActions";
import { useEffect } from "react";
import Product from "./Product/Product";
import Loader from "./Layouts/Loader";
import { useAlert } from "react-alert";
const Home = () => {
const [currentPage, setCurrentPage] = useState(1);
const dispatch = useDispatch();
const alert = useAlert();
const { loading, products, error, productsCount, resultPerPage } =
useSelector((state) => state.products);
const { keyword } = useParams();
useEffect(() => {
if (error) {
return alert.error(error);
}
dispatch(getAllProducts(keyword, currentPage));
}, [dispatch, error, alert, keyword, currentPage]);
function setCurrentPageNo(pageNumber) {
setCurrentPage(pageNumber);
}
return (
<Fragment>
{loading ? (
<Loader />
) : (
<Fragment>
<MetaData title="Buy At Discount Price" />
<h1 id="products_heading">Latest Products</h1>
<section id="products" className="container mt-5">
<div className="row">
{products &&
products.map((product) => (
<Product product={product} key={product._id} />
))}
</div>
</section>
{resultPerPage <= productsCount && (
<div className="d-flex justify-content-center mt-5">
<Pagination
activePage={currentPage}
itemsCountPerPage={resultPerPage}
totalItemsCount={productsCount}
onChange={setCurrentPageNo}
nextPageText={"Next"}
prevPageText={"Prev"}
firstPageText={"First"}
lastPageText={"Last"}
itemClass="page-item"
linkClass="page-link"
/>
</div>
)}
</Fragment>
)}
</Fragment>
);
};
export default Home;
Action
import axios from "axios";
import {
ALL_PRODUCTS_REQUEST,
ALL_PRODUCTS_SUCESS,
ALL_PRODUCTS_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
CLEAR_ERRORS,
} from "../Constants/productConstants";
// Get All Product ( ACTIONS )
export const getAllProducts =
(keyword = "", currentPage = 1) =>
async (dispatch) => {
try {
dispatch({ type: ALL_PRODUCTS_REQUEST });
const { data } = await axios.get(
`/api/v1/products?keyword=${keyword}&page=${currentPage}`
);
dispatch({
type: ALL_PRODUCTS_SUCESS,
payload: data,
});
} catch (error) {
dispatch({
type: ALL_PRODUCTS_FAIL,
payload: error.response.data.message,
});
}
};
// Product Details ( ACTIONS )
export const getProductDetails = (id) => async (dispatch) => {
try {
dispatch({ type: PRODUCT_DETAILS_REQUEST });
const { data } = await axios.get(`/api/v1/product/${id}`);
dispatch({
type: PRODUCT_DETAILS_SUCCESS,
payload: data.product,
});
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload: error.response.data.message,
});
}
};
// Clearing eroor message
export const clearErrors = () => async (dispatch) => {
dispatch({ type: CLEAR_ERRORS });
};
You can try to use the useNavigate() hook instead of createBrowserHistory()
First import useNavigate
import { useNavigate } from 'react-router-dom';
then just do this after importing
const navigate = useNavigate();
const searchHandler = (e) => {
e.preventDefault();
if (keyword.trim()) {
navigate(`/search/${keyword}`);
} else {
navigate("/");
}
};

Currently unable to display svg images from API in React app

Issue that I am currently having is displaying the svg images I am getting from an API for countries. Right now they are showing up as empty divs in the HTML and no errors. I am also using the ReactSVG package and still no luck.
Here below is the Home component which is making the API call and the Card component that fed the content:
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import { Container, Row } from 'react-bootstrap';
import CardComponent from '../components/Card';
const baseURL = 'https://restcountries.eu/rest/v2/all';
const Home = () => {
const [ countries, setCountries ] = useState(null);
useEffect(() => {
console.log('hello')
axios.get(baseURL).then((res) => {
setCountries(res.data);
})
}, []);
return (
<>
<Container>
<Row>
{countries && (
countries.map((country) => {
console.log(country.flag)
return <CardComponent key={country.name}
title={country.name}
image={country.flag}
population={country.population}
region={country.region}
capital={country.capital}/>
})
)}
</Row>
</Container>
</>
)
}
export default Home;
import React from 'react';
import {ReactSVG} from 'react-svg';
import { Card } from 'react-bootstrap';
const CardComponent = (props) => {
const { title, flag, population, region, capital } = props;
return (
<Card>
<ReactSVG src={flag}/>
<Card.Body>
<Card.Title>{title}</Card.Title>
<Card.Text>Population: <span id="Population">{population}</span><br></br></Card.Text>
<Card.Text>Region: <span id="Region">{region}</span><br></br></Card.Text>
<Card.Text>Capital: <span id="Capital">{capital}</span><br></br></Card.Text>
</Card.Body>
</Card>
)
}
export default CardComponent;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Here is also a link to my Git repo for this project https://github.com/codejohnson89/react-countries
In your code, there is some error. Please check this code.
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import { Container, Row } from 'react-bootstrap';
import CardComponent from '../components/Card';
const baseURL = 'https://restcountries.eu/rest/v2/all';
const Home = () => {
const [ countries, setCountries ] = useState(null);
useEffect(() => {
console.log('hello')
axios.get(baseURL).then((res) => {
setCountries(res.data);
})
}, []);
return (
<>
<Container>
<Row>
{countries && (
countries.map((country) => {
console.log(country.flag)
return <CardComponent key={country.name}
title={country.name}
image={country.flag}
population={country.population}
region={country.region}
capital={country.capital}/>
})
)}
</Row>
</Container>
</>
)
}
export default Home;
import React from 'react';
import {ReactSVG} from 'react-svg';
import { Card } from 'react-bootstrap';
const CardComponent = (props) => {
const { title, image, population, region, capital } = props;
return (
<Card>
<img src={image} alt="title" width="80"/>
<Card.Body>
<Card.Title>{title}</Card.Title>
<Card.Text>Population: <span id="Population">{population}</span><br></br></Card.Text>
<Card.Text>Region: <span id="Region">{region}</span><br></br></Card.Text>
<Card.Text>Capital: <span id="Capital">{capital}</span><br></br></Card.Text>
</Card.Body>
</Card>
)
}
export default CardComponent;

React hooks and context api localstorage on refresh

In my SPA, I am utilizing react hooks and context API. I need to persist the current state of the component view rendered using the context API so that I can implement the global component conditional rendering through the application.
I have two views on a single dashboard page: overview & detail. The button triggers the global state change and the view should be fixed on the state value even on page refresh.
Here's my code snippets:
AppRoutes file
import React, { useState } from "react";
import { Router, Route, Switch } from "react-router-dom";
import history from "../utils/history";
import { PyramidProvider } from "../context/pyramidContext";
import Dashboard from "../pages/dashboard/Dashboard";
const AppRoutes = () => {
return (
<div>
<React.Suspense fallback={<span>Loading...</span>}>
<Router history={history}>
<Switch>
<PyramidProvider>
<Route path="/" component={Dashboard} />
</PyramidProvider>
</Switch>
</Router>
</React.Suspense>
</div>
);
};
export default AppRoutes;
Dashboard page
import React, { useState, useEffect, useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
import PyramidDetail from "../../components/pyramidUI/pyramidDetail";
import PyramidOverview from "../../components/pyramidUI/pyramidOverview";
const Dashboard = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<React.Fragment>
{info.uiname === "overview" ? <PyramidOverview /> : <PyramidDetail />}
</React.Fragment>
);
};
export default Dashboard;
Overview component
import React, { useState, useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
const Overview = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<div className="d-flex flex-column dashboard_wrap">
<main>
<div className="d-flex">
<button
onClick={() => setInfo({ uiname: "detail", pyramidvalue: 1 })}
>
change view
</button>
</div>
</main>
</div>
);
};
export default Overview;
Detail component
import React, { useContext } from "react";
import { PyramidContext } from "../../context/pyramidContext";
// import axios from "axios";
const Detail = (props) => {
const { info, setInfo } = useContext(PyramidContext);
return (
<div className="d-flex flex-column dashboard_wrap">
<h2>Detail View</h2>
<div>
<button
type="button"
onClick={() => setInfo({ uiname: "overview", pyramidvalue: 0 })}
>
Back
</button>
</div>
</div>
);
};
export default Detail;
Context File
import React, { createContext, useEffect, useReducer } from "react";
let reducer = (info, newInfo) => {
return { ...info, ...newInfo };
};
const initialState = {
uiname: "overview",
pyramidvalue: 0,
};
const localState = JSON.parse(localStorage.getItem("pyramidcontent"));
const PyramidContext = createContext();
function PyramidProvider(props) {
const [info, setInfo] = useReducer(reducer, initialState || localState);
useEffect(() => {
localStorage.setItem("pyramidcontent", JSON.stringify(info));
}, [info]);
return (
<PyramidContext.Provider
value={{
info,
setInfo,
}}
>
{props.children}
</PyramidContext.Provider>
);
}
export { PyramidContext, PyramidProvider };
I click the button to render a detail view and soon as the page is refreshed, the component changes its view to overview instead of sticking around to detail. I checked the local storage values, and it is being updated properly, but still, the component view does not persist as per the value.
I am unable to understand where I am doing wrong, any help to resolve this issue, please? Thanks in advance.
You're never using the value of localStage in your info state,
you should replace your code with:
const [info, setInfo] = useReducer(reducer, localState || initialState);

Categories

Resources