Connecting body background image to a state in next.js - javascript

I am trying to make a pomodoro timer using Next.js And I need to connect body's background image to a state. But my code doesn't work.
I used this code to update body style:
import { VscDebugRestart } from "react-icons/vsc";
import { IoMdSettings } from "react-icons/io";
import { AiFillPlayCircle, AiFillPauseCircle } from "react-icons/ai";
import { FaTasks } from "react-icons/fa";
import { Modal } from "#/components";
import { useModal } from "#/components/modal";
import { useEffect } from "react";
const Timer = () => {
const settingsModal = useModal();
const tasksModal = useModal();
// Update
useEffect(() => {
document.body.style.background =
"url('/images/city-3.jpg') no-repeat center center fixed;";
}, []);
return (
<div className="timer">
<div className="session-options">
<button className="btn">studying</button>
<button className="btn">short break</button>
<button className="btn">long break</button>
</div>
<h1 className="time-info">1:30:00</h1>
<div className="options">
<AiFillPlayCircle className="btn-icon" />
{/* <AiFillPauseCircle className="btn-icon" /> */}
<VscDebugRestart className="btn-icon" />
<IoMdSettings
className="btn-icon"
onClick={settingsModal.open}
/>
<FaTasks className="btn-icon" />
</div>
<Modal modal={settingsModal}>settings</Modal>
</div>
);
};
export default Timer;
But the styling doesn't apply and I can't see any failed requests to image. (I'm sure that image exists)
What can i do to fix it? Or are there any other solutions that makes the image fit in the background even if size changes?

Remove the semicolon from the background string, it is not CSS.
document.body.style.background = "url('/images/city-3.jpg') no-repeat center center fixed"

What a mistake! Anyways, thanks for the help. I fixed it and added the functionality:
const [bgImageName, setBgImageName] = useState<string | undefined>(
undefined
);
useEffect(() => {
setBgImageName("city-1.jpg");
}, []);
useEffect(() => {
document.body.style.background = ` rgba(0, 0, 0, .3) url("/images/${bgImageName}") no-repeat center center fixed`;
document.body.style.backgroundSize = "cover";
document.body.style.backgroundBlendMode = "darken";
}, [bgImageName]);

Related

Next/image doesn't load my src image when I use a map

I use next/image to load my images in my app. It works fine except for a carousel with multiple images.
When I do like this I have that error :
Error: Image is missing required "src" property. Make sure you pass "src" in props to the next/image component. Received: {}
The problem is not because I have an entity without any file
image.js
import { getStrapiMedia } from "../utils/medias"
import NextImage from "next/image"
const Image = (props) => {
if (!props.media) {
return <NextImage {...props} />
}
const { url, alternativeText } = props.media
const loader = ({ src }) => {
return getStrapiMedia(src)
}
return (
<NextImage
loader={loader}
layout="responsive"
objectFit="contain"
width={props.media.width}
height={props.media.height}
src={url}
alt={alternativeText || ""}
/>
)
}
export default Image
Carousel.js
import React, { useCallback } from "react"
import useEmblaCarousel from "embla-carousel-react"
import NextImage from "./Image"
export const EmblaCarousel = (product) => {
const [emblaRef, emblaApi] = useEmblaCarousel()
useEmblaCarousel.globalOptions = { loop: true }
const scrollPrev = useCallback(() => {
if (emblaApi) emblaApi.scrollPrev()
}, [emblaApi])
const scrollNext = useCallback(() => {
if (emblaApi) emblaApi.scrollNext()
}, [emblaApi])
return (
<div className="embla" ref={emblaRef}>
<div className="embla__container">
{product.gallery.map((_gallery) => (
<div key={_gallery.id}>
<NextImage media={_gallery.image} className="embla__slide" />
</div>
))}
</div>
<button
className="hidden md:inline embla__prev mr-2"
onClick={scrollPrev}
>
Prev
</button>
<button
className="hidden md:inline embla__next ml-2"
onClick={scrollNext}
>
Next
</button>
</div>
)
}
export default EmblaCarousel
The issue is
if (!props.media) {
return <NextImage {...props} />
}
in your custom Image component. When the media prop is falsy like undefined or null, you're passing everything else to NextImage but that everything else doesn’t include src prop which is mandatory for next Image component. Also your url extraction is dependent on media prop to be truthy and have a property called url. Can be seen from the next line :-
const { url, alternativeText } = props.media
And you intend to pass this url to src as can be seen from your usage. Either you can return null when media is falsy or you can filter out those items in your list where media prop is falsy and then map on it.
Not sure if you ever found an answer for this but I was running into the same issue and noticed that when looping through the multiple images object from Strapi the object is slightly different than with single images.
To fix this issue I supplied it to the getStrapiMedia() function in the same way it expects single images i.e:
{aboutpage?.attributes.shareCta.images.data.slice(0, 4).map((image) => (
<div key={image.id} className="relative h-64 w-full">
<Image
layout="fill"
objectFit="cover"
placeholder="blur"
blurDataURL={blurDataUrl}
src={
getStrapiMedia({ data: image }) ||
"/images/placeholders/image-placeholder.png"
}
/>
</div>
));
}
Hope this helps and kind regards
Replace NextImage with Image
import { getStrapiMedia } from "../utils/medias"
import Image from "next/image"
const NextImage = (props) => {
if (!props.media) {
return <Image {...props} />
}
const { url, alternativeText } = props.media
const loader = ({ src }) => {
return getStrapiMedia(src)
}
return (
<Image
loader={loader}
layout="responsive"
objectFit="contain"
width={props.media.width}
height={props.media.height}
src={url}
alt={alternativeText || ""}
/>
)
}
export default NextImage
Carousel.js
import React, { useCallback } from "react"
import useEmblaCarousel from "embla-carousel-react"
import NextImage from "./Image"
export const EmblaCarousel = (product) => {
const [emblaRef, emblaApi] = useEmblaCarousel()
useEmblaCarousel.globalOptions = { loop: true }
const scrollPrev = useCallback(() => {
if (emblaApi) emblaApi.scrollPrev()
}, [emblaApi])
const scrollNext = useCallback(() => {
if (emblaApi) emblaApi.scrollNext()
}, [emblaApi])
return (
<div className="embla" ref={emblaRef}>
<div className="embla__container">
{product.gallery.map((_gallery) => (
<div key={_gallery.id}>
<NextImage media={_gallery.image} className="embla__slide" />
</div>
))}
</div>
<button
className="hidden md:inline embla__prev mr-2"
onClick={scrollPrev}
>
Prev
</button>
<button
className="hidden md:inline embla__next ml-2"
onClick={scrollNext}
>
Next
</button>
</div>
)
}
export default EmblaCarousel

Grid Size isn't changing properly in React when changing state

I'm trying to change the grid size on my Conway game of life app but when I click button to set new numCols/numRows only one of them is effected on the app. How do I affectively set new state so grid changes size as expected.
I have 2 buttons in the app, one to make grid smaller, one to make it bigger.
onClick they Trigger function sizeHandler & sizeHandler2.
My guess is I need to set new state in a different method but I tried a few methods to no avail.
import React, { useState } from 'react'
import './App.css';
function App() {
const color = "#111"
const [numRows, setNumRows] = useState(20)
const [numCols, setNumCols] = useState(20)
const generateEmptyGrid = () => {
const rows = [];
for (let i = 0; i < numRows; i++) {
rows.push(Array.from(Array(numCols), () => 0))
}
return rows
}
const [grid, setGrid] = useState(() => {
return generateEmptyGrid();
})
const sizeHandler = () => {
setNumRows(40)
setNumCols(40)
}
const sizeHandler2 = () => {
setNumRows(20)
setNumCols(20)
}
// RENDER
return (
<div className="page">
<div className="title">
<h1>
Conway's Game Of Life
</h1>
<button onClick={sizeHandler}>
Size+
</button>
<button onClick={sizeHandler2}>
Size-
</button>
</div>
<div className="grid" style={{
display: 'grid',
gridTemplateColumns: `repeat(${numCols}, 20px)`
}}>
{grid.map((rows, i) =>
rows.map((col, j) =>
<div className="node"
key={`${i}-${j}`}
style={{
width: 20,
height: 20,
border: 'solid 1px grey',
backgroundColor: grid[i][j] ? color : undefined
}}
/>
))}
</div>
</div>
);
}
export default App;
What you are doing is you want a change of numRows and numCols to have a side effect. You actually almost said it yourself. React has a hook for that: useEffect:
useEffect(() => {
setGrid(generateEmptyGrid())
}, [numRows, numCols]);

(Gatsby) How to add overflow: hidden with an on Click event?

I'm having a problem with adding overflow hidden to my page in Gatsby with SASS.
can it be done with JS ?
import React, { useState } from "react";
import "./styles.scss";
import Hamburger from "hamburger-react";
import Menu from "../Menu";
export default function TopBar() {
const [isOpen, setOpen] = useState(false);
const toggleMenu = () => {
setOpen(!isOpen);
};
return (
<div className="topBar">
<div className="topbar-container">
<div className="topbar-container__logo">Liza Willow</div>
<span className="topbar-container__menu-btn" onClick={toggleMenu}>
<Hamburger size={29} rounded toggled={isOpen} toggle={setOpen} />
</span>
<Menu isOpen={isOpen} setOpen={setOpen} toggleMenu={toggleMenu} />
</div>
</div>
);
}
You can easily get add a unique class name to the body tag or even apply the styles directly once the menu is open. the code will sit inside the toggleMenu function as it is already used to toggle between the menu states.
I would go with something like this:
const toggleMenu = () => {
// get the body element tag
let body = document.getElementsByTagName("body")[0];
// apply the styles based on menu state
if (!isOpen) body.style.overflow = "hidden";
else body.style.overflow = "auto";
}

Changing body image with an onClick Event

I was wondering how would I approach to change a body background image with onClick Event. Should I be using useRef hook or. I would really appriciate the help
body {
background: url("http");
background-position: center;
background-size: cover;
overflow-y: hidden;
overflow-x: hidden;
min-width: 100%;
}
function App() {
return (
<div>
<h1 onClick={...}> click here to change background image </h1>
</div>
);
}
export default App;
You can simply use document.body.style.backgroundImage like this:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [counter, setCounter] = useState(0);
const IMAGES = [
"https://cdn.pixabay.com/photo/2012/08/25/22/22/saturn-54999_1280.jpg",
"https://cdn.pixabay.com/photo/2020/07/06/01/33/sky-5375005_1280.jpg",
"https://cdn.pixabay.com/photo/2011/12/14/12/23/solar-system-11111_1280.jpg"
];
const changeBodyBgImage = () => {
document.body.style.backgroundImage = `url(${IMAGES[counter]})`;
setCounter(counter > 2 ? 0 : counter + 1);
};
return (
<div className="App">
<h1 onClick={changeBodyBgImage}>Hello CodeSandbox</h1>
</div>
);
}
Here is the demo: https://codesandbox.io/s/serene-brook-82prg?file=/src/App.js:0-665
Use document.body to set the background changes you need.
The event handler should look like this.
onClick={() => {
document.body.style.background = ...
}}

How to add classNames in conditional rendering in reactjs?

I have a list of data with images. I want to make image carousel. For this I have created card component and I want here to display 4 cards at a time and remaining should be hidden. Then i want to setTimeout of 5s to display remaining but only for at a time.
So far I have done this.
about.js
import './about.scss';
import data from '../../data/data';
import CardSection from './card';
class About extends React.Component{
constructor(props){
super(props);
this.state = {
properties: data.properties,
property: data.properties[0]
}
}
nextProperty = () => {
const newIndex = this.state.property.index+4;
this.setState({
property: data.properties[newIndex]
})
}
prevProperty = () => {
const newIndex = this.state.property.index-4;
this.setState({
property: data.properties[newIndex]
})
}
render() {
const {property, properties} = this.state;
return (
<div className="section about__wrapper">
<div>
<button
onClick={() => this.nextProperty()}
disabled={property.index === data.properties.length-1}
>Next</button>
<button
onClick={() => this.prevProperty()}
disabled={property.index === 0}
>Prev</button>
<Container className="card__container">
<div class="card__main" style={{
'transform': `translateX(-${property.index*(100/properties.length)}%)`
}}>
{
this.state.properties.map(property => (
<CardSection property={property}/>
))
}
</div>
</Container>
</div>
</div>
)
}
}
export default About
about.scss
.card__container{
overflow-x: hidden;
}
.card__main{
display: flex;
position: absolute;
transition: transform 300ms cubic-bezier(0.455, 0.03, 0.515, 0.955);
.card__wrapper {
padding: 20px;
flex: 1;
min-width: 300px;
}
}
card.js
import React from "react";
import { Card, CardImg, CardText, CardBody,
CardTitle, CardSubtitle, Button } from 'reactstrap';
class CardSection extends React.Component {
render() {
return (
<div className="card__wrapper">
<Card>
<CardImg top width="100%" src={this.props.property.picture} alt="Card image cap" />
<CardBody>
<CardTitle>{this.props.property.city}</CardTitle>
<CardSubtitle>{this.props.property.address}</CardSubtitle>
<CardText>Some quick example text to build on the card title and make up the bulk of the card's content.</CardText>
<Button>Button</Button>
</CardBody>
</Card>
</div>
);
}
}
export default CardSection;
I have added transition in them to change card onclick but i want them to auto change and hide the remaining card.
Right now it looks like this,
You can add items in componentDidMount method using setInterval
componentDidMount() {
this.interval = setInterval(() => this.setState({
properties:data.properties /* add your data*/ }), 4000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
You can have a property called showCardIds that holds an array of the Id of cards that need to be shown, and use that to set a Boolean property called hidden on the div of the card.
You could also do something like this as shown in the example below, this example also uses showCardIds as a state. It filters only for the property that needs to be rendered and filters out the rest.
Here is an example:
...
{
this.state.properties.filter((property, index) => showCardIds.includes(index)).map(property => (
<CardSection property={property}/>
))
}
...
That way only the ones that are present in the array of showCardIds would show up, there needs to be more logic to be written that would populate the ids in showCardIds
Hope this helps. The hidden property is supported from HTML5, and should work on most browsers, unless they are truly "ancient".

Categories