Adding a loader to react component - javascript

I'm trying to implement a loader to my react component, when the background image is loading it should display 'loading' and then once it has loaded it should display 'loaded'
I have a setTimeout() on my componentwillMount() to test that the loader functions as expected which it does
I'm struggling to understand how it knows when the image is loaded and to change the loading state
Is it best to put the image into a seperate component with the loader rather than have it on the Hello component?
https://www.webpackbin.com/bins/-KsOEkf9ubvR6iz4bMxG
Update
I have managed to get a simple image loader working using the onload() method attached to the image - https://www.webpackbin.com/bins/-KsNpZPzveUoby1yriFo
Hello.js
import React from 'react'
import Loader from './Loader'
import styled from 'styled-components'
const Card = styled.div`
height: 400px;
width:20%;
background: url('https://www.planwallpaper.com/static/images/Light-Wood-Background-Wallpaper.jpg');
`
export default class Test extends React.Component {
constructor() {
super()
this.state = { loading:true}
}
componentWillMount()
{
setTimeout(() => this.setState({loading: false}), 3000)
console.log("componentDidMount");
}
render() {
return (
<div>
<Card>
<Loader loading={this.state.loading} />
</Card>
</div>
)
}
}
Loader.js
import React, { Component } from 'react'
import styled, { keyframes } from 'styled-components'
import { string } from 'prop-types'
const transition1 = keyframes`
0% { background: #F19939; }
33.33% { background: #F8CA8F; }
66.66% { background: #FBD8AE; }
100% { background: #F19939; }
`
const transition2 = keyframes`
0% { background: #FBD8AE; }
33.33% { background: #F19939; }
66.66% { background: #F8CA8F; }
100% { background: #FBD8AE; }
`
const transition3 = keyframes`
0% { background: #F8CA8F; }
33.33% { background: #FBD8AE; }
66.66% { background: #F19939; }
100% { background: #F8CA8F; }
`
const Box = styled.span`
height: 12px;
width: 12px;
margin: 0 3px 0 3px;
border-radius: 4px;
animation: 0.4s ${transition1 } infinite;
`
const Box2 = styled(Box)`
animation: 0.4s ${transition2 } infinite;
`
const Box3 = styled(Box)`
animation: 0.4s ${transition3 } infinite;
`
const TextWrap = styled.div`
display: flex;
flex: 0 0 100%;
justify-content: center;
color: #fff;
`
const Para = styled.p`
color: #fff
padding: 19px 0 0 0;
`
const ParaLoaded = styled(Para)`
color: #fff;
padding: 22px 0 0 0;
`
export default class Loader extends Component {
render() {
return (
<div >
<div >
<TextWrap>
{
this.props.loading
?
<Para>Loading...</Para>
:
<ParaLoaded>Loaded</ParaLoaded>
}
</TextWrap>
</div>
</div>
)
}
}

You can do it like this: https://www.webpackbin.com/bins/-KsOJpBVfpazfXghJAaF
LoadBackgroundImage.js
const LoadBackgroundImage = (component, imageUrl, seconds, success, failure) => {
let timeoutOccured = false;
const image = new Image();
const timeout = setTimeout(() => {
timeoutOccured = true;
failure();
}, seconds * 1000);
image.onload = () => {
clearTimeout(timeout);
component.style.backgroundImage = `url('${imageUrl}')`;
if (!timeoutOccured) success();
};
image.src = imageUrl;
};
export default LoadBackgroundImage;
Hello.js
import React from 'react'
import Loader from './Loader'
import styled from 'styled-components'
import LoadBackgroundImage from './LoadBackgroundImage'
const Card = styled.div`
height: 400px;
width:20%;
`
export default class Hello extends React.Component {
constructor() {
super()
this.state = { loading:true}
}
componentDidMount() {
LoadBackgroundImage(
this.card,
'https://www.planwallpaper.com/static/images/Light-Wood-Background-Wallpaper.jpg',
5,
() => this.setState({ loading: false }),
() => console.log('The image did not load in 5 seconds')
);
}
render() {
return (
<div>
<Card innerRef={card => this.card = card}>
<Loader loading={this.state.loading} />
</Card>
</div>
)
}
}
In render() you are using innerRef to obtain a reference to the card component and save it in this.card. Then in componentDidMount you are using this reference and LoadBackgroundImage function to load the image and monitor when it's loaded. If the image is loaded in given number of second success callback will be called, otherwise failure callback will be called. The image can still load after 5 seconds, but the success callback will not be called. If you want to be called anyway, you can skip this ckeck: if (!timeoutOccured) in LoadBackgroundImage function.

Related

eventListener "animationend " behave unpredictably, or maybe it's because of setState function

I'm trying to make a toast message component that works like this.
It comes in from outside the right screen, disappears out of the screen after a certain period of time, and the component get deleted after the animation.
It works perfectly when I make a single component. However, when I create multiple toast components, some of them just get removed before the animation starts.
I tried many ways for 3days, but nothing worked... 😭 I would appreciate it if you could check which part is wrong. Help me plz
Toast Contatiner
import React, { useState, useCallback } from 'react';
import ToastItem from './Toast';
import styled from 'styled-components';
type ToastItemInfo = {
content: string | React.ReactNode;
id: string;
};
const ToastContainer = () => {
const [toastItems, setToastItems] = useState<ToastItemInfo[]>([]);
const handleClick = () => {
setToastItems((toastItems) => [
{
content: 'Toasted !',
id: String(Math.random()).substring(2, 8),
},
...toastItems,
]);
};
const removeToast = useCallback((id: string) => {
setToastItems((toastItems) => toastItems.filter((toast) => toast.id !== id));
}, []);
const hasItems = (toastItems: ToastItemInfo[]) => {
return toastItems.length > 0;
};
const mapItems = (toastItems: ToastItemInfo[]) => {
return toastItems.map((toast) => (
<ToastItem key={toast.id} id={toast.id} removeToast={removeToast}>
{toast.content}
</ToastItem>
));
};
return (
<div>
<Button onClick={handleClick}>Toast 🍞</Button>
{hasItems(toastItems) && <Container>{mapItems(toastItems)}</Container>}
</div>
);
};
const Container = styled.div`
position: fixed;
display: flex;
flex-direction: column;
gap: 8px;
top: 0;
right: 0;
margin: 16px;
`;
// .slideout {
// animation: 0.5s linear animate;
// }
// #keyframes animate {
// from {
// transform: translateX(0);
// }
// to {
// transform: translateX(120%);
// }
// }
const Button = styled.button`
width: 132px;
height: 40px;
background: #434253;
color: white;
font-size: 1rem;
border: 2px solid white;
cursor: pointer;
`;
export default ToastContainer;
ToastItem
import React, { memo, useEffect, useRef, useState } from 'react';
import styled, { keyframes } from 'styled-components';
import ProgressBar from './ProgressBar';
interface ToastProps {
id: string;
children: string | React.ReactNode;
removeToast: (id: string) => void;
}
const autoCloseDelay = 3000;
const ToastItem: React.FC<ToastProps> = (props) => {
const { id, removeToast, children } = props;
const [toClose, setToClose] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const removeFn = () => {
removeToast(id);
};
const { current } = ref;
const timeout = setTimeout(() => {
current?.addEventListener('animationend', () => {
removeToast(id);
});
setToClose(true);
}, autoCloseDelay);
return () => {
clearTimeout(timeout);
current?.removeEventListener('animationend', removeFn);
};
}, [id, removeToast]);
return (
<Toast id={id} ref={ref} onClick={() => removeToast(id)} toClose={toClose}>
{children}
<ProgressBar autoCloseDelay={autoCloseDelay} />
</Toast>
);
};
const slideIn = keyframes`
from{
transform: translateX(120%);
}
to{
transform: translateX(0);
}
`;
const slideOut = keyframes`
from{
transform: translateX(0);
}
to{
transform: translateX(120%);
}
`;
const Toast = styled.div<{ toClose: boolean }>`
box-sizing: border-box;
position: relative;
padding: 0 16px;
width: 250px;
height: 58px;
line-height: 58px;
background: #fabb4d;
color: #5f2700;
border-radius: 2px;
cursor: pointer;
animation: 0.5s ease 0s ${(props) => (props.toClose ? slideOut : slideIn)};
&:hover {
transform: scale(1.05);
}
`;
export default memo(ToastItem);
enter image description here

REACT - Generate number automaticaly

I need help.
I'm trying to generate a random number in this code {number} every given second.
How can i do this?
I tried this, it generates the number randomly but doesn't update it every second.
var number;
(function repeat() {
number = Math.floor((Math.random()*100)+1);
setTimeout(repeat, 1000);
setInterval(repeat, 1000);
})();
import React from 'react'
import styled, { keyframes } from 'styled-components'
class ProgressBar extends React.Component {
render() {
const { text } = this.props
const ProgressContainer = styled.div`
margin-bottom: 25px;
`
const Text = styled.span`
font-size: 17px;
font-family: Poppins;
color: #fff;
`
const Value = styled.span`
font-size: 17px;
font-family: Poppins;
color: #fff;
float: right;
`
const ColorAnimation = keyframes`
0% {background: #04e5e5;}
10% {background: #f37055;}
20% {background: #ef4e7b;}
30% {background: #a166ab;}
40% {background: #5073b8;}
50% {background: #04e5e5;}
60% {background: #07b39b;}
70% {background: #6fba82;}
80% {background: #5073b8;}
90% {background: #1098ad;}
100% {background: #f37055;}
`
const Progress = styled.div`
height: 5px;
border-radius: 2.5px;
margin-top: 10px;
transition: 2s;
animation: ${ColorAnimation} 10s infinite alternate;
`
return(
<ProgressContainer>
<Text>{text}</Text>
<Value>{number}%</Value>
<Progress style={{width: `${number}%`}}></Progress>
</ProgressContainer>
)
}
Thanks
Using the useEffect hook, you can create the needed setInterval and clean up when the component unmounts:
useEffect(() => {
const interval = setInterval(() => {
** code for random number goes here **
}, 1000);
return () => clearInterval(interval);
}, []);
In order to have the component re-render when ever the random number changes, you can utilize the useState hook:
const [randomNumber, setRandomNumber] = useState(null);
Putting it all together:
import React, {useState, useEffect} from "react";
const Container = () => {
const [randomNumber, setRandomNumber] = useState(null)
useEffect(() => {
const interval = setInterval(() => {
setRandomNumber(Math.floor((Math.random()*100)+1))
}, 1000);
return () => clearInterval(interval);
}, []);
return (<div>{randomNumber}</div>)
}
You can see it in action in this JSFiddle.

React: spinner effect is canceling my scrolling effect

OK guys, so I'm noob in React, and I know that I'm mistaken something, but I can't see what.
I put loading spinners between my pages and no problems, until this page :
So I have this page that is scrolling on X axe and a logo spinning during scroll, until know, all was working correctly. And there is the code without the loading spinner :
import React, { useEffect, useRef } from "react";
import styled, { ThemeProvider } from "styled-components";
import { DarkTheme } from "./Themes";
import {motion} from 'framer-motion';
import LogoComponent from '../subComponents/LogoComponent';
import SocialIcons from '../subComponents/SocialIcons';
import PowerButton from '../subComponents/PowerButton';
import { Work } from '../data/WorkData';
import Card from "../subComponents/Card";
import { Umbrella } from "./AllSvgs";
import BigTitle from "../subComponents/BigTitle";
const Box = styled.div`
background-color: ${props => props.theme.body};
height: 400vh;
position: relative;
display: flex;
align-items: center;
`
const Main = styled(motion.ul)`
position: fixed;
top: 12rem;
left: calc(10rem + 15vw);
height: 40vh;
display: flex;
color: white;
`
const Rotate = styled.span`
display: block;
position: fixed;
right: 1rem;
bottom: 1rem;
width: 80px;
height: 80px;
z-index:1;
`
// Framer-motion configuration
const container = {
hidden: {opacity:0},
show: {
opacity:1,
transition: {
staggerChildren:0.5,
duration:0.5,
}
}
}
const WorkPage = () => {
const ref = useRef(null);
const umbrella = useRef(null);
useEffect(() => {
let element = ref.current;
const rotate = () => {
element.style.transform = `translateX(${-window.scrollY}px)`
umbrella.current.style.transform = `rotate(` + -window.scrollY + 'deg)'
}
window.addEventListener('scroll', rotate)
return () => window.removeEventListener('scroll', rotate)
}, [])
return (
<ThemeProvider theme={DarkTheme}>
<Box>
<LogoComponent theme='dark'/>
<SocialIcons theme='dark'/>
<PowerButton />
<Main ref={ref} variants={container} initial='hidden' animate='show' >
{
Work.map( d =>
<Card key={d.id} data={d} />
)
}
</Main>
<Rotate ref={umbrella}>
<Umbrella width={80} height={80} fill={DarkTheme.theme} />
</Rotate>
<BigTitle text="PROJETS" top="10%" right="20%" />
</Box>
</ThemeProvider>
)
}
export default WorkPage
Then I put the code with the same logic than the other pages that are working :
import React, { useEffect, useRef, useState } from "react";
import styled, { ThemeProvider } from "styled-components";
import { DarkTheme } from "./Themes";
import {motion} from 'framer-motion';
import RingLoader from "react-spinners/RingLoader";
import { css } from "#emotion/react";
import LogoComponent from '../subComponents/LogoComponent';
import SocialIcons from '../subComponents/SocialIcons';
import PowerButton from '../subComponents/PowerButton';
import { Work } from '../data/WorkData';
import Card from "../subComponents/Card";
import { Umbrella } from "./AllSvgs";
import BigTitle from "../subComponents/BigTitle";
const Box = styled.div`
background-color: ${props => props.theme.body};
height: 400vh;
position: relative;
display: flex;
align-items: center;
`
const Main = styled(motion.ul)`
position: fixed;
top: 12rem;
left: calc(10rem + 15vw);
height: 40vh;
display: flex;
color: white;
`
const Rotate = styled.span`
display: block;
position: fixed;
right: 1rem;
bottom: 1rem;
width: 80px;
height: 80px;
z-index:1;
`
const override = css`
position: absolute;
bottom: 10%;
right: 10%;
`
// Framer-motion configuration
const container = {
hidden: {opacity:0},
show: {
opacity:1,
transition: {
staggerChildren:0.5,
duration:0.5,
}
}
}
const WorkPage = () => {
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true)
setTimeout(() => {
setLoading(false)
}, 2000)
}, [])
const ref = useRef(null);
const umbrella = useRef(null);
useEffect(() => {
let element = ref.current;
const rotate = () => {
element.style.transform = `translateX(${-window.scrollY}px)`
umbrella.current.style.transform = `rotate(` + -window.scrollY + 'deg)'
}
window.addEventListener('scroll', rotate)
return () => window.removeEventListener('scroll', rotate)
}, [])
return (
<ThemeProvider theme={DarkTheme}>
{
loading ?
<RingLoader
color={'#000'}
loading={loading}
size={60}
css={override}
/>
:
<Box>
<LogoComponent theme='dark'/>
<SocialIcons theme='dark'/>
<PowerButton />
<Main ref={ref} variants={container} initial='hidden' animate='show' >
{
Work.map( d =>
<Card key={d.id} data={d} />
)
}
</Main>
<Rotate ref={umbrella}>
<Umbrella width={80} height={80} fill={DarkTheme.theme} />
</Rotate>
<BigTitle text="PROJETS" top="10%" right="20%" />
</Box>
}
</ThemeProvider>
)
}
export default WorkPage
And the scroll is not working anymore. The logo is still spinning. I try to put in conditions, but nope.
Put the useState on true, but then I have an error on the rotate func :
TypeError: Cannot read properties of null (reading 'style')
rotate
src/components/WorkPage.js:89
86 | let element = ref.current;
87 |
88 | const rotate = () => {
> 89 | element.style.transform = `translateX(${-window.scrollY}px)`
| ^ 90 |
91 | umbrella.current.style.transform = `rotate(` + -window.scrollY + 'deg)'
92 | }
I don't see what I'm missing... Thanks y'all 🙏
In your second example code (where you added loading and setLoading from the useState hook), the <Box> component (which contains the <Main ref={ref}... component) doesn't get rendered when loading is set to true (and you're setting it to true in your first useEffect).
Since the <Main ref={ref}... component isn't being rendered, the line let element = ref.current will initialize element as null. (that's why you're getting that error)

Basic React Styled Component, prop is false but should be true

I am building my first React side project, and I am abit stuck. I can't get the bgColor to work properly.
The Square Component (I am making a grid)
import React from 'react';
import styled from 'styled-components';
const Sqr = styled.div`
border: 1px solid gray;
background-color: ${props => props.bgColor || "red"};
box-sizing: border-box;
`;
// BG COLOR DOESNT WORK
const square = (props) => {
console.log("[square] props", props)
return (
<Sqr>{props.num}</Sqr>
)
};
export default square;
And this is the main component where I am rendering it:
import React from 'react';
import { connect } from 'react-redux';
import styles from './appname.module.css'
import Board from './Board/Board';
import * as actions from '../../store/actions'
import Square from './Square/Square';
import BoardControls from './BoardControls/BoardControls';
class appname extends React.Component {
constructor(props){
super(props);
this.props.initSquares();
}
render() {
return(
<div className={styles.appname}>
<BoardControls />
<Board size={this.props.settings.size}>
{this.props.squares.map(sqr => <Square key={sqr} num={sqr} bgColor={this.props.settings.bgColor} />)}
</Board>
</div>
)
}
}
const mapStateToProps = state => {
return {
settings: state.appname.settings,
squares: state.appname.squares
}
}
const mapDispatchToProps = dispatch => {
return {
initSquares: () => dispatch(actions.initSquares())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(appname);
The background-color is Red in the browser, and if I change that line to read...
const Sqr = styled.div`
border: 1px solid gray;
background-color: ${props => props.bgColor};
box-sizing: border-box;
`;
...the background color property disappears from the class in the browser tools.
The console.log reads [square] props {num: 0, bgColor: "#ccc"}...so it is getting passed.
What am I doing wrong please?
const square = (props) => {
console.log("[square] props", props)
const squareStyle= {
border: '1px solid gray',
background-color: props.bgColor,
box-sizing: 'border-box',
};
return (
<Sqr style={squareStyle}>{props.num}</Sqr>
)
};
Put the Sqr inside the component
const square = (props) => {
const Sqr = styled.div`
border: 1px solid gray;
background-color: ${props => props.bgColor || "red"};
box-sizing: border-box;
`;
return (
<Sqr>{props.num}</Sqr>
)
};

react, styled-components - setting props on a imported styled div

I am trying to write a react component where I can load different styled div-s according to passed props and render them. This is my code so far:
Component:
import React, { Component } from "react";
import s, { keyframes } from "styled-components";
import PropTypes from "prop-types";
import { jumpyRotation } from "../animations/jumpyRotation.js";
import { normalRotation } from "../animations/normalRotation.js";
import { baseShape } from "../shapes/base";
const animations = {
jumpyRotation: jumpyRotation,
normalRotation: normalRotation
};
const shapes = {
square: baseShape
};
class Loader extends Component {
render() {
const {
size = "14px",
color = "#000",
fontSize = "14px",
loaderText = "Loading...",
length = "4s",
animation = "jumpyRotation",
shape = "square"
} = this.props;
const styledShape = shapes[shape];
styledShape.attrs.size = size;
styledShape.attrs.color = color;
styledShape.attrs.animation = animation;
styledShape.attrs.length = length;
return (
<LoaderStyles
length={length}
animation={animations[animation]}
fontSize={fontSize}
color={color}
size={size}
>
styledShape
<span className="loader-text">{loaderText}</span>
</LoaderStyles>
);
}
}
Loader.propTypes = {
size: PropTypes.string, // Size in a valid CSS unit
color: PropTypes.string, // A valid CSS color, changes both loader and text
fontSize: PropTypes.string, // Size in a valid CSS unit
loaderText: PropTypes.string, // Text displayed under the loader
length: PropTypes.string, // The length of animation in a valid CSS unit
animation: PropTypes.string // The name of the animation
};
const LoaderStyles = s.div`
font-size: ${props => props.fontSize};
display: flex;
align-items: center;
justify-items: center;
flex-direction: column;
padding: ${props => props.fontSize};
.loader-text {
color: ${props => props.color};
}
`;
export default Loader;
Styled component ../shapes/base:
import s from "styled-components";
export const baseShape = s.div`
margin: ${props => props.size};
height: ${props => props.size};
width: ${props => props.size};
background-color: ${props => props.color};
animation: ${props => props.animation} ${props =>
props.length} linear infinite;
`;
Now according to the styled component docs, using the syntax should work, but I get the following error:
TypeError: Cannot set property 'size' of undefined
Even though the browser debugger shows styledShape as a styled.div
React only recognizes components if they start with capital letters, so the solution was this (omitted irrelevant code from the anwser):
Styled component:
import s from "styled-components";
export const baseShape = s.div`
margin: ${Brops => props.size};
height: ${props => props.size};
width: ${props => props.size};
background-color: ${props => props.color};
animation: ${props => props.animation} ${props =>
props.length} linear infinite;
`;
Component:
import React, { Component } from "react";
import s from "styled-components";
import { BaseShape } from "../shapes/base";
const shapes = {
square: BaseShape
};
class Loader extends Component {
render() {
const {
size,
color,
animation,
shape
} = this.props;
const ShapeDiv = shapes[shape];
return (
<ShapeDiv size={size} color={color} animation={animation} shape={shape}/>
);
}
}
export default Loader;

Categories

Resources