Context:
I want to add an AOS-effect to multiple divs filled with data from a server, which are rendered after the initial render:
import {useEffect, useState} from "react";
export const Test = () => {
const [data, setData] = useState([]);
async function fetchData() {
//Example fetch request
const response = await fetch(API, {method:"GET"});
const json = await response.json();
setData(json);
}
useEffect(() => {fetchData();}, []);
return (
<>
{
data.map((text, index) => {
//add aos effect to each div
<div key={index}>
{text}
</div>
});
}
</>
)
}
<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>
What I've tried:
I tried to import AOS and add it as in this question:
<div key={index} data-aos="fadeIn"></div>
This had no effect at all.
I also looked at react-animate-on-scroll, but it is not compatible with react v. 18.1.0:
Question:
How can I get animate on scroll (AOS) to work on fetched data in react?
I found the error. I had to import the aos css files as well:
import AOS from "aos";
import "aos/dist/aos.css";
Did you initialize AOS in the first place?
<script>
AOS.init();
</script>
You might need to refresh AOS due to reacts rendering nature, which renders new html elements on each component update.
AOS.refresh();
I would try giving a class which animates or using Intersection Observer
Related
What I am trying to do?
I am trying to call an api that sends information and I want to render the information on to the react app. I have acheived what I wanted to however, there is a problem.
THE PROBLEM
React is firing unlimited request to the api as shown in the image below.
app.js
import React, { useState } from 'react';
import './css/main.css'
const App = () => {
const [data, setData] = useState([]);
fetch(`http://localhost/api/index.php`).then((res)=>{return res.json()}).then(
(data)=>{
setData(data)
}
)
return (
<div>
{data.length > 0 && (
<ul>
{data.map(ad => (
<li key={info.id}>
<h3>{info.name}</h3>
<p>{info.details}</p>
</li>
))}
</ul>
)}
</div>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
Why is it not working?
The reason why this happens is that you are fetching data and update state on the fly that causing component to rerender, then to fetch data again, set state, rerender (getting stuck in a loop).
How to solve?
You should use useEffect hook (read more here). Also, you can read more about data fetching on official documentation website here.
What will change in your code?
The whole fetch will be wrapped in a useEffect hook.
useEffect(() => {
fetch(`http://localhost/api/index.php`).then((res)=>{return res.json()}).then(
(data)=>{
setData(data)
}
)
}, []);
you need to use useEffect(). The problem is when you set the data your component rendering from scratch and again fetching data and again setting and again again...
useEffect(() => {
fetch(`http://localhost/api/index.php`)
.then(res=>res.json())
.then(data=>setData(data))
}, []);
This is because you are setting the state in the API response itself and state change triggers re-render.
This happens as follows: fetch API call -> Data response -> set state -> trigger re-render -> fetch API call and the cycle continuous and result in infinite API call.
Solution: Call the API inside useEffect, useEffect is a hook that triggers once when the page renders or when its dependency changes.
Update your app.js as follows:
import React, { useState } from 'react';
import './css/main.css'
const App = () => {
const [data, setData] = useState([]);
useEffect(()=> {
fetch(`http://localhost/api/index.php`).then((res)=>{return res.json()}).then(
(data)=>{
setData(data)
}
),[]
}
return (
<div>
{data.length > 0 && (
<ul>
{data.map(ad => (
<li key={info.id}>
<h3>{info.name}</h3>
<p>{info.details}</p>
</li>
))}
</ul>
)}
</div>
);
}
export default App;
Put fetch function inside useEffect and inside dependency array define when you want to run it. It will stop React from firing unlimited requests.
React calls the body of your component function on each render. In your case you:
Perform a request,
Upon completing the request, you set the state of your useState hook,
The state triggers a re-render,
The cycle repeats.
So the solution is to use 'life cycles' by using something like useEffect, in which you can determine when to run your callback (the fetch() in your case) - only on mounting the component, or when props change.
const renderData = () => {
fetch(`http://localhost/api/index.php`).then((res)=>{return res.json()}).then(
(data)=>{
setData(data)
}
}
useEffect(() => {
renderData();
},[]);
On the first image you can see next.js rendered this element twice
I used tables and thought that it is because of them but then I tried to remove tables and put jut and it still renders twice so I don't know what it can be.
Next.js does not renders only that element but the first from this object
const Sections = {
1: Locations,
0: Departments, // here it will render this one twice
2: Managers,
3: JobTitles,
};
Maybe it has something to do with useState and my statemanagment in this code below
Component that renders twice.
const Locations = () => {
return <div>hdjsad</div>;
};
// Tab Sections
import Locations from ''
import Departments from ''
import Managers from ''
import JobTitles from ''
import Icons from "../../Icons";
import TabItem from "./TabItem";
const tabs_text = ["Locations", "Departments", "Managers", "Job Titles"];
const Sections = {
0: Locations, // THIS IS THE COMPONENT WHICH RENDERS TWICE
1: Departments,
2: Managers,
3: JobTitles,
};
const SettingsTab = () => {
const [active, setActive] = useState<number>(0);
const select = useCallback((id: number) => {
return () => setActive(id);
}, []);
const ActiveSection = useMemo(() => Sections[active], [active]);
return (
<section className={"mb-[24px]"}>
<header
className={"w-full flex items-center mb-[34px] pl-[24px] pr-[12px]"}
>
<div className={"flex space-x-[8px] !mb-0 overflow-x-scroll"}>
{tabs_text.map((tab_text, i) => {
return (
<div onClick={select(i)} key={i}>
<TabItem active={+active === i}>{tab_text}</TabItem>
</div>
);
})}
</div>
<ImportLocationsAndFilter />
</header>
<ActiveSection />
</section>
);
};
APP.js
import { AppProps } from "next/app";
import "antd/dist/antd.css";
import "../styles/global.css";
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default MyApp;
I can't comment yet so I'll do it here. I know react says in the official docs to never rely on UseMemo or Use callback for functionality. It says you should create your application so it works without them, and then add them for performance reasons. What would happen if you took the useMemo out and put
ActiveSelection = Selections[active]
I don't think it'll fix your problem but it might give you more insight into what's causing it.
I just imported my tabs dynamically and set SSR: false.
It has to do something with next.js hydration.
https://nextjs.org/docs/advanced-features/dynamic-import
dynamic(
() => import(""),
{
ssr: false,
}
);
It's strange behaviour / bug related to next.js ssr to fix it wrap your Component in a div like this:
function MyApp({ Component, pageProps }: AppProps) {
return <div id=#root><Component {...pageProps} /></div>;
}
I want each of my pages to have different loading animations when loading. How can i achieve this?
It is not possible to put the loading component on the page component like this:
//Page component
Page.Loader = SomeLoaderComponent
//_app.tsx
const Loader = Component.Loader || DefaultLoader;
This will not work because "Component(the page)" isnt loaded/rendered yet.
I have also tried dynamic import with next.js, so that i can import the correct page based on the url, and then get the correct loader. My initial plan was to add the Loader to the page component, as shown at the first line in the code above. That does not work because I have to give an explicit path.
const getLoader = (pagePath: string): ComponentType => {
const Loader = dynamic(() => import(pagePath));
return Page.Loader;
};
This is stated in the Next.js docs:
So the question is: How can I get a custom loader per page?
You can use Suspense and lazy to accomplish your task.
lets say you have ComponentA.js as follows
const ComponentA = () => {
return <div>Helloooo</div>
}
You can create another component WrapperA.js file
import React, { Suspense } from 'react';
const WrapperA = React.lazy(() => import('./ComponentA'));
function WrapperA() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<ComponentA />
</Suspense>
</div>
);
}
in the place of <div>Loading...</div> you can have any loader component you like. and export WrapperA to your routes or tab as needed.
I am trying to use vanta with next.js, following this guide. It works completely fine with the Net Effect, however, when I try to use the Globe Effect, I get
[VANTA] Init error TypeError: r.Geometry is not a constructor
at h.onInit (vanta.globe.min.js:1)
at h.init (vanta.globe.min.js:1)
at new r.VantaBase (vanta.globe.min.js:1)
at new h (vanta.globe.min.js:1)
at r.<computed> (vanta.globe.min.js:1)
I have isolated Vanta into an Background Component
//Background.js
import { useState, useRef, useEffect } from "react";
import NET from "vanta/dist/vanta.globe.min"
import * as THREE from "three";
export default function Background({ width, height, children }) {
const [vantaEffect, setVantaEffect] = useState(0);
const vantaRef = useRef(null);
useEffect(() => {
if (!vantaEffect) {
setVantaEffect(
NET({
THREE,
el: vantaRef.current,
})
);
}
return () => {
if (vantaEffect) vantaEffect.destroy();
};
}, [vantaEffect]);
return (
<div ref={vantaRef}>{children}</div>
)
}
And added the THREE script into my _app.js
import '../styles/globals.css'
import Head from "next/head";
import Navbar from "../components/Navbar";
import { useEffect } from "react";
export default function App({ Component, pageProps }) {
useEffect(() => {
const threeScript = document.createElement("script");
threeScript.setAttribute("id", "threeScript");
threeScript.setAttribute(
"src",
"https://cdnjs.cloudflare.com/ajax/libs/three.js/r121/three.min.js"
);
document.getElementsByTagName("head")[0].appendChild(threeScript);
return () => {
if (threeScript) {
threeScript.remove();
}
};
}, []);
return (
<>
<Head>
<title>BrainStorm Tutoring</title>
</Head>
<Navbar />
<Component {...pageProps} />
</>
)
}
and used it like so
//index
import Background from "../components/Background";
export default function Home() {
return (
<Background height="400" width="400">
<h1 className="text-white text-8xl text-left p-36">Fish Bowl</h1>
</Background >
)
}
Is it something wrong with THREE, or is it that next.js can't support vanta?
I have that issue with Halo, so i think the THREE object was not available or was not defined in the HALO.js file.
So i go to the official github repo of Vanta and take the source of Halo and Net (the tutorial effect) file, and i found constructor was missing in the Halo file. So i take the one of Net and put in the Halo file.
constructor(userOptions) {
THREE = userOptions.THREE || THREE;
super(userOptions);
}
Then i import my custom Halo file for the effect and it works.
I was playing around with this and found that, if I keep the Three.js version to 122. I don't get the error. Apparently any version after that has a breaking change.
I am currently working on the final project of a coding Bootcamp and there's just one thing that I can't seem to figure out(not sure if it is possible though).
I have a list of activities, where I map every activity into a card (which works fine). Now I would like to put these cards into the React Bootstrap Carousel and show 4 cards at a time. When I press one of the indicator it should slide to the next (or previous) card (not the next 4 cards). The only thing that I can get to work is having 1 card in the carousel.
Hoping that there is someone out there who is able to help me out.
Thank you from a nonDeveloper to a soon to become noobDeveloper.
Here's my code:
import React, { useState, useEffect } from "react";
import "./Cardslider.css";
import Explorecard from "./Explorecard/Explorecard";
import axios from "axios";
import Carousel from "react-bootstrap/Carousel";
function createCard(area) {
return (
<Carousel.Item>
<div>
<Explorecard image={`images/${area.area}.jpg`} title={area.area} />
</div>
</Carousel.Item>
);
}
const Cardslider = (props) => {
const [listOfActivities, setListOfActivities] = useState([]);
const getAllActivities = () => {
axios
.get("http://localhost:9999/activities")
.then((responseFromApi) => {
console.log(responseFromApi);
setListOfActivities(responseFromApi.data);
})
.catch((error) => console.error(error));
};
useEffect(getAllActivities, []);
return (
<div className="cards-container">
<Carousel className="explore-card-carousel" indicators={false}>
{listOfActivities.map(createCard)}
</Carousel>
</div>
);
};
export default Cardslider;
Before using the Carousel.Item tag, you must use the Carousel tag.