How do i use useEffect() / useState() hooks here - reactjs

I am new to React Js and learning to implement certain functionalities by developing a website. Here i need to implement something like this -
https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_typewriter but on page load (not on button click)
But as we know that React has Hooks, which needs to be used while implementing functionalities in the project. So i tried to implement Hook, but need some help in using it, I researched some examples on internet but didn't understood and doesn't fit for my current requirement.
Main.js
import React, { useEffect } from 'react';
import '../App.css';
const Main = () => {
useEffect( () => {
var i = 0;
var txt = 'Some Text here lorem ipsum lorem ipsum';
var speed = 50;
function typeWriter() {
if (i < txt.length) {
document.getElementById("typingText").innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter, speed);
}
}
});
return(
<main>
<div className='row'>
<img src='image.png' height='auto' width='100%' alt="banner"/>
<div className='position-absolute top-50 start-0 translate-middle-y p-5'>
<div class="card bg-black" style={{width: '30%'}}>
<div class="card-body text-white fw-bold">
<h1 id="typingText"></h1>
</div>
</div>
</div>
</div>
</main>
);
export default Main;

First of all, you are missing the array of dependencies of the hook. This prop is extemely important, otherwise the useEffect hook will keep on calling, reaching an overflow of calls.
If I understand what you want, every x milliseconds the typed text should change. useEffect will do the work. In this case, as you want only to run when the component mounts, the dependency array of the hook will be empty ([]). Inside the hook, you can set up an interval, that keeps running the function that you want. With this brief explanation, you could do:
import React, { useEffect, useState } from 'react';
import '../App.css';
const Main = () => {
// We will keep track of how many letters are being displayed
const [index, setIndex] = useState(0);
const type = () => {
setIndex((prev) => Math.min(prev + 1, 50));
};
useEffect(() => {
const id = setInterval(type, 500);
// It is important to clear the interval when the component unmounts!
return () => clearInterval(id);
}, []); // We are setting an empty dep array
return (
<main>
<div className='row'>
<img src='image.png' height='auto' width='100%' alt='banner' />
<div className='position-absolute top-50 start-0 translate-middle-y p-5'>
<div class='card bg-black' style={{ width: '30%' }}>
<div class='card-body text-white fw-bold'>
<h1 id='typingText'>
{'Your text'.substring(0, index)}
</h1>
</div>
</div>
</div>
</div>
</main>
);
};
export default Main;
To sum up, what we are doing is:
Setting an interval to repeat after 500ms. As commented in the code, it is crucial to clear the interval once the component unmounts. The setInterval returns the identifier of the interval, which is used to identify the interval to remove with clearInterval.
Instead of using the window to manipulate the DOM, we keep track of the state, increasing the letters to display from our string.
Finally, we "cut" the string displaying from 0 to the current index, which will be incremented every 500ms, creating the typewritter effect.
Hope to have been clear!
I recommend you take a look at the react hooks documentation.

Related

Render component on button click to change page view with useState (or otherwise) in React

I've made a landing page for my portfolio and I'd like to have the page perform a transition animation (like a fadeout/In, but I can get to that later) then render the component of my actual portfolio instead of my landing page on the button click.
I started trying to write a function but am a bit lost with how to implement this.
I have this as my App setup so far but can't figure out how to implement the state change to render the Portfolio component instead of the HTML from App.js
import React, { useState } from "react";
import "./App.css";
// import Portfolio from "./Portfolio"
function App() {
//States
// const [view, setView] = useState();
// function viewPortfolio (
// )
return (
<div className="App">
<div className="title">
<h1 className="title maintext">Josh Bellingham</h1>
<h2 className="title subtext">Junior Web Developer</h2>
<button className="title btn">
<span>Welcome</span>
</button>
</div>
<div className="sun">
<img className="sun-img" src="Sun.png" alt="Sun" />
<div className="sun-ray r-one"></div>
<div className="sun-ray r-two"></div>
<div className="sun-ray r-three"></div>
<div className="sun-ray r-four"></div>
</div>
<div className="waves">
<img className="wave-1" src="Wave 1 (1).png" alt="Wave"></img>
<img className="wave-2" src="Wave 2 (1).png" alt="Wave"></img>
<img className="wave-3" src="Wave 3 (1).png" alt="Wave"></img>
</div>
</div>
);
}
just define a function in your code and call it with arrow functions or you can use the bind function. ps, it is good to use 'useMemo' or 'useCallback' hooks depending on your
function App() {
const [count, setCount] = useState(0);
//define function
const testFunction = () => {
console.log("you can call a function here ");
setCount(10); // or whatever you want to call
}
return (
<div className="App">
{/* call function */}
<button onClick={() => testFunction()}>test btn</button>
<p>{count}</p>
</div>
)
}
you can call any function with onClick method. if you just pass the function to the onClick, it will call your function when the component is being rendered and we don't want that for now, we wrap that in the arrow-function. every time you call a function and it changes any state in your component, it will cause re-rendering, so if you want to re-render you should change some states.
in the code I wrote when you click on the test btn you call testFunction and it will change the count state to 10 and it will cause re-rendering, we can use that state as I used it in the paragraph
you can read more at events or hooks ,lifecycle

Reactjs Child Component not updating

I'm having trouble updating the state of the child component.
If i upload an image it doesn't appear in the ReactChromakeyedImage component. (which is a package i use)
import './App.css';
import ReactChromakeyedImage from 'react-chromakeyed-image';
import React, { useState, useEffect } from 'react';
function App() {
const [file, setFile] = useState();
function handleChange(e) {
console.log(e.target.files);
setFile(URL.createObjectURL(e.target.files[0]));
}
return (
<div className="App">
<h2>Add Image:</h2>
<input type="file" onChange={handleChange} />
<h3>Original</h3>
<img style={{width: "400px"}} src={file} />
<h3>Chromakeyed</h3>
<ReactChromakeyedImage src={file} style={{width: "400px"}} findColor="#3CC156" replaceColor="#FFFFFF" tolerance={70}/>
</div>
);
}
export default App;
I figured it out. It's a bug in the lib. Internally, it checks the img tag it uses inside to see if the complete property has been set on the image HTML element. This goes to true when the browser has painted the image. However, it only does this on every rerender, and it it also does not wait until it becomes true (it's asynchronous). That's why it's flaky.
Really the lib needs to be forked to fix, but there is a pretty horrible workaround you could do (this is really not nice at all, but it might be your only option unless you fork).
In this solution we attach a load listener using the DOM API (bad!) and then force a rerender, which triggers the library to check the completed property again.
Try this https://codesandbox.io/s/bold-voice-bhxw36?file=/src/App.js:0-1085.
import ReactChromakeyedImage from "react-chromakeyed-image";
import React, { useState, useRef, useLayoutEffect } from "react";
function App() {
const [file, setFile] = useState();
const containerRef = useRef();
const [, forceReload] = useState(0);
function handleChange(e) {
setFile(URL.createObjectURL(e.target.files[0]));
}
useLayoutEffect(() => {
if (!containerRef.current) return;
containerRef.current.querySelector("img").addEventListener("load", () => {
forceReload((x) => x + 1);
});
}, [file]);
return (
<div className="App">
<h2>Add Image:</h2>
<input type="file" onChange={handleChange} />
<h3>Original</h3>
<img style={{width: "400px"}} src={file} />
<h3>Chromakeyed</h3>
{file && (
<div ref={containerRef}>
<ReactChromakeyedImage
src={file}
findColor="#3CC156"
replaceColor="#FFFFFF"
tolerance={70}
width="400"
height="400"
/>
</div>
)}
</div>
);
}
export default App;
I do not know this library, and it could be a bug or simply that values are just stored and don't get updated if you change the value of the image. My advice would be just to make the component re-render to update it. The simplest way is to add a key to it, whenever this key changes the component will get re-rendered. I use file.name in this example but any changing value would be fine.
<ReactChromakeyedImage key={file.name} src={file} style={{width: "400px"}} findColor="#3CC156" replaceColor="#FFFFFF" tolerance={70}/>

How would I test this using Jest & React Testing library?

I have a component that I would like to test using Jest and React Testing Library. When I say test, I'm basically saying that I want to check if the content shows up on the screen. However, I'm running into a serious problem because I'm dealing with an async operation that updates the state, so the content is not appearing immediately. How would I approach this problem? A code snippet would be much appreciated.
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
const Home = () => {
const [tv, setTv] = useState([]);
const [tvLoading, setTvLoading] = useState(true);
// Go and fetch popular TV shows
const getPopularTv = async () => {
axios.get( ... )
setTv(data);
setTvLoading(false);
};
// This will run once. As soon as the component gets rendered for the 1st time
useEffect(() => {
getPopularTv();
}, []);
let TvData, loading;
const img_path = 'https://image.tmdb.org/t/p/w500/';
// If we have TV shows, set the 'TvData' variable to a pre-defined block of JSX using it.
if (tv && tv.total_results > 0) {
TvData = (
<div className="row animated fadeIn ">
{tv.results.slice(0, 10).map((show) => {
return (
// I WANT TO TEST IF THIS DIV APPEARS ON THE SCREEN
// SO, ON THIS DIV I'M SETTING UP THE 'data-testid'
// HOWEVER THIS IS A ASYNC OPERATION AND THE CONTENT
// WON'T SHOW UP IMMEDIATELY. HOW WOULD I TEST THIS???
<div
data-testid="home-shows" // HERE'S THE ID THAT I WANT TO USE IN MY TEST
className="col s6 m6 l6"
key={show.id}
>
<Link to={'/tvs/' + show.id}>
<img
className="responsive-img z-depth-3 poster tooltipped"
data-tooltip={show.name}
data-position="top"
src={img_path + show.poster_path}
alt={show.name}
/>
</Link>
</div>
);
})}
</div>
);
}
// Set up the 'loading' screen
loading = (
<div className="progress">
<div className="indeterminate"></div>
</div>
);
return (
<div className="container">
{tvLoading ? loading : TvData}
</div>
);
};
export default Home;
I've tried a combination of act, findByTestId, waitFor, etc. But I can't get it to work properly.
For example, I tried something like this:
it('should display TV shows', async () => {
const { getByText, findByTestId } =
render(
<BrowserRouter>
<Home />
</BrowserRouter>
)
await findByTestId('home-shows')
expect(getByText('More Info')).toBeInTheDocument();
});
My thinking was, if the content appears then it should contain the text of "More Info". If that's not the case the content is not visible, so the test should fail. however, the test fails regards if the content appears or not and I'm getting an error that I should wrap my test inside of an act() callback.
Thanks to #EstusFlask I came to a breakthrough. The solution was to use waitFor.
This is how I solved the problem:
it('should display movies', async () => {
render(
<BrowserRouter>
<Home />
</BrowserRouter>
);
const data = await waitFor(() => screen.findByTestId('home-shows'));
expect(data).toBeTruthy();
});

React useMeasure not working with nextJS?

I'm currently trying to animate a div so that it slides from bottom to top inside a card.
The useMeasure hook is supposed to give me the height of the wrapper through the handler I attached to it : <div className="desc-wrapper" {...bind}>
Then I am supposed to set the top offset of an absolutely positionned div to the height of its parent and update this value to animate it.
The problem is that when logging the bounds returned by the useMeasure() hook, all the values are at zero...
Here is a link to production exemple of the panel not being slided down because detected height of parent is 0 : https://next-portfolio-41pk0s1nc.vercel.app/page-projects
The card component is called Project, here is the code :
import React, { useEffect, useState } from 'react'
import './project.scss'
import useMeasure from 'react-use-measure';
import { useSpring, animated } from "react-spring";
const Project = (projectData, key) => {
const { project } = projectData
const [open, toggle] = useState(false)
const [bind, bounds] = useMeasure()
const props = useSpring({ top: open ? 0 : bounds.height })
useEffect(() => {
console.log(bounds)
})
return (
<div className="project-container">
<div className="img-wrapper" style={{ background: `url('${project.illustrationPath}') no-
repeat center`, backgroundSize: project.portrait ? 'contain' : 'cover' }}>
</div>
<div className="desc-wrapper" {...bind} >
<h2 className="titre">{project.projectName}</h2>
<span className="description">{project.description}</span>
<animated.div className="tags-wrapper" style={{ top: props.top }}>
</animated.div>
</div>
</div>
);
};
export default Project;
Is this a design issue from nextJS or am I doing something wrong ? Thanks
I never used react-use-measure, but in the documentations, the first item in the array is a ref and you are suppose to use it this way.
function App() {
const [ref, bounds] = useMeasure()
// consider that knowing bounds is only possible *after* the view renders
// so you'll get zero values on the first run and be informed later
return <div ref={ref} />
}
You did...
<div className="desc-wrapper" {...bind} >
Which I don't think is correct...

Can i change a single child component in a loop without re-rendering the whole list (react hooks)

I have a parent component where i am looping over a products array and rendering a child component within. Child component contains a button which should only update some value of that component only without re-rendering the whole list.
import React, { useState } from "react";
import { connect } from "react-redux";
import ListComponent from "./ListComponent";
import Search from "./search";
import Loader from "./../Loader";
// import { getProductList } from "../../redux/actions/index";
const mapStateToProps = state => {
return { list: state.product.productList, loading: state.product.loader };
};
const App = ({ list, loading }) => {
const [products, setProducts] = useState(list);
const handleButtonAction = data => {
let newProductList = list.map((v, i) => {
if (v.id === data.id) {
v.isInCatalogue = true;
}
return v;
});
setProducts(newProductList);
list = products;
};
return (
<div className="container-fluid">
{loading ? <Loader /> : ""}
<div className="row d-flex mt-5">
<div className="col-md-12 col-sm-12">
<Search />
</div>
<div className="col-md-12 col-sm-12">
<div className="row ">
{list.map((v, i) => {
if (v.isInCatalogue) {
v.iconClass = "fa fa-check text-success";
} else {
v.iconClass = "fa fa-plus";
}
return (
<div key={i} className="col-sm-6 col-md-4 mb-4 ">
<ListComponent
data={v}
handleButtonAction={handleButtonAction}
/>
</div>
);
})}
</div>
</div>
</div>
</div>
);
};
export default connect(mapStateToProps)(App);
As you are doing immutable change of your list (correctly), the rerendering of the whole list is unavoidable.
Functional stateless component rerender also when its props did not changed (unless you are using React.memo as #AspirinWang suggested), see this answer:
Will a stateless component re-render if its props have not changed?
The purpose of keys in react are exactly designed for that situation. If your not-updated (and also updated) items has the same key before and after the change, it is ok and do not bother of rerender (unless your list item carries a huge JSX tree).
It is good to know that callig render for any component is not a problem and making a component purposely to not rerender should not be your target (especially functional stateless components), because:
It is important to remember that the reconciliation algorithm is an implementation detail. React could rerender the whole app on every action; the end result would be the same.
function ListComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(ListComponent, areEqual);
React will not rerender the component if the output is not different, moreover in this case in listComponent, save the data prop as state, and use useEffect, with data as dependency.
Something like below
const ListComponent = ({data})=>{
const [compData, setCompData] = useState(data);
useEffect(()=>{
setCompData(data)
},[data])
return (
<h1>{compData}</h1>
)
}

Resources