react styled component created dynamically - reactjs

I am new to styled Components from react and I am having trouble creating dynamically a clock with has an initial degree for minutes(minute degrees) and an initial degree for hours (hourDegrees).
This is so far what I have achieved, but I get the following message:
Keyframes.js:20 The component styled.div with the id of "..." has been created dynamically.
You may see this warning because you've called styled inside another component.
To resolve this only create new StyledComponents outside of any render method and function component.
APP CODE
function App() {
return (
<div className="App">
<main>
<div className="clock-wrap">
<section className="firstMinute">
{numbers[0].map((coord, index) => {
return (
<Clock
key={index}
hourDegrees={coord[0]}
minuteDegrees={coord[1]}
/>
);
})}
</section>
<section className="secondMinute">
{numbers[1].map((coord, index) => {
return (
<Clock
key={index}
hourDegrees={coord[0]}
minuteDegrees={coord[1]}
/>
);
})}
</section>
</div>
</main>
</div>
);
}
But I can't solve this issue as, from what I understand, I have created separated components with the info passed as props.
import styled from 'styled-components'
export default function Clock ({ minuteDegrees, hourDegrees, index}) {
const finalHourDegrees = Number(hourDegrees + 360)
const finalMinuteDegrees = Number(minuteDegrees + 360)
const StyledClock = styled.div`
width: 10vh;
`
//hour
const animationHour = keyframes`
from {
transform: rotate(${props => props.hourDegrees}deg);
}
to {
transform: rotate(${finalHourDegrees}deg);
}
`
const HourStyled = styled.div`
animation: ${animationHour} 4s ease-out infinite;
`
//minutes
const animationMinute = keyframes`
from {
transform: rotate(${props => props.minuteDegrees}deg);
}
to {
transform: rotate(${finalMinuteDegrees}deg);
}
`
const MinuteStyled = styled.div`
animation: ${animationMinute} 4s ease-out infinite;
`
return(
<StyledClock className={index}>
<HourStyled className={hourDegrees} key={index} hourDegrees={hourDegrees}/>
<MinuteStyled className={minuteDegrees} key={index} minuteDegrees={minuteDegrees}/>
</StyledClock>
)
}
Thanks a lot beforehand!

You can create StyledClock or MinuteStyled styled-components outside the target component. Also, you can send props to the styled components if needed.
UPDATED I forked your code below and updated by using a callback function for the keyframes to pass the dynamic degrees codesandbox
const StyledComponent = styled.div`
background: ${props => props.background};
`;
Clock
const StyledClock = styled.div`
width: 6vw;
`;
export default function Clock({ minuteDegrees, hourDegrees, index }) {
return (
<StyledClock className={index}>
<Hours index={index} hourDegrees={hourDegrees} />
<Minutes index={index} minuteDegrees={minuteDegrees} />
</StyledClock>
);
}
Minutes
const animationMinute = keyframes`
from {
transform: rotate(${(props) => props.minuteDegrees}deg);
}
to {
transform: rotate(${(props) => props.finalMinuteDegrees}deg);
}
`;
const MinuteStyled = styled.div`
animation: ${animationMinute} 4s ease-out infinite;
`;
export default function Minutes({ minuteDegrees, index }) {
const finalMinuteDegrees = Number(minuteDegrees + 360);
return (
<MinuteStyled
className={minuteDegrees}
key={index}
minuteDegrees={minuteDegrees}
finalMinuteDegrees={finalMinuteDegrees}
/>
);
}

Related

How can I use useRef when using ScrollTigger in React?

I'm using Gsap's ScrollTigger to develop horizontal scrolling.
If a ref is passed when using Gsap's toArray, only the ref of the last element that uses the ref will be referenced. How can I pass all used refs to toArray?
Is only className used as an argument to toArray? Or is there another way to implement horizontal scrolling differently?
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { useLayoutEffect, useRef } from 'react';
import styled from 'styled-components';
gsap.registerPlugin(ScrollTrigger);
const Home = () => {
const panelRef = useRef(null);
const containerRef = useRef(null);
useLayoutEffect(() => {
const sections = gsap.utils.toArray(panelRef); // If you pass a ref, only the last ref will be referenced
gsap.to(sections, {
xPercent: -100 * (sections.length - 1),
scrollTrigger: {
trigger: containerRef.current,
pin: true,
scrub: 1,
end: '+=3500',
},
});
}, []);
return (
<Container ref={containerRef}>
<Panel className="panel" ref={panelRef}>
ONE
</Panel>
<Panel className="panel" ref={panelRef}>
TWO
</Panel>
<Panel className="panel" ref={panelRef}>
THREE
</Panel>
</Container>
);
};
const Container = styled.div`
position: relative;
overscroll-behavior: none;
height: 100%;
width: max-content;
display: flex;
flex-direction: row;
`;
const Panel = styled.div`
height: 100%;
width: 100vw;
background-color: #000;
`;
export default Home;
import { useRef, useEffect } from 'react';
import { ScrollTrigger } from 'react-scroll-trigger';
function MyComponent() {
const triggerRef = useRef(null);
useEffect(() => {
const current = triggerRef.current;
current.addEventListener("enter", () => {
// do something
});
current.addEventListener("leave", () => {
// do something
});
return () => {
current.removeEventListener("enter", () => {});
current.removeEventListener("leave", () => {});
};
}, []);
return (
<div>
<ScrollTrigger ref={triggerRef}>
<MyContent />
</ScrollTrigger>
</div>
);
}

Passing ref into styled components in react

I want to get height and width of styled component in react. I am using this https://opensourcelibs.com/lib/use-resize-observer and my code looks like:
const Test = ({className, ref}) => {
return (
<div className={className} ref={ref}/>
)
}
const StyledTest = styled(Test)`
height: 100px;
width: 100px;
background-color: greenyellow;
`
const TestClass = () => {
const testRef = useRef(null)
const testSize = useResizeObserver({ref: testRef});
return (
<React.Fragment>
<ThemeProvider theme={testTheme}>
<h1>Height test: {leftContainerSize.height}</h1>
<StyledTest ref={leftContainerRef}/>
</ThemeProvider>
</React.Fragment>
)
}
Unfortunately it doesn't work. If I try to do the same for example with image it works so I think there are problem with passing ref into styled components. I read this article Using 'ref' on React Styled Components is not working, but I don't know how to use innerRef in my case. I also tried to use forwardRef but I failed too. Does someone know to make it work?
Try this.
Use forwardRef in functional component to get ref. Don't try to get ref from props.
Your example is missing variables: leftContainerRef, leftContainerSize.
Although you are trying to use them.
const Test = forwardRef(({ className }, ref) => {
return (
<div className={className} ref={ref} />
)
})
const StyledTest = styled(Test)`
height: 100px;
width: 100px;
background-color: greenyellow;
`
const TestClass = () => {
const { ref, height } = useResizeObserver();
return (
<React.Fragment>
<ThemeProvider theme={testTheme}>
<h1>Height test: {height}</h1>
<StyledTest ref={ref} />
</ThemeProvider>
</React.Fragment>
)
}
If you want to work with the ref. You can create your ref and pass it to the hook.
const Test = forwardRef(({ className }, ref) => {
return (
<div className={className} ref={ref} />
)
})
const StyledTest = styled(Test)`
height: 100px;
width: 100px;
background-color: greenyellow;
`
const TestClass = () => {
const ownRef = useRef(null)
const { height } = useResizeObserver({ ref: ownRef });
return (
<React.Fragment>
<ThemeProvider theme={testTheme}>
<h1>Height test: {height}</h1>
<StyledTest ref={ownRef} />
</ThemeProvider>
</React.Fragment>
)
}

React GridList w/ Modal Images

Well, how to start, hi!
I'm creating some Slider with images using Material-UI GridList, and I want those images to be opened in a modal way, just to see them clearly.
I will put the code, and then explain.
import React, {useState} from "react";
import { makeStyles } from "#material-ui/core/styles";
import Modal from "#material-ui/core/Modal";
import tileData from './../../utils/tileData'
import useStylesForSlider from './../../hooks/useStylesForSlider'
import GridList from '#material-ui/core/GridList'
function getModalStyle() {
const top = 50
const left = 50
return {
top: `${top}%`,
left: `${left}%`,
transform: `translate(-${top}%, -${left}%)`
};
}
const useStyles = makeStyles((theme) => ({
paper: {
position: "absolute",
width: 400,
backgroundColor: theme.palette.background.paper,
border: "2px solid #000",
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3)
}
}));
export default function SimpleModal() {
// I have this in another folder, i will put the other ones too, i'm just starting
const classesRoot = useStylesForSlider()
const classes = useStyles();
// getModalStyle is not a pure function, we roll the style only on the first render
const [modalStyle] = useState(getModalStyle);
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const body = (
<div style={modalStyle} className={classes.paper}>
</div>
);
return (
<div className={classesRoot.root}>
<GridList className={classesRoot.gridList} cols={2.5}>
{tileData.map((tile) => (<img src={tile.img} alt={tile.img} onClick={handleOpen} key={tile.img}/>))}
</GridList>
<Modal
open={open}
onClose={handleClose}>
{body}
</Modal>
</div>
);
}
I have in "tileData" an array with the images, and I map them into a tag just to put them in the slider (GridList). It works well. Now, i want to click some img, and then open it in modal window. I click it, and the modal opens, but now comes my question, how do I put the image I clicked somewhere in the "body" constant, or how do I do to do it well. I don't know if i'm explaining well, but I expect to have some good advices, i'm pretty new in React world
You could create a state for the current chosen image index (or a unique id) of your tileData array and then load the image in the body by its index (or id). Here is an example:
import React, { useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Modal from "#material-ui/core/Modal";
import tileData from './../../utils/tileData'
import GridList from "#material-ui/core/GridList";
function getModalStyle() {
const top = 50;
const left = 50;
return {
top: `${top}%`,
left: `${left}%`,
transform: `translate(-${top}%, -${left}%)`
};
}
const useStyles = makeStyles((theme) => ({
paper: {
position: "absolute",
width: 400,
backgroundColor: theme.palette.background.paper,
border: "2px solid #000",
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3)
}
}));
export default function SimpleModal() {
// I have this in another folder, i will put the other ones too, i'm just starting
//const classesRoot = useStylesForSlider();
const classes = useStyles();
// getModalStyle is not a pure function, we roll the style only on the first render
const [modalStyle] = useState(getModalStyle);
const [open, setOpen] = useState(false);
const [currentIdx, setCurrentIdx] = useState(null); // add a state for the current index
const handleOpen = (idx) => {
setCurrentIdx(idx); // set new current index
setOpen(true);
};
const handleClose = () => {
setCurrentIdx(null); // reset current index
setOpen(false);
};
const body = (
<div style={modalStyle} className={classes.paper}>
{tileData[currentIdx] && (
<img src={tileData[currentIdx].img} alt={tileData[currentIdx].img} />
)}
</div>
);
return (
<div>
<GridList cols={2.5}>
{tileData.map((tile, idx) => (
<img
src={tile.img}
alt={tile.img}
onClick={() => handleOpen(idx)}
key={tile.img}
/>
))}
</GridList>
<Modal open={open} onClose={handleClose}>
{body}
</Modal>
</div>
);
}
Live Demo

Setting responsive handlers in React then using them in scss

I have a custom Hook that detects whether the app is the mobile or desktop version. Serving up 2 versions of components works but I am at a loss on how to pass the variable to a scss file.
The code sandbox demo is here.
In my app.js I have a couple of classes that are modified based on #media (max-width: 768px) within the scss file. This would be fine if I only had 1 style but with multiple styles, I would like to find a way to set in React which style to use.
How do I use {windowSize} to pass a JS variable to a .scss file? If I used styled-component what would it look like?
import "./app.scss";
import useWindowSize from "./useWindowSize";
export default function App() {
const windowSize = useWindowSize();
return (
<div className="App">
<h1>Making the app responsive</h1>
<h2 className="TestTitle">{windowSize}</h2>
<p className="BoxWidth">Hello world</p>
</div>
);
}
Styling looks like this:
$width: 768px;
$Colour1: rgb(0, 255, 213);
.BoxWidth {
background-color: green;
#media (max-width: $width) {
background-color: lightblue;
}
}
This is how you can do it with styled-components:
const Box = styled.div` // or 'p' depending on which element you want to use
background-color: green;
// Note that you are using a 'width' prop that needs to be passed in
#media (max-width: ${({ width }) => width}) {
background-color: lightblue;
}
`;
export default function App() {
const windowSize = useWindowSize();
return (
<div className="App">
...
// You pass the window size in as the width prop
<Box width={windowSize}>Hello world</Box>
</div>
);
}
See your modified codesandbox
EDIT
We clarified the question in chat. To which this is the solution:
const commonStyles = { background: "pink", height: 100, margin: "0 auto" };
const SmallComponent = () => <div style={{ ...commonStyles, width: "100%" }} />;
const LargeComponent = () => (
<div style={{ ...commonStyles, width: "500px" }} />
);
const Box = styled.div`
color: white;
background-color: ${({ isMobile }) => (isMobile ? "green" : "lightblue")};
`;
export default function App() {
const windowSize = useWindowSize();
const isMobile = windowSize === "useMobileVersion";
return (
<div className="App">
<h1>Making the app responsive</h1>
<h2>{windowSize}</h2>
<Box isMobile={isMobile}>Hello world</Box>
{isMobile ? <SmallComponent /> : <LargeComponent />}
</div>
);
}
The original codesandbox link has been updated with this latest answer.
You can just use if else.
For example:
export default function App() {
const windowSize = useWindowSize();
const csTestTiele = windowSize <= 768 ? "TestTitleSmall" : "TestTitleNormal";
const csBoxWidth = windowSize <= 768 ? "BoxWidthSmall" : "BoxWidthNormal";
return (
<div className="App">
<h1>Making the app responsive</h1>
<h2 className={csTestTiele}>{windowSize}</h2>
<p className={csBoxWidth}>Hello world</p>
</div>
);
}
or use classnames library:
import cs from 'classnames';
export default function App() {
const windowSize = useWindowSize();
const csTestTiele = cs({
TestTieleSmall: windowSize <= 768,
TestTieleNormal: windowSize > 768,
});
const csBoxWidth = cs({
BoxWidthSmall: windowSize <= 768,
BoxWidthNormal: windowSize > 768,
});
return (
<div className="App">
<h1>Making the app responsive</h1>
<h2 className={csTestTiele}>{windowSize}</h2>
<p className={csBoxWidth}>Hello world</p>
</div>
);
}

CSSTransition from react-transition-group not applying classes

I'm trying to integrate CSSTransition to my Gatsby site, but it is not applying any of the classes. I'm utilizing CSS modules, and I've got a <div> that serves as the parent that fades in and out, essentially applying the fade effect to this and covering the content while it changes. It's got the class fadEffect. Here is my app-layout component, and the SASS.
AppLayout.tsx
import React, { ReactNode, useState } from 'react';
import { ApiContext } from 'contexts/ApiContext';
import { graphql, StaticQuery } from 'gatsby';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import { Devtools } from '../devtools/Devtools';
import { Footer } from '../footer/Footer';
import { Header } from '../header/Header';
import s from './AppLayout.scss';
interface AppLayoutProps {
children: ReactNode;
location: string;
}
const isDev = process.env.NODE_ENV === 'development';
// tslint:disable no-default-export
export default ({ children, location }: AppLayoutProps) => {
const [fadeEffectVisible, setFadeEffectVisible] = useState(false);
const handleFadeEffectEntered = () => {
setTimeout(() => {
setFadeEffectVisible(false);
}, 50);
};
return (
<StaticQuery
query={`${NavQuery}`}
render={(data) => (
<>
<ApiContext>
<Header navigationContent={data.prismic.allNavigations.edges[0].node} />
<CSSTransition
in={fadeEffectVisible}
timeout={150}
classNames={{
enter: s.fadeEffectEnter,
enterActive: s.fadeEffectEnterActive,
enterDone: s.fadeEffectEnterDone,
exit: s.fadeEffectExit,
exitActive: s.fadeEffectExitActive,
}}
onEntered={handleFadeEffectEntered}
>
<div className={s.fadeEffect} aria-hidden="true" />
</CSSTransition>
<TransitionGroup component={null}>
<CSSTransition
key={location}
timeout={150}
classNames={{
enter: s.pageEnter,
}}
>
<div className={s.layout}>
{children}
<Footer navigationItems={data.prismic.allNavigations.edges[0].node} />
{isDev && <Devtools />}
</div>
</CSSTransition>
</TransitionGroup>
</ApiContext>
</>
)}
/>
);
};
const NavQuery = graphql`
query NavQuery {
prismic {
allNavigations {
edges {
node {
...NotificationBar
...NavigationItems
...FooterNavigationItems
}
}
}
}
}
`;
AppLayout.scss
#import '~styles/config';
:global {
#import '~styles/base';
}
.layout {
display: block;
min-height: 100vh;
}
.pageEnter {
display: none;
}
.fadeEffect {
display: none;
position: fixed;
z-index: 9;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
transition: opacity 0.15s linear;
&Enter {
display: block;
opacity: 0;
}
&Active,
&Done,
&Exit {
display: block;
opacity: 1;
}
&ExitActive {
opacity: 0;
}
}
I'm happy to provide more details/code if this isn't enough. I'm newish to React and Gatsby, so I'm still learning the lingo. Thanks in advance.
I don't see part of your code where you are updating fadeEffectVisible to true for first CSSTransition and I don't see in property at all on second CSSTransition and I would bet that is your issue. Please take a look at this example from React Transition Group for understanding usage of properties.
App.js
function App() {
const [inProp, setInProp] = useState(false);
return (
<div>
<CSSTransition in={inProp} timeout={200} classNames="my-node">
<div>
{"I'll receive my-node-* classes"}
</div>
</CSSTransition>
<button type="button" onClick={() => setInProp(true)}>
Click to Enter
</button>
</div>
);
}
Style.css
.my-node-enter {
opacity: 0;
}
.my-node-enter-active {
opacity: 1;
transition: opacity 200ms;
}
.my-node-exit {
opacity: 1;
}
.my-node-exit-active {
opacity: 0;
transition: opacity 200ms;
}
When the in prop is set to true, the child component will first receive the class example-enter, then the example-enter-active will be added in the next tick.

Resources