In my react app, using React-Bootstrap, I set the navbar to fixed after srcolling, but after that I'm unable to use the toggler button, that was working before scrolling :
const [sticky, setSticky] = useState('');
React.useEffect(() => {
window.addEventListener('scroll', stickNavbar);
return () => window.removeEventListener('scroll', stickNavbar);
}, []);
const stickNavbar = () => {
if (window !== undefined) {
let windowHeight = window.scrollY;
// window height changed for the demo
windowHeight > 150 ? setSticky('top') : setSticky('');
}
};
return (
<Navbar bg="light" fixed={stickyClass} className="menu">
<Container fluid >
</Container>
</Navbar>
<Collapse in={open} timeout={200}>
<Container fluid className="dropdown-container">
<Row className="dropdown">
</Row>
</Container>
</Collapse>
)
CSS
.menu {
max-width: 100%;
height: 7rem;
border-bottom: 2px solid rgb(228, 228, 228);
}
.dropdown-container {
position: absolute;
}
.dropdown {
background-color: #f8f9fa;
border-bottom: 2px solid rgb(228, 228, 228);
position: relative;
}
Solution: I created for the useState, useEffect and stickNavbar corresponding items:
const [scroll, setScroll] = useState(0);
React.useEffect(() => {
window.addEventListener('scroll', stickNavbar);
return () => window.removeEventListener('scroll', stickNavbar);
}, []);
const scrollPos = () => {
if (window !== undefined) {
let posHeight = window.scrollY;
setScroll(posHeight)
}
};
than I used them to follow the change in the scroll Y coordinates to set the distance of the position + rem in pixels of the height of the navbar:
<Container fluid className="dropdown-container" style=
{{top:`${112+scroll}`+"px"}}>
Related
Expected Scenario: if the user doesn't use the sidebar for more than 5 seconds, the sidebar fadeout (opacity set to 20%) and fade in (opacity:100%) when mouse is 20px nearby.
Current implementation what i got is: when I load the page and on sidebar if its not hovered for 5 sec, its faded out and when i hover on it, it fades in (opacity 100%) and when i hover out (mouseleave) it is 100%.
I am failing to achieve when mouse is 20px nearby - fade in.
const [isSidebarFaded, setSidebarFaded] = useState(false)
const [isHovered, setHovered] = useState(false)
useEffect(() => {
if (!isHovered) {
const timerId = setTimeout(() => {
//after 5 seconds if not hovered
setSidebarFaded(true)
}, 5000)
return () => {
clearTimeout(timerId)
}
}
}, [])
const handleMouseEnter = () => {
setHovered(true)
if (isSidebarFaded) {
setSidebarFaded(false)
}
}
const handleMouseLeave = () => {
setHovered(false)
if (isSidebarFaded)
setSidebarFaded(true)
}
return(
<div
className={classNames(styles.component, {
[styles.fadeOut]: isSidebarFaded,
})}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
)
Create a wrapper around the actual sidebar, with a padding of 20px. Put the event handlers on the wrapper.
In my version of your code, there is a single timeout with ref, that the event handlers start/stop:
const { useState, useRef, useCallback, useEffect } = React
const Demo = () => {
const [isSidebarFaded, setSidebarFaded] = useState(false)
const timeout = useRef()
const handleMouseEnter = useCallback(() => {
setSidebarFaded(false)
clearTimeout(timeout.current)
}, [])
const handleMouseLeave = useCallback(() => {
clearTimeout(timeout.current)
timeout.current = setTimeout(() => {
setSidebarFaded(true)
}, 2000)
}, [])
useEffect(() => {
handleMouseLeave();
return () => {
clearTimeout(timeout.current)
}
}, [handleMouseLeave])
return(
<div
className={classNames({ sidebarContainer: true, sidebarFaded: isSidebarFaded })}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div className="sidebar"></div>
</div>
)
}
ReactDOM
.createRoot(root)
.render(<Demo />)
.sidebarContainer {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100px;
padding-right: 20px;
opacity: 1;
transition: opacity 0.3s;
}
.sidebarFaded {
opacity: 0.2;
}
.sidebar {
height: 100%;
width: 100%;
background: blue;
}
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.3.2/index.min.js" integrity="sha512-GqhSAi+WYQlHmNWiE4TQsVa7HVKctQMdgUMA+1RogjxOPdv9Kj59/no5BEvJgpvuMTYw2JRQu/szumfVXdowag==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<div id="root"></div>
I've been trying to upload multiple images WITH preview in NextJS (React). I tried changing the constants to arrays and tried mapping through them but it just doesn't seem to work and I don't know how I could get it to work.
I've made a component out of the upload functionality and here is the code that works for uploading a single image with a Preview.
uploadImage.js
import React, { useEffect, useRef, useState } from "react";
import styled from "styled-components";
function imageUpload() {
const [image, setImage] = useState(null);
const fileInputRef = useRef();
const [preview, setPreview] = useState();
useEffect(() => {
if (image) {
const reader = new FileReader();
reader.onloadend = () => {
setPreview(reader.result);
};
reader.readAsDataURL(image);
} else {
}
}, [image]);
return (
<div className="flex ">
<StyledImg
src={preview}
style={{ objectFit: "cover" }}
onClick={() => setImage(null)}
/>
<StyledButton
onClick={(e) => {
e.preventDefault();
fileInputRef.current.click();
}}
/>
<input
type="file"
style={{ display: "none" }}
accept="image/*"
ref={fileInputRef}
onChange={(e) => {
const file = e.target.files[0];
if (file && file.type.substr(0, 5) === "image") {
setImage(file);
} else {
setImage(null);
}
}}
/>
</div>
);
}
const StyledButton = styled.button`
`;
const StyledImg = styled.img`
width: 100px;
height: 100px;
margin-right: 10px;
`;
export default imageUpload;
Based on these references https://react-dropzone.js.org/#section-previews and https://stackblitz.com/edit/nextjs-buk2rw?file=pages%2Findex.js I replaced my code with the following
ImageUpload.js
import React, { useEffect, useState } from "react";
import { useDropzone } from "react-dropzone";
import styled from "styled-components";
function DragAndDrop() {
const [files, setFiles] = useState([]);
const { getRootProps, getInputProps } = useDropzone({
accept: "image/*",
onDrop: (acceptedFiles) => {
setFiles((files) => [
...files,
...acceptedFiles.map((file) =>
Object.assign(file, {
key: file.name + randomId(), // to allow adding files with same name
preview: URL.createObjectURL(file),
})
),
]);
},
});
const removeFile = (file) => {
setFiles((files) => {
const newFiles = [...files];
newFiles.splice(file, 1);
return newFiles;
});
};
const thumbs = files.map((file, i) => (
<div style={thumb} key={file.key}>
<div style={thumbInner}>
<img src={file.preview} style={img} />
</div>
<button type="button" style={removeButton} onClick={() => removeFile(i)}>
X
</button>
</div>
));
useEffect(
() => () => {
files.forEach((file) => URL.revokeObjectURL(file.preview));
},
[files]
);
return (
<section className="container">
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<StyledP className="flex align-center justify-center">
Glisser Images Ici ou Cliquer pour selectionner
</StyledP>
</div>
<aside style={thumbsContainer}>{thumbs}</aside>
</section>
);
}
const StyledP = styled.p`
cursor: pointer;
padding: 30px;
`;
const randomId = () => (Math.random() + 1).toString(36).substring(7);
const thumbsContainer = {
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
marginTop: 16,
};
const thumb = {
display: "inline-flex",
borderRadius: 2,
border: "1px solid #eaeaea",
marginBottom: 8,
marginRight: 8,
width: 100,
height: 100,
padding: 4,
boxSizing: "border-box",
position: "relative",
};
const thumbInner = {
display: "flex",
minWidth: 0,
overflow: "hidden",
};
const img = {
display: "block",
width: "auto",
height: "100%",
};
const removeButton = {
color: "red",
position: "absolute",
right: 4,
};
export default DragAndDrop;
I have been working on a project and while trying to create a slideshow I found somewhere, wanted to edit it so that instead of using the setTimeout to scroll through the images or the circles, I want to be able to use the buttons to go left and right but just cant seem to figure it out.
Here is the link to what it looks like:
https://codesandbox.io/s/throbbing-bash-ir9g0?fontsize=14&hidenavigation=1&theme=dark
Anyone able to point me in the right direction?
In order to achieve what you are looking for, we just need to add click handlers for left and right buttons.
Below are the respective functions in order to display the next/previous slide based on the current index.
//Handler to update the current index on click of right button
const onNextClick = () => {
if(index !== colors.length - 1) {
setIndex(idx => idx + 1);
}
}
//Handler to update the current index on click of left button
const onPreviousClick = () => {
if(index !== 0) {
setIndex(idx => idx - 1);
}
}
<button className="left" onClick={onPreviousClick}>‹</button>
<button className="right" onClick={onNextClick}>›</button>
Adding these functions to slideshow.js & updating the buttons as shown will help you in achieving the expected functionality.
Here is the demo
const colors = ["#0088fE", "#00C49F", "#FFBB28"];
const delay = 5000;
function Slideshow() {
const [index, setIndex] = React.useState(0);
const timeoutRef = React.useRef(null);
function resetTimeout() {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
React.useEffect(() => {
resetTimeout();
timeoutRef.current = setTimeout(
() =>
setIndex((prevIndex) =>
prevIndex === colors.length - 1 ? 0 : prevIndex + 1
),
delay
);
return () => {
resetTimeout();
};
}, [index]);
//Handler to update the current index on click of right button
const onNextClick = () => {
if(index !== colors.length - 1) {
setIndex(idx => idx + 1);
}
}
//Handler to update the current index on click of left button
const onPreviousClick = () => {
if(index !== 0) {
setIndex(idx => idx - 1);
}
}
return (
<div className="slideshow">
<div
className="slideshow-slider"
style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
>
{colors.map((backgroundColor, index) => (
<div className="slide" key={index} style={{ backgroundColor }} />
))}
</div>
<button className="left" onClick={onPreviousClick}>‹</button>
<button className="right" onClick={onNextClick}>›</button>
<div className="slideshow-tabs">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshow-tab${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
}
ReactDOM.render(<Slideshow />, document.getElementById("react"));
.slideshow {
margin: 0 auto;
overflow: hidden;
width: 30%;
position: relative;
}
.slideshow-slider {
white-space: nowrap;transition: ease 1000ms;
}
.slide {
display: inline-block;
height: 240px;
width: 100%;
}
button {
width: 25px;
height: 240px;
position: absolute;
cursor: pointer;
}
.left {
left: 0;
top: 0;
}
.right {
right: 0;
top: 0;
}
.slideshow-tabs {
text-align: center;
position: relative;
bottom: 50px;
}
.slideshow-tab {
display: inline-block;
height: 20px;
width: 20px;
border-radius: 50%;
cursor: pointer;
margin: 15px 7px 0px;
background-color: #c4c4c4;
}
.active {
background-color: #6a0dad;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
-- Update --
As per you requirement, if you wish to go back to the beginning at the end of all the slides, you can make the changes to onNextClick like below
const onNextClick = () => {
setIndex(idx => idx !== colors.length - 1? idx + 1: 0);
}
you can do that by handling the onClick event
here's the link of the code you could check this demo
import React from "react";
import "./slideshow.scss";
const colors = ["#0088fE", "#00C49F", "#FFBB28"];
function Slideshow() {
const [index, setIndex] = React.useState(0);
const handleChange=()=>{
setIndex((prevIndex) =>
prevIndex === colors.length - 1 ? 0 : prevIndex + 1
)
}
React.useEffect(() => {
}, [index]);
return (
<div className="slideshow">
<div
className="slideshow-slider"
style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
>
{colors.map((backgroundColor, index) => (
<div className="slide" key={index} style={{ backgroundColor }} />
))}
</div>
<button className="left" onClick={()=>handleChange} >‹</button>
<button className="right" onClick={()=>handleChange}>›</button>
<div className="slideshow-tabs">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshow-tab${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
}
export default Slideshow;
So I have this code base:
import React, { useEffect, useRef, useState } from 'react';
function App() {
const container = useRef(null);
const canvas = useRef(null);
const [ctx, setCtx] = useState(undefined);
useEffect(() => {
setCtx(canvas.current.getContext("2d"));
}, []);
useEffect(() => {
if (!ctx) return;
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, canvas.current.width, canvas.current.height);
}, [ctx]);
return (
<div
style={{
height: "100vh",
display: "flex",
flexDirection: "column",
padding: 8,
position: "relative",
}}
>
<header>
Some Header
</header>
<div style={{ margin: 40, flex: '1 1' }} ref={container}>
<canvas ref={canvas} />
</div>
</div>
);
}
export default App;
It's pretty basic example for a canvas element placed inside a container div.
What I want to do is to resize the canvas width and height according to the user's screen ( and make it responsive ).
So I found out two options:
To use window.addEventListener('resize', ...) or to use ResizeObserver.
I tried them both, but without any success, thats what I tried to do:
import React, { useEffect, useRef, useState } from 'react';
function App() {
const container = useRef(null);
const canvas = useRef(null);
const [ctx, setCtx] = useState(undefined);
const [size, setSize] = useState([0, 0]);
useEffect(() => {
const resize = () => {
const { offsetWidth, offsetHeight } = container.current;
canvas.current.width = offsetWidth;
canvas.current.height = offsetHeight;
setSize([offsetWidth, offsetHeight]);
setCtx(canvas.current.getContext('2d'));
};
resize();
window.addEventListener('resize', resize);
return () => window.removeEventListener('resize', resize);
}, []);
useEffect(() => {
if (!ctx) return;
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, size[0], size[1]);
}, [ctx, size]);
return (
<div
style={{
height: "100vh",
display: "flex",
flexDirection: "column",
padding: 8,
position: "relative",
}}
>
<header>
Some Header
</header>
<div style={{ margin: 40, flex: '1 1' }} ref={container}>
<canvas ref={canvas} style={{ width: size[0], height: size[1] }} />
</div>
</div>
);
}
export default App;
But from some reason it makes the height of the canvas greater in every resize cycle.
Whys that and how can I fix that?
If you don't have a problem using a package for the canvas you can use react-konva which will take care of the responsive nature really well.
So all you have to do is change the height and width as the window is resized and send them as attributes.
...
useEffect(() => {
const checkSize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener("resize", checkSize);
return () => window.removeEventListener("resize", checkSize);
}, []);
...
return (
<Stage
width={size.width}
height={size.height}
>
...
</Stage>
)
...
Demo: codesandbox
My Menu Drawer is working except for the css transitions. I think whta is happening is, when I change the value of menuOpen (which is a useState), the DOM rerenders and the transition never happens. How do I stop this? I think I need to use the useRef I have already, but not sure how?
My Page Component with a white div that will be the drawer:
import React, { useState, useEffect, useRef } from 'react';
import { Typography } from '#material-ui/core';
import './page.css';
function Page({ someProps }) {
const [ menuOpen, setMenuOpen ] = useState(false);
const menuRef = useRef();
const handleMenuClick = () => {
setMenuOpen(!menuOpen);
console.log('MENU CLICKED!!!!!!!!!!!!!!!!!!!!', menuOpen);
};
const handleClickOutside = (event) => {
console.log('CLICKED!!!!!!!!!!!!!!!!!!!!', event, menuRef.current);
if (menuRef.current && !menuRef.current.contains(event.target) && menuOpen === true) {
setMenuOpen(false);
}
};
useEffect(
() => {
document.addEventListener('click', handleClickOutside, false);
return () => {
document.removeEventListener('click', handleClickOutside, false);
};
},
[ menuOpen ]
);
return (
<Typography className="screen">
<div className="menuButton" onClick={handleMenuClick}>
MENU
</div>
{menuOpen && <div ref={menuRef} className={`menuContainer ${menuOpen === true ? 'isOpen' : ''}`} />}
</Typography>
);
}
export default Page;
My page.css:
.menuContainer {
position: fixed;
top: 0;
left: 0;
width: 250px;
height: 100vh;
background-color: white;
z-index: 1;
transition: margin 1s ease-in;
margin: 0 0 0 -250px;
}
.menuContainer.isOpen {
margin: 0 0 0 0px;
transition: margin 2s;
}