React joyride - closing tour leads to the next step instead - javascript

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

Related

Same page section navigation using dynamic useRef hook in React

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.

Select only a card at a time on click in reactjs

I have a react component which has some cards, When I click the plus icon in the card, it would expand and show some data for 30sec and then the data will disappear and on click it will reappear again, here is the component
import React from "react";
import { FaPlus } from "react-icons/fa";
import useTimeout from "../../../common/useTimeout";
import { Modal } from "../../../common/Modal";
import { ToggleState } from "../../../common/Toggle";
import { BsSearch } from "react-icons/bs";
import AdvancedFilter from "./AdvancedFilter";
export default function CitiesList({ cities }: { cities: any }): JSX.Element {
const [filter, setFilter] = React.useState("");
const [sortType, setSortType] = React.useState("");
const [selectedCity, setSelectedCity] = React.useState<any | null>(null);
console.log(filter);
const sorted = cities.sort((a: { name: string }, b: { name: any }) => {
const isReversed = sortType === "asc" ? 1 : -1;
return isReversed * a.name.localeCompare(b.name);
});
const onSort = (sortType: React.SetStateAction<string>) => {
console.log("changed");
setSortType(sortType);
};
const [showMeta, setShowMeta] = React.useState(false);
const handleClick = () => setShowMeta(true);
const getSelectedCity = (selectedCity: any) => {
setSelectedCity(selectedCity);
console.log("SELECTED CITY", selectedCity);
};
const [visible, setVisible] = React.useState(true);
const hide = () => setVisible(false);
useTimeout(hide, 30000);
console.log("CITIES", cities);
console.log({ selectedCity });
return (
<div style={{ marginTop: "3rem" }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "20px",
}}
>
<div>List of cities</div>
<div style={{ display: "flex", alignItems: "center" }}>
<div style={{ marginRight: "1rem" }}>
<ToggleState
render={({ isOpen, open, close }) => {
return (
<>
<button
type="button"
className="btn btn-primary"
onClick={() => {
isOpen ? close() : open();
}}
>
Advanced Filter
</button>
<Modal
isActive={isOpen}
modalContentWidth={"30%"}
header={() => "Advanced Filter"}
close={() => close()}
renderBody={() => {
return <AdvancedFilter close={() => close()} />;
}}
></Modal>
</>
);
}}
/>
</div>
<div style={{ position: "relative", marginRight: "1rem" }}>
<input
type="text"
placeholder="Filter"
name="namePrefix"
style={{ padding: "0.35rem" }}
onChange={(e: any) => {
setFilter(e.target.value);
}}
/>
<div style={{ position: "absolute", top: "5px", right: "5px" }}>
<BsSearch size="16" />
</div>
</div>
<div style={{ width: "8rem" }}>
<div className="btn-group">
<button
type="button"
className="btn dropdown-toggle sort-button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{sortType === "asc"
? "Ascending"
: sortType === "desc"
? "Descending"
: "Select"}
</button>
<ul className="dropdown-menu sort-button">
<li>
<button
className="dropdown-item"
type="button"
onClick={() => onSort("asc")}
>
Ascending
</button>
</li>
<li>
<button
className="dropdown-item"
type="button"
onClick={() => onSort("desc")}
>
Descending
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
<div>
<div>
<div className="row">
{cities &&
sorted.map((item: any, index: number) => (
<div className="col-lg-3" key={index}>
<div
className="card"
style={{
textAlign: "center",
display: "flex",
justifyContent: "center",
paddingBottom: "1rem",
marginBottom: "1rem",
marginRight: "1rem",
}}
>
<div className="card-body">
<h5 className="card-title">{item.name}</h5>
</div>
{visible && showMeta ? (
<div>
<p>Longitude: {item.longitude}</p>
<p>Latitude: {item.latitude}</p>
<p>Population: {item.population}</p>
{/* <p>Time Zone: America</p> */}
</div>
) : (
<div
onClick={() => {
handleClick();
getSelectedCity(item.id);
}}
style={{ cursor: "pointer" }}
>
<FaPlus size="18" />
</div>
)}
</div>
</div>
))}
</div>
</div>
</div>
<div
style={{ marginTop: "30px", display: "flex", justifyContent: "center" }}
>
{cities && cities.length > 10 ? (
<button className="secondary-button">Load More</button>
) : (
<p>There are no more cities</p>
)}
</div>
</div>
);
}
here is the useTimeout function
import { useEffect, useRef } from "react";
function useTimeout(callback: () => void, delay: number | null) {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) {
return;
}
const id = setTimeout(() => savedCallback.current(), delay);
return () => clearTimeout(id);
}, [delay]);
}
export default useTimeout;
Now currently if I click on one card, all the cards opens and also after when the data disappears after 30sec it does not reappear on button click. I need to reload the page to do reappear data again.I need to solve 2 issues here: 1. how can I open one card only at a time on clicking on the icon, 2. how can I reappear data on button click without refreshing the page.
As I understand you, some information is
<p>Longitude: {item.longitude}</p>
<p>Latitude: {item.latitude}</p>
<p>Population: {item.population}</p>
You have global statement of showing this info for all cards. To reduce it, make new component which will have local state of this info:
import React from 'react'
const timeout = 15000
export const CityCard = ({item, visible, getSelectedCity}) => {
const [showMeta, setShowMeta] = React.useState(false)
const handleClick = React.useCallback(()=>{
setShowMeta(true)
setTimeout(()=>{
setShowMeta(false)
},timeout )
}, [showMeta])
return(
<div className="col-lg-3" key={index}>
<div
className="card"
style={{
textAlign: "center",
display: "flex",
justifyContent: "center",
paddingBottom: "1rem",
marginBottom: "1rem",
marginRight: "1rem",
}}
>
<div className="card-body">
<h5 className="card-title">{item.name}</h5>
</div>
{visible && showMeta ? (
<div>
<p>Longitude: {item.longitude}</p>
<p>Latitude: {item.latitude}</p>
<p>Population: {item.population}</p>
{/* <p>Time Zone: America</p> */}
</div>
) : (
<button
onClick={() => {
handleClick();
getSelectedCity(item.id);
}}
type='button'
disabled={showMeta}
style={{ cursor: "pointer" }}
>
<FaPlus size="18" />
</button>
)}
</div>
</div>
)
}
At the end, add this component to the CityList

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;

ReactJs, Semantic UI , toggle button

Hello i wanted to make a toggle button menu
ie by clicking on the button the div would increase the height with some transition
but I can't imagine how to do this in react
function App() {
const handleClick = e => {
e.preventDefault();
};
return (
<div
className="Wrapper"
style={{
backgroundColor: "#000",
height: "30px",
width: "200px"
}}
>
<Button
style={{ padding: 0, height: "30px", width: "200px" }}
circular
icon="settings"
onClick={handleClick}
>
Button
</Button>
</div>
);
}
my codebox: https://codesandbox.io/s/funny-minsky-j9x6x
You need to add a state variable to your function. You can now use hooks for that.
import React from "react";
import ReactDOM from "react-dom";
import { Button } from "semantic-ui-react";
import "./styles.css";
import { useState } from 'react';
function App() {
const [open, setOpen] = useState(0); // declare new state variable "open" with setter
const handleClick = e => {
e.preventDefault();
setOpen(!open);
};
return (
<div
className="Wrapper"
style={{
backgroundColor: "#000",
height: (open ? "400px" : "30px"), // make the div tall when "open"
width: "200px",
transition: "height 1s" // transition for 1 second
}}
>
<Button
style={{ padding: 0, height: "30px", width: "200px" }}
circular
onClick={handleClick}
>
Button
</Button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

<Link> only changes the url when click but not the components and the page

I have problem with my app. In my header i have Twitter, Youtube, Instgram which is if i click one of them it will show the page from the component. Example if i click instagram it will load Instagram Component and change the page to Instagram. But in my app, after i click one of them the pages not changes and not load the component but the url changes. This is my code :
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import { Row, Col, Card, Layout, Spin } from "antd";
import "antd/dist/antd.css";
import "./index.css";
import cubejs from "#cubejs-client/core";
import { QueryRenderer } from "#cubejs-client/react";
import { Line, Bar, Pie } from "react-chartjs-2";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import Facebook from "./Facebook";
import Youtube from "./Youtube";
import Instagram from "./Instagram";
import Twitter from "./Twitter";
import moment from "moment";
const AppLayout = ({ children }) => (
<Layout>
<Layout.Header>
<div
style={{
float: "left"
}}
>
<h2
style={{
color: "#fff",
margin: 0
}}
>
NARASIDATA
</h2>
</div>
<Router>
<div
style={{
float: "right"
}}
>
<Link to="/facebook/" component={Facebook}>
<h2
style={{
color: "#fff",
margin: 0,
paddingLeft: 50
}}
>
Facebook
</h2>
</Link>
</div>
<div
style={{
float: "right"
}}
>
<Link to="/instagram/">
<h2
style={{
color: "#fff",
margin: 0,
paddingLeft: 50
}}
>
Instagram
</h2>
</Link>
</div>
<div
style={{
float: "right"
}}
>
<Link to="/youtube/">
<h2
style={{
color: "#fff",
margin: 0,
paddingLeft: 50
}}
>
Youtube
</h2>
</Link>
</div>
<div
style={{
float: "right"
}}
>
<Link to="/twitter/">
<h2
style={{
color: "#fff",
margin: 0,
paddingLeft: 50
}}
>
Twitter
</h2>
</Link>
</div>
</Router>
</Layout.Header>
<Layout.Content
style={{
padding: "0 25px 25px 25px",
margin: "25px"
}}
>
{children}
</Layout.Content>
</Layout>
);
const Dashboard = ({ children }) => (
<Row type="flex" justify="space-around" align="top" gutter={24}>
{children}
</Row>
);
const DashboardItem = ({ children, title }) => (
<Col span={24} lg={12}>
<Card
title={title}
style={{
marginBottom: "24px"
}}
>
{children}
</Card>
</Col>
);
const COLORS_SERIES = ["#FF6492", "#141446", "#7A77FF"];
const lineRender = ({ resultSet }) => {
const data = {
labels: resultSet.categories().map(c => c.category),
datasets: resultSet.series().map((s, index) => ({
label: s.title,
data: s.series.map(r => r.value),
borderColor: COLORS_SERIES[index],
fill: false
}))
};
const options = {};
return <Line data={data} options={options} />;
};
const API_URL = "http://localhost:4000";
const cubejsApi = cubejs(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1NjI4MjQ2NjYsImV4cCI6MTU2MjkxMTA2Nn0.h9s4qtiFSia8vHqtyYNwkJDihh-_NcCD57wozOmmz4k",
{
apiUrl: API_URL + "/cubejs-api/v1"
}
);
const renderChart = Component => ({ resultSet, error }) =>
(resultSet && <Component resultSet={resultSet} />) ||
(error && error.toString()) || <Spin />;
function App() {
return (
<div className="App">
<AppLayout>
<Dashboard>
<DashboardItem>
<Router>
<Route exact path="/twitter/" component={Twitter} />
<Route exact path="/youtube/" component={Youtube} />
<Route exact path="/instagram/" component={Instagram} />
<Route exact path="/facebook/" component={Facebook} />
</Router>
<QueryRenderer
query={{
measures: ["Twitter.retweetcount"],
timeDimensions: [
{
dimension: "Twitter.publishat",
granularity: "day",
dateRange: "This month"
}
],
filters: [
{
dimension: "Twitter.username",
operator: "equals",
values: ["narasitv"]
}
]
}}
cubejsApi={cubejsApi}
render={renderChart(lineRender)}
/>
</DashboardItem>
</Dashboard>
</AppLayout>
</div>
);
}
export default App;
What's the problem in my app?
I am new in react.
You should only have one <Router /> so remove the one around your links in AppLayout and wrap your whole App render in <Router /> like this:
<Router>
<AppLayout>
<Route />
<Route />
// ...
</AppLayout>
</Router>
Also you should replace Router by a BrowserRouter https://reacttraining.com/react-router/web/api/BrowserRouter

Categories

Resources