Same page section navigation using dynamic useRef hook in React - javascript

There are many packages available to navigate between sections on same page. But I don't want to integrate any package as dependency for doing a simple task. Here is a sample code for navigation which is not dynamic
import React, { useRef } from "react";
function Navigation() {
const top = useRef(null);
const scrollAnySection = (ref) => {
window.scrollTo({
top: ref.current.offsetTop - 10,
behavior: 'smooth',
});
};
return (
<>
<div className="navigation" style={{ width: "80%", margin: "3rem auto" }}>
<section className="menu--container">
<h3 onClick={() => scrollAnySection(top)}>Top</h3>
</section>
<section>
<h3 ref={top} className="section--items"
style={{ margin:"100rem 0",textAlign:"center",border:"1px solid blue" }}> Top </h3>
</section>
</div>
</>
);
}
export default Navigation;
But I am getting difficulty with dynamic useRef here where the ref of HTML element will be called from an array. Below is a sample what I am trying to do for dynamic contents. But I am not getting the expected result.
import React, { useRef } from "react";
function Navigation() {
const listSection = ["Head", "Body", "Main", "Footer", "Bottom"];
const navigateToSection = useRef([]);
const scrollAnySection = (ref) => {
window.scrollTo({
top: ref.current.offsetTop - 10,
behavior: 'smooth',
});
};
return (
<>
<div className="navigation" style={{ width: "80%", margin: "3rem auto" }}>
<section className="menu--container" style={{ display: "flex", justifyContent: "space-between"}}>
{ listSection.map((item) => (
<h3
style={{ padding: ".5rem 2rem", borderRadius: "25rem", border: "1px solid blue" }}
className="menu--items"
onClick={() => scrollAnySection ({ item }) }
>{ item }</h3>
))
}
</section>
<section>
{ listSection.map((item, index) => (
<h3
className="section--items"
style={{ margin: "100rem 0", textAlign: "center", border: "1px solid blue" }}
ref={ ref => { navigateToSection.current[index] = ref; }}
>This is { item } section</h3>
))
}
</section>
</div>
</>
);
}
export default Navigation;
I am getting Uncaught TypeError: ref.current is undefined at scrollAnySection and onClick() event.
Any suggestion or a better approach will be appreciated. Thanks.

Related

Update scroll so that cursor remains with the dragged item when new items are added

I have a list of items in a scrollable div (overflow-y: scroll). When I start dragging (in the real website I am using react-beautiful-dnd) some of the items will expand to show subitems since they are dropdown. This causes the position of the items to shift down and so the item that I was dragging moves downwards but my cursor remains in the same position.
Here's the link to the problem: https://codesandbox.io/s/gracious-einstein-vvsbtp?file=/src/App.js
import { useState } from "react";
export default function App() {
const [show, setShow] = useState(false);
const handleDrag = () => {
setShow(true);
};
const DisplayHello = () => {
return (
<>
{new Array(5).fill(0, 0, 5).map((ele, index) => {
return (
<p style={{ margin: "5px" }} key={index}>
Hello
</p>
);
})}
</>
);
};
return (
<div className="App" style={{ height: "400px" }}>
<div
style={{
display: "flex",
flexDirection: "column",
height: "100%",
border: "1px solid red",
width: "200px",
overflow: "scroll"
}}
>
<DisplayHello />
{show && <DisplayHello />}
<div
style={{ backgroundColor: "dodgerblue" }}
draggable="true"
onDrag={handleDrag}
>
Drag Me
</div>
{show && <DisplayHello />}
<DisplayHello />
</div>
</div>
);
}
What I want is that even if the items expand, the cursor should remain on the draggable item. Is this even possible?

I am trying to align movieCards side by side. But they are aligning in a column

This is my movie list component. I tried different attributes in the div section to get the cards in a row. But all the cards are aligned in a column.should i use either flex or grid.If yes how can i use them. Even i referred to many resources for this.But didn't worked for me. What should I do to get the cards as I wish . Please assist me.
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import MovieCard from './MovieCard'
const MoviesList = (props) => {
const [search, setSearch] = useState('')
const [filterBy, setFilterBy] = useState([])
const [orderBy, setOrderBy] = useState('')
const movies = useSelector((state) => {
console.log(state.movies)
return state.movies
})
const handleChange = (e) => {
const inputValue = e.target.value
setSearch(inputValue)
const filteredValue = movies.filter((movie) => {
return movie.Title.toLowerCase().includes(inputValue)
})
setFilterBy(filteredValue)
console.log(filteredValue)
}
const handleSelectChange = (e) => {
setOrderBy(e.target.value)
}
const show = (movies) => {
switch (orderBy) {
case 'a-z': return [...movies.sort((a, b) => a.Title.localeCompare(b.Title))]
case 'z-a': return [...movies.sort((a, b) => b.Title.localeCompare(a.Title))]
case '1-100': return [...movies.sort((a, b) => a.imdbRating - b.imdbRating)]
case '100-1': return [...movies.sort((a, b) => b.imdbRating - a.imdbRating)]
default: return [...movies]
}
}
return (
<div className='container'>
<div className='d-flex mb-3 '>
<h1 style={{ textAlign: 'center', border: 'solid lightgreen', backgroundColor: 'white' }} >My Movie List</h1>
<form style={{ float: 'right', marginRight: '35px' }}>
<input type="text" placeholder='search by name' value={search} onChange={handleChange} />
<select value={orderBy} onChange={handleSelectChange} >
<option value="">orderBy</option>
<option value="a-z">a-z</option>
<option value="z-a">z-a</option>
<option value="1-100">1-100</option>
<option value="100-1">100-1</option>
</select>
</form>
</div>
<div className=" row pt-2 justify-content-around " style={{ textAlign: 'center', width: '100%' }}>
{
filterBy.length > 0 ? (
filterBy.map((movie) => {
return <MovieCard key={movie.imdbID} Poster={movie.Poster} Title={movie.Title} imdbRating={movie.imdbRating} imdbID={movie.imdbID} />
})
) : (
show(movies).map((movie) => {
return <MovieCard key={movie.imdbID} {...movie} />
})
)
}
</div>
</div >
)
}
export default MoviesList
And this is Movie card component
import React from 'react'
import { useDispatch } from 'react-redux'
import { removeMovie } from '../actions/moviesAction'
const MovieCard = (props) => {
console.log(props)
const { Title, Poster, imdbRating, imdbID } = props
const dispatch = useDispatch()
return (
<div className="card mt-2 p-2 bd-highlight border shadow rounded" style={{ width: '16rem' }}>
<img className="card-img-left pt-2" src={Poster} alt={Title} style={{ width: '200px', height: '200px' }} />
<div className="card-body">
<h4 className="card-title">Name : {Title}</h4>
<h5 className="card-title">Ranking : #{imdbRating}</h5>
<ion-icon name="trash" style={{ color: 'red' }} onClick={() => {
dispatch(removeMovie(imdbID))
}}>
</ion-icon>
</div>
</div>
)}
export default MovieCard
Please help me to resolve this. Thankyou
Try providing display: flex, to the div rendering MovieCard, like this:
<div className=" row pt-2 justify-content-around "
style={{ textAlign: 'center', width: '100%', display: 'flex' }}>
{
filterBy.length > 0 ? (
filterBy.map((movie) => {
return <MovieCard key={movie.imdbID} Poster={movie.Poster} Title={movie.Title} imdbRating={movie.imdbRating} imdbID={movie.imdbID} />
})
) : (
show(movies).map((movie) => {
return <MovieCard key={movie.imdbID} {...movie} />
})
)
}
</div>
You can just use flex here.
add the below code to your parent div (div containing all the cards).
If you have a css file add a class to that div and add the following styles.
display: flex;
flex-wrap: wrap;
justify-content: center;
or if you want to use inline styling do the following:
<div className=" row pt-2 justify-content-around " style={{ textAlign: 'center', width: '100%', display: "flex", flexWrap: "wrap", justifyContent: "center" }}>
{filterBy.length > 0 ? (
filterBy.map((movie) => {
return <MovieCard key={movie.imdbID} Poster={movie.Poster} Title={movie.Title} imdbRating={movie.imdbRating} imdbID={movie.imdbID} />
})
) : (
show(movies).map((movie) => {
return <MovieCard key={movie.imdbID} {...movie} />
}
)
)}
</div>
Try using a grid method something like the code below will allow you control the number of rows and columns you want as well as size them.
**
<h1>Example { grid-template-rows: 40% 50% 50px;}</h1>
<p><strong style="color: red">The shorthand property, grid-template, can replace both </strong>grid-template-rows and grid-template-columns.</p>
<hr>
<p style="font-family: Courier">When using grid-template, the values before the slash will determine the size of each row. The values after the slash determine the size of each column. </p><hr>
<p style="font-family: Courier">In this example there are 3 rows and 3 columns added to the grid.</p><h1>Example {grid-template: 40% 50% 50px/100px 50% 200px;}</h1><br>
<p><strong style="color: red">By using the (fr) unit, we can define the size of columns and rows as a fraction of the grid’s length and width.</strong><br>Using (fr) makes it easier to prevent grid items from overflowing the boundaries of the grid.</p>
<h1>Example {grid-template: 1fr 1fr 1fr / 3fr 50% 1fr;}<br> width: 400px;<br>
**
If you're using Bootstrap then you don't need to use flex nor grid directly. This is the Bootstrap's way to achieve the desired layout:
<div className="row">
{movies.map(item => (
<div className="col">
// then here yo can put your card for the movie
</div>
))}
</div>
This is the Bootstrap doc about it

How to make a custom menu close when I click somewhere else other than the menu

To piggyback on a previous question, I'd like my menu to close whenever I click somewhere else other than the menu.
Currently, it will open/close when I click the "Hamburger Menu Button". It will close if I click a link on the menu or the menu itself but I'd also like to close when I "blur" away from it. This would be like if you got an alert message and you clicked somewhere else, the alert would close.
Here is the current CodeSandbox
The relevant code is in Header.tsx:
import React, { useState } from "react";
import { Link } from "react-router-dom";
import {
AppBar,
Container,
createStyles,
IconButton,
makeStyles,
Theme,
Toolbar,
Typography
} from "#material-ui/core";
import MenuIcon from "#material-ui/icons/Menu";
import clsx from "clsx";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
flexGrow: 1,
"& a": {
color: "white",
textDecoration: "none"
}
},
menuButton: {
marginRight: theme.spacing(2),
zIndex: 2
},
title: {
flexGrow: 1,
zIndex: 2
},
toolBar: {
"& div": {
transition: "left .1s"
}
},
menu: {
zIndex: 1,
width: 200,
height: "100%",
position: "fixed",
top: 48,
transition: "left .1s",
marginRight: theme.spacing(2),
left: -200,
background: "#3f51b5",
"& div:first-element": {
marginTop: 100
}
},
menuOpen: {
left: 0,
transition: "left .1s"
},
menuClose: {
left: -200,
transition: "left .1s"
},
topMenu: {
display: "flex",
"& div": {
marginLeft: theme.spacing(1)
}
}
})
);
const UserMenu = () => {
const classes = useStyles();
const [menuOpen, setMenuOpen] = useState(false);
const toggleMenu = () => setMenuOpen(!menuOpen);
const handleMenuClick = () => toggleMenu();
return (
<>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="menu"
onClick={toggleMenu}
>
<MenuIcon />
</IconButton>
<div className={classes.toolBar}>
<Container
className={clsx(classes.menu, {
[classes.menuOpen]: menuOpen,
[classes.menuClose]: !menuOpen,
[classes.toolBar]: true
})}
onClick={handleMenuClick}
>
<div>
<Link to="#">My Profile</Link>
</div>
<div>
<Link to="#">Account</Link>
</div>
<div>
<Link to="#">Admin</Link>
</div>
</Container>
</div>
</>
);
};
const Header: React.FC = ({ children }) => {
const classes = useStyles();
return (
<AppBar position="static" className={classes.root}>
<Toolbar variant="dense">
<UserMenu />
<Typography variant="h6" className={classes.title}>
<Link to="#">Widgets, LLC</Link>
</Typography>
<div className={classes.topMenu}>
<div>
<Link to="#">Sign out</Link>
</div>
<div>
<Link to="#">One more</Link>
</div>
</div>
</Toolbar>
</AppBar>
);
};
export default Header;
**Edit: ** This is the current Sandbox after applying Ryan's changes and moving the UserMenu into its own file.
You can use Material-UI's ClickAwayListener for this. The only tricky part is avoiding an immediate close of your menu after clicking on the button to open the menu (because of the click event being handled by the button opening the menu and then the same click event being handled by the ClickAwayListener closing the menu). Typically you would want to avoid rendering the ClickAwayListener until the menu is open, but I think that might break the transition on the menu unless you did further changes. My example addresses this problem by calling event.stopPropagation() in the click handler for the menu button (handleOpen).
Here's a modified version of your code/sandbox demonstrating this:
import React, { useState } from "react";
import { Link } from "react-router-dom";
import {
AppBar,
Container,
ClickAwayListener,
createStyles,
IconButton,
makeStyles,
Theme,
Toolbar,
Typography
} from "#material-ui/core";
import MenuIcon from "#material-ui/icons/Menu";
import clsx from "clsx";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
flexGrow: 1,
"& a": {
color: "white",
textDecoration: "none"
}
},
menuButton: {
marginRight: theme.spacing(2),
zIndex: 2
},
title: {
flexGrow: 1,
zIndex: 2
},
toolBar: {
"& div": {
transition: "left .1s"
}
},
menu: {
zIndex: 1,
width: 200,
height: "100%",
position: "fixed",
top: 48,
transition: "left .1s",
marginRight: theme.spacing(2),
left: -200,
background: "#3f51b5",
"& div:first-element": {
marginTop: 100
}
},
menuOpen: {
left: 0,
transition: "left .1s"
},
menuClose: {
left: -200,
transition: "left .1s"
},
topMenu: {
display: "flex",
"& div": {
marginLeft: theme.spacing(1)
}
}
})
);
const UserMenu = () => {
const classes = useStyles();
const [menuOpen, setMenuOpen] = useState(false);
const handleOpen = (event: React.MouseEvent) => {
if (!menuOpen) {
event.stopPropagation();
setMenuOpen(true);
}
};
const handleClose = (event: React.MouseEvent<any, MouseEvent>) => {
if (menuOpen) {
setMenuOpen(false);
}
};
return (
<>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="menu"
onClick={handleOpen}
>
<MenuIcon />
</IconButton>
<div className={classes.toolBar}>
<ClickAwayListener onClickAway={handleClose}>
<Container
className={clsx(classes.menu, {
[classes.menuOpen]: menuOpen,
[classes.menuClose]: !menuOpen,
[classes.toolBar]: true
})}
onClick={handleClose}
>
<div>
<Link to="#">My Profile</Link>
</div>
<div>
<Link to="#">Account</Link>
</div>
<div>
<Link to="#">Admin</Link>
</div>
</Container>
</ClickAwayListener>
</div>
</>
);
};
const Header: React.FC = ({ children }) => {
const classes = useStyles();
return (
<AppBar position="static" className={classes.root}>
<Toolbar variant="dense">
<UserMenu />
<Typography variant="h6" className={classes.title}>
<Link to="#">Widgets, LLC</Link>
</Typography>
<div className={classes.topMenu}>
<div>
<Link to="#">Sign out</Link>
</div>
<div>
<Link to="#">One more</Link>
</div>
</div>
</Toolbar>
</AppBar>
);
};
export default Header;

Mouse pointer cannot detect element to trigger onClick()

From the snippet below, you can see that there are 2 Box components that are "underneath" the BigBox component, hence the mouse cannot detect them to trigger onClick(). Is there a way for the mouse to detect those 2 components.
I understand that you can just render the BigBox component first then Box like what I did with the <Box id="yes onClick"> component
However, for the project I'm working on, I specifically need to render a <Box>-like component first then a <BigBox>-like component.
const boxStyle = {
position: "absolute",
border: "1px #999 solid",
borderRadius: "10px",
textAlign: "center",
}
const Box = (props) => {
return (
<div style = {{ ...boxStyle, left: props.left, top: props.top, height: 50, width: 50 }}
onClick = {() => console.log("clicked")} > { props.id } </div>
);
};
const BigBox = (props) => {
return (
<div style = {{ ...boxStyle, left: props.left, top: props.top, height: 200, width: 200 }}> Big Box </div>
);
}
const App = () => {
return (
<div >
<Box id="no onClick 1" left={40} top={50}></Box>
<Box id="no onClick 2" left={80} top={100}></Box>
<BigBox left={10} top={10}></BigBox>
<Box id="yes onClick" left={150} top={150}></Box>
</div>
);
};
ReactDOM.render( <App/> ,
document.getElementById('root')
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Option 1 - pointer-events
You can set the CSS Property pointer-events of the BigBox style to none.
const boxStyle = {
position: "absolute",
border: "1px #999 solid",
borderRadius: "10px",
textAlign: "center",
}
const Box = (props) => {
return (
<div style = {{ ...boxStyle, left: props.left, top: props.top, height: 50, width: 50 }}
onClick = {() => console.log("clicked")} > { props.id } </div>
);
};
const BigBox = (props) => {
return (
<div style = {{ ...boxStyle, pointerEvents: 'none', left: props.left, top: props.top, height: 200, width: 200 }}> Big Box </div>
);
}
const App = () => {
return (
<div >
<Box id="no onClick 1" left={40} top={50}></Box>
<Box id="no onClick 2" left={80} top={100}></Box>
<BigBox left={10} top={10}></BigBox>
<Box id="yes onClick" left={150} top={150}></Box>
</div>
);
};
ReactDOM.render( <App/> ,
document.getElementById('root')
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Option 2 - z-index
Another solution would be to set the z-index of you Boxes to something bigger than the z-index of the BigBox component. That way the BigBox can still receive pointer events.
Option 3 - Use the layout hierarchy
Probably the most idiomatic way would be, to put your boxes inside BigBox.

React joyride - closing tour leads to the next step instead

Im having issues with closing the tour. Clicking on the "Skip tour" leads to the next step... Why? Why it doesnt close the tooltip?
Sample:
const App = () => {
const steps = [];
return (
<div>
<div style={{ background: 'yellow' }} className={'a'}>
hey
</div>
<div style={{ background: 'green' }} className={'b'}>
hey
</div>
<div style={{ background: 'red' }} className={'c'}>
hey
</div>
<Joyride steps={steps} tooltipComponent={CustomTooltip} />
</div>
);
};
const CustomTooltip = ({
index,
size,
step,
closeProps,
primaryProps,
tooltipProps,
isLastStep,
}) => (
<div {...tooltipProps} style={{ background: 'blue', padding: '20px' }}>
<button {...closeProps}>Skip Tour</button>
<button {...primaryProps}>{isLastStep ? 'Close' : 'Next'}</button>
</div>
);
Playground: https://codesandbox.io/s/boring-carson-fmrfo?file=/src/index.tsx
Docs: https://docs.react-joyride.com/custom-components
What you need is skipProps not closeProps. Replace both instances of the variable and you should be good.
import React from "react";
import ReactDOM from "react-dom";
import "react-app-polyfill/ie11";
import "core-js/stable";
import "regenerator-runtime/runtime";
import "polyfill-array-includes";
import Joyride from "react-joyride";
const CustomTooltip = ({
index,
size,
step,
skipProps,
primaryProps,
tooltipProps,
isLastStep
}) => (
<div {...tooltipProps} style={{ background: "blue", padding: "20px" }}>
<button {...skipProps}>Skip Tour</button>
<button {...primaryProps}>{isLastStep ? "Close" : "Next"}</button>
</div>
);
const App = () => {
const steps = [
{
target: ".a",
title: "",
content: "a",
placement: "bottom-start",
disableBeacon: true
},
{
target: ".b",
title: "",
content: "b",
placement: "bottom-start",
disableBeacon: true
},
{
target: ".c",
title: "",
content: "c",
placement: "bottom-start",
disableBeacon: true
}
];
return (
<div>
<div style={{ background: "yellow" }} className={"a"}>
hey
</div>
<div style={{ background: "green" }} className={"b"}>
hey
</div>
<div style={{ background: "red" }} className={"c"}>
hey
</div>
<Joyride steps={steps} tooltipComponent={CustomTooltip} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
https://codesandbox.io/s/compassionate-gagarin-3skx6?file=/src/index.tsx

Categories

Resources