I am trying to Redirect or Switch the following Hero React component after a 5 second delay using React-Router.
<Redirect to="/home" /> redirect's the component to http://url.com/home instantly.
Any suggestions on how to handle this? Thx.
class Intro extends Component {
render() {
return (
<div className="d-flex w-100 h-100 mx-auto p-4 flex-column">
<Header />
<div
style={{
paddingTop: '40px'
}}>
</div>
<Hero />
<Footer />
</div>
);
}
}
export default Intro;
I've also tried using setTimeout as well
You can use the history prop injected by react-router and invoke the push method inside a setTimeout. You should do it in componentDidMount:
constructor(props) {
// ...
this.redirectTimeout = null;
}
componentDidMount() {
const { history } = this.props;
this.redirectTimeout = setTimeout(() => {
history.push('/home')
}, 5000);
}
Edit: Make sure to clear the timeout to avoid redirects when you move out of the page before the 5 seconds are over, else you will still get redirected:
componentWillUnmount() {
clearTimeout(this.redirectTimeout);
}
You can create a custom DelayRedirect component.
Gist here: https://gist.github.com/JT501/4b0f60fb57b1410604b754fd9031150a
import * as React from 'react';
import { useEffect, useState } from 'react';
import { Redirect, RedirectProps } from 'react-router';
interface DelayProps {
delay: number;
}
const DelayRedirect = ({ delay, ...rest }: RedirectProps & DelayProps) => {
const [timeToRedirect, setTimeToRedirect] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => {
setTimeToRedirect(true);
}, delay);
return () => clearTimeout(timeout);
}, [delay]);
return timeToRedirect ? <Redirect {...rest} /> : null;
};
export default DelayRedirect;
Usage:
<DelayRedirect to={'/'} delay={1000} />
Related
My context is as follows:
import React, {createContext, useEffect, useState} from "react";
export const CartContext = createContext();
const CartContextProvider = (props) => {
const [cart, setCart] = useState(JSON.parse(localStorage.getItem('cart')) || []);
useEffect(() => {
localStorage.setItem('cart', JSON.stringify(cart));
}, [cart]);
const updateCart = (productId, op) => {
let updatedCart = [...cart];
if (updatedCart.find(item => item.id === productId)) {
let objIndex = updatedCart.findIndex((item => item.id === productId));
if (op === '-' && updatedCart[objIndex].qty > 1) {
updatedCart[objIndex].qty -= 1;
} else if (op === '+') {
updatedCart[objIndex].qty += 1;
}
} else {
updatedCart.push({id: productId, qty: 1})
}
setCart(updatedCart);
}
const removeItem = (id) => {
setCart(cart.filter(item => item.id !== id));
};
return (
<CartContext.Provider value={{cart, updateCart, removeItem}}>
{props.children}
</CartContext.Provider>
)
};
export default CartContextProvider;
App.js:
import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import NavigationBar from "./components/layout/navigationBar/NavigationBar";
import Homepage from "./pages/homepage/Homepage";
import AboutUsPage from "./pages/aboutUs/AboutUsPage";
import ContactPage from "./pages/contact/ContactPage";
import SearchPage from "./pages/search/SearchPage";
import ShoppingCart from "./components/layout/shoppingCart/ShoppingCart";
import CartContextProvider from "./context/CartContext";
function App() {
return (
<div>
<CartContextProvider>
<Router>
<NavigationBar/>
<ShoppingCart/>
<Routes>
<Route exact path="/" element={<Homepage/>}/>
<Route path="/a-propos" element={<AboutUsPage/>} />
<Route path="/contact" element={<ContactPage/>}/>
<Route path="/recherche" element={<SearchPage/>}/>
</Routes>
</Router>
</CartContextProvider>
</div>
);
}
export default App;
In the component ShoppingCart I am using another component ShoppingCartQuantity which in turn makes use of the context. It works as it should.
Here's the ShoppingCartQuantity component:
import React, {useContext} from "react";
import {CartContext} from "../../../context/CartContext";
import styles from './ShoppingCartQuantity.module.css'
const ShoppingCartQuantity = ({productId}) => {
const {cart, updateCart} = useContext(CartContext);
let qty = 0;
if (cart.find((item => item.id === productId))) {
let objIndex = cart.findIndex((item => item.id === productId));
qty = cart[objIndex].qty;
}
return (
<div>
<span>
<span className={`${styles.op} ${styles.decrementBtn}`} onClick={() => updateCart(productId, '-')}>-</span>
<span className={styles.qty}>{qty}</span>
<span className={`${styles.op} ${styles.incrementBtn}`} onClick={() => updateCart(productId, '+')}>+</span>
</span>
</div>
)
}
export default ShoppingCartQuantity;
Now I am trying to use the ShoppingCartQuantity component in the Homepage component which is a route element (refer to App.js) but getting the error Uncaught TypeError: Cannot destructure property 'cart' of '(0 , react__WEBPACK_IMPORTED_MODULE_0__.useContext)(...)' as it is undefined.
So the context is working for components outside the router but not for those inside it. If I have wrapped the router within the provider, shouldn't all the route elements get access to the context or am I missing something?
UPDATE
As user Build Though suggested in the comments, I tried using the ShoppingCartQuantity component in another route element and it works fine; so the problem is not with the router!
Below is the code of how I am using the ShoppingCartQuantity component in the Homepage component:
import React, { useState, useEffect, useRef } from "react";
import { Responsive, WidthProvider } from "react-grid-layout";
import Subcat from "../../components/subcat/Subcat";
import CategoryService from "../../services/api/Category";
import SubCategoryService from "../../services/api/SubCategory";
import CategoriesLayout from "../../utils/CategoriesLayout";
import CategoryCard from "../../components/category/CategoryCard";
import { Triangle } from 'react-loader-spinner'
import ScrollIntoView from 'react-scroll-into-view'
import ProductService from "../../services/api/Product";
import Swal from 'sweetalert2'
import withReactContent from 'sweetalert2-react-content';
import YouTube from 'react-youtube';
import FavoriteBtn from "../../components/favorite/FavoriteBtn";
import ShoppingCartQuantity from "../../components/layout/shoppingCart/ShoppingCartQuantity";
import "./Homepage.css";
import "../../components/product/ProductModal.css"
import "react-loader-spinner";
import modalStyles from "../../components/product/ProductModal.module.css"
function Homepage() {
const [categories, setCategories] = useState([]);
const [subCats, setSubCats] = useState([]);
const [loader, setLoader] = useState(false);
const ResponsiveGridLayout = WidthProvider(Responsive);
const scrollRef = useRef();
const productModal = withReactContent(Swal);
const opts = {
// height: '390',
// width: '640',
playerVars: {
autoplay: 1,
}
};
useEffect(() => {
CategoryService.get().then((response) => {
setCategories(response);
});
}, []);
function showSubCatsHandler(catId) {
setLoader(true);
setSubCats([]);
SubCategoryService.get(catId).then((response) => {
setSubCats(response.data);
setLoader(false);
scrollRef.current.scrollIntoView({ behavior: "smooth" });
});
}
function showProductPopupHandler(productId) {
ProductService.get(productId).then((response) => {
const product = response.data;
return productModal.fire({
html:
<div>
<h3 className={modalStyles.header}>{product.AMP_Title}</h3>
<h4 className={`${modalStyles.price} ${modalStyles.header}`}>{"CHf " + product.AMP_Price}</h4>
<img className={modalStyles.image} src={process.env.REACT_APP_BACKEND_BASE_URL + 'images/products/' + product.AMP_Image} />
{
product.descriptions.map((desc, _) => (
<div key={desc.AMPD_GUID}>
{
desc.AMPD_Title === '1' && <h4 className={modalStyles.header}>{product.AMP_Title}</h4>
}
{
desc.AMPD_Image !== '' && <img src={process.env.REACT_APP_BACKEND_BASE_URL + 'images/descriptions/' + desc.AMPD_Image} className={desc.AMPD_Alignment === 'left' ? modalStyles.descImageLeft : modalStyles.descImageRight} />
}
<p className={modalStyles.description}>{desc.AMPD_Description}</p>
</div>
))
}
<br/>
<div>
<FavoriteBtn productId={product.AMP_GUID}/>
<ShoppingCartQuantity productId={product.AMP_GUID} />
</div>
<br/>
{
product.AMP_VideoId !== '' &&
<YouTube
videoId={product.AMP_VideoId}
opts={opts}
/>
}
</div>,
showConfirmButton: false,
showCloseButton: true
});
});
}
return (
<div>
<div className="categories-container">
<ResponsiveGridLayout
className="layout"
layouts={ CategoriesLayout }
breakpoints={ { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 } }
cols={ { lg: 8, md: 8, sm: 6, xs: 4, xxs: 2 } }
isDraggable={ false }
>
{
categories.map((cat, index) => (
<div key={index}>
<CategoryCard
category_id = {cat.AMC_GUID}
image = {cat.AMC_Image}
showSubCatsHandler = {showSubCatsHandler}
/>
</div>
))
}
</ResponsiveGridLayout>
{
loader &&
<Triangle
height="100"
width="100"
color='#bcad70'
ariaLabel='loading'
wrapperClass="loader"
/>
}
<div ref={scrollRef}>
{
Object.keys(subCats).map((keyName, _) => (
<Subcat
key={subCats[keyName].AMSC_GUID}
title={ subCats[keyName].AMSC_Title }
products={ subCats[keyName].products }
showProductPopupHandler = {showProductPopupHandler}
/>
))
}
</div>
</div>
</div>
);
}
export default Homepage;
I am using the component in a SweetAlert popup. I guess it's the SweetAlert component that is not getting access to the context. Does anyone have an idea how to pass the context to the SweetAlert component?
UPDATE 2
The accepted solution works great except for 1 small issue: the ShoppingCartQuantity component was not re-rendering inside the SweetAlert popup and the qty would not change visually.
I updated the component by using the qty as a state.
const ShoppingCartQuantity = ({ qty, productId, updateCart }) => {
const [quantity, setQuantity] = useState(qty);
const updateCartHandler = (productId, amount) => {
updateCart(productId, amount);
setQuantity(Math.max(quantity + amount, 1));
}
return (
<div>
<span>
<span
className={`${styles.op} ${styles.decrementBtn}`}
onClick={() => updateCartHandler(productId, -1)}
>
-
</span>
<span className={styles.qty}>{quantity}</span>
<span
className={`${styles.op} ${styles.incrementBtn}`}
onClick={() => updateCartHandler(productId, 1)}
>
+
</span>
</span>
</div>
)
}
Issue
It's very likely that the sweet alert component is rendered outside your app, and thus, outside the CartContextProvider provider. I just searched the repo docs if there is a way to specify a root element, but this doesn't seem possible since this sweet alert code isn't React specific.
See this other similar issue regarding accessing a Redux context in the alert.
Solution
It doesn't seem possible ATM to access the context value from within the modal, so IMHO a workaround could be to refactor your ShoppingCartQuantity component into a wrapper container component to access the context and a presentation component to receive the context values and any callbacks.
I suggest also just passing the amount you want to increment/decrement the quantity by to updateCart instead of passing a "+"/"-" string and operator comparison.
Example:
export const withShoppingCartContext = Component => props => {
const { cart, removeItem, updateCart } = useContext(CartContext);
return <Component {...props} {...{ cart, removeItem, updateCart }} />;
}
const ShoppingCartQuantity = ({ cart, productId, updateCart }) => {
const qty = cart.find(item => item.id === productId)?.qty ?? 0;
return (
<div>
<span>
<span
className={`${styles.op} ${styles.decrementBtn}`}
onClick={() => updateCart(productId, -1)}
>
-
</span>
<span className={styles.qty}>{qty}</span>
<span
className={`${styles.op} ${styles.incrementBtn}`}
onClick={() => updateCart(productId, 1)}
>
+
</span>
</span>
</div>
)
}
export default ShoppingCartQuantity;
In places in your app where ShoppingCartQuantity component is used within the CartContextProvider decorate it with the withShoppingCartContext HOC and use normally.
ShoppingCart
import ShoppingCartQuantityBase, {
withShoppingCartContext
} from "../../components/layout/shoppingCart/ShoppingCartQuantity";
const ShoppingCartQuantity = withShoppingCartContext(ShoppingCartQuantityBase);
const ShoppingCart = (props) => {
...
return (
...
<ShoppingCartQuantity productId={....} />
...
);
};
In places where ShoppingCartQuantity component is used outside the context, like in the sweet modal, access the context within the React code and pass in the context values and callbacks.
...
import ShoppingCartQuantity from "../../components/layout/shoppingCart/ShoppingCartQuantity";
...
function Homepage() {
...
const { cart, updateCart } = useContext(CartContext);
const productModal = withReactContent(Swal);
...
function showProductPopupHandler(productId) {
ProductService.get(productId)
.then((response) => {
const product = response.data;
return productModal.fire({
html:
<div>
...
<div>
<FavoriteBtn productId={product.AMP_GUID}/>
<ShoppingCartQuantity
productId={product.AMP_GUID}
{...{ cart, updateCart }}
/>
</div>
...
</div>,
showConfirmButton: false,
showCloseButton: true
});
});
}
return (...);
}
export default Homepage;
Additional Issues
Your context provider is mutating state when updating quantities. When updating nested state you should still create a shallow copy of the array elements that are being updated.
Example:
const CartContextProvider = (props) => {
...
const updateCart = (productId, amount) => {
// only update if item in cart
if (cart.some(item => item.id === productId)) {
// use functional state update to update from previous state
// cart.map creates shallow copy of previous state
setCart(cart => cart.map(item => item.id === productId
? {
...item, // copy item being updated into new object reference
qty: Math.max(item.qty + amount, 1), // minimum quantity is 1
}
: item
));
}
}
const removeItem = (id) => {
setCart(cart => cart.filter(item => item.id !== id));
};
return (
<CartContext.Provider value={{ cart, updateCart, removeItem }}>
{props.children}
</CartContext.Provider>
);
};
You did't show where you are using the ShoppingCart component or the ShoppingCartQuantity component.
Anyway, when you declare a route, you must pass the component, not the root element. So, this line:
<Route exact path="/" element={<Homepage/>}/>
must be
<Route exact path="/" component={Homepage}/>
I have a navbar component that has a submenu. Once logged in, the submenu of the navbar should change.
I used the hook useContext, but it doesn't refresh the navbar component when the user logs. It works fine when I refresh the page.
Where is my code problem?
APP COMPONENT
import React, { useState, useEffect } from "react";
import logo from "./assets/logoBusca.png";
import "./App.css";
import { Route } from "wouter";
import Home from "./components/Home";
import Login from "./components/Login";
import Post from "./components/Post";
import NavbarUser from "./components/NavbarUser";
import { AuthContext } from "./context/AuthContext";
import logic from "../src/logic";
function App() {
const [user, setUser] = useState(false);
useEffect(() => {
(async () => {
const loggedIn = await logic.isUserLoggedIn;
if (loggedIn) setUser(true);
})();
}, [user]);
return (
<AuthContext.Provider value={user}>
<div className="App">
<header className="App-header">
<div className="images">
<div className="logo">
<a href="/">
<img src={logo} alt="logo" />
</a>
</div>
<div className="user_flags">
<NavbarUser />
</div>
</div>
</header>
<Route path="/">
<Home />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path="/nuevabusqueda">
<Post />
</Route>
</div>
</AuthContext.Provider>
);
}
export default App;
NAVBAR COMPONENT
import React, { useContext } from "react";
import userIcon from "../../assets/userIcon.png";
import { AuthContext } from "../../context/AuthContext";
export default function NavbarUser() {
const isAuthenticated = useContext(AuthContext);
return (
<>
{!isAuthenticated ? (
<div className="navbar-item has-dropdown is-hoverable">
<img src={userIcon} alt="user" />
<div className="navbar-dropdown">
<a href="/login" className="navbar-item" id="item_login">
Login
</a>
<hr className="navbar-divider" />
<a href="/registro" className="navbar-item" id="item_register">
Registro
</a>
</div>
</div>
) : (
<div className="navbar-item has-dropdown is-hoverable">
<img src={userIcon} alt="user" />
<div className="navbar-dropdown">
<a href="/datos" className="navbar-item" id="item_login">
Perfil
</a>
<hr className="navbar-divider" />
<a href="/user" className="navbar-item" id="item_register">
Logout
</a>
</div>
</div>
)}
</>
);
}
CONTEXT COMPONENT
import { createContext } from "react";
export const AuthContext = createContext();
LOGIC COMPONENT
import buscasosApi from "../data";
const logic = {
set userToken(token) {
sessionStorage.userToken = token;
},
get userToken() {
if (sessionStorage.userToken === null) return null;
if (sessionStorage.userToken === undefined) return undefined;
return sessionStorage.userToken;
},
get isUserLoggedIn() {
return this.userToken;
},
loginUser(email, password) {
return (async () => {
try {
const { token } = await buscasosApi.authenticateUser(email, password);
this.userToken = token;
} catch (error) {
throw new Error(error.message);
}
})();
},
};
export default logic;
I think Login component doesn't call setUser(true)
Here's an example how it might work.
const { useState, useEffect, createContext, useContext } = React;
const AuthContext = createContext();
const Login = () => {
const [isAuthenticated, setAuth] = useContext(AuthContext);
const onClick = () => setAuth(true);
return <button disabled={isAuthenticated} onClick={onClick}>Login</button>
}
const App = () => {
const [isAuthenticated] = useContext(AuthContext);
return <div>
<Login/>
<button disabled={!isAuthenticated}>{isAuthenticated ? "Authenticated" : "Not Authenticated"}</button>
</div>;
}
const AuthProvider = ({children}) => {
const [isAuthenticated, setAuth] = useState(false);
return <AuthContext.Provider value={[isAuthenticated, setAuth]}>
{children}
</AuthContext.Provider>;
}
ReactDOM.render(
<AuthProvider>
<App />
</AuthProvider>,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
It seems to me that you are not updating state. Initially your state is false. After first render, your effect is fired and state is again set to false. After that your effect don't run anymore, since it depends on state that it should change. No state change - no effect - no state change again. Also, if state will change, it will trigger effect, but you will have no need for this.
Your goal here is to build a system that will:
Check current status on load
Update (or recheck) status when changed
To do this, you need some way to asynchronously send messages from logic to react. You can do this with some kind of subscription, like this:
const logic = {
set userToken(token) {
sessionStorage.userToken = token;
},
get userToken() {
if (sessionStorage.userToken === null) return null;
if (sessionStorage.userToken === undefined) return undefined;
return sessionStorage.userToken;
},
get isUserLoggedIn() {
return this.userToken;
},
loginUser(email, password) {
return (async () => {
try {
const { token } = await buscasosApi.authenticateUser(email, password);
this.userToken = token;
this.notify(true);
} catch (error) {
throw new Error(error.message);
}
})();
},
subscribers: new Set(),
subscribe(fn) {
this.subscribers.add(fn);
return () => {
this.subscribers.remove(fn);
};
},
notify(status) {
this.subscribers.forEach((fn) => fn(status));
},
};
function useAuthStatus() {
let [state, setState] = useState("checking");
let setStatus = useCallback(
(status) => setState(status ? "authenticated" : "not_authenticated"),
[setState]
);
useEffect(function () {
return logic.subscribe(setStatus);
}, []);
useEffect(function () {
setStatus(logic.isUserLoggedIn);
}, []);
return state;
}
Notice, that now there is three possible states - 'checking', 'authenticated' and 'not_authenticated'. It is more detailed and will prevent some errors. For example if you would want to redirect user to login page when they are not authenticated.
I've been playing around with the react context api and I'm just not getting why it's not working.
I have a component with a container that should show or hide depending on a valuer stored in context.
This is the component:
import React, { useContext } from 'react';
import ResultsContext from '../../context/results/resultsContext';
const ResultsPanelContainer = () => {
const resultsContext = useContext(ResultsContext);
const { showResults } = resultsContext;
console.log('showResults in ResultsPanelConatiner: ', showResults);
return (
<div
className='container-fluid panel'
style={{ display: showResults ? 'block' : 'none' }}
>
<div className='container'>
<div className='row'>
<div className='col'>
<h1 className='display-4'>Results.Panel.js</h1>
</div>
</div>
</div>
</div>
);
};
export default ResultsPanelContainer;
For completeness, the context is divided up into three sections, the call to the context itself, a 'state' file and a reducer. These are displayed below:
resultsContext.js
import { createContext } from 'react';
const resultsContext = createContext();
export default resultsContext;
ResultsState.js
import React, { useReducer } from 'react';
// import axios from 'axios';
import ResultsContext from './resultsContext';
import ResultsReducer from './resultsReducer';
import { UPDATE_SHOW_RESULTS } from '../types';
const ResultsState = (props) => {
const initialState = {
showResults: false,
};
const [state, dispatch] = useReducer(ResultsReducer, initialState);
const updateShowResults = (data) => {
console.log('updateShowResults - ', data);
dispatch({
type: UPDATE_SHOW_RESULTS,
payload: data,
});
};
return (
<ResultsContext.Provider
value={{
showResults: state.showResults,
updateShowResults,
}}
>
{props.children}
</ResultsContext.Provider>
);
};
export default ResultsState;
resultsReducer.js
import { UPDATE_SHOW_RESULTS } from '../types';
export default (state, action) => {
switch (action.type) {
case UPDATE_SHOW_RESULTS:
return {
...state,
showResults: action.payload,
};
default:
return state;
}
};
The change is triggered by a button click in a separate component and this does trigger an update in the context as shown when you log it to the console. However, the component is not rerendering.
I understand from reading various answers on here that changing context doesn't trigger a rerender of all child components in the same way that setState does. However, the component displaying this is calling the context directly so as far as I can see the rerender should take effect.
Am I missing something glaringly obvious?
Thanks in advance.
Stef
Forget the above... I'm an idiot - wrapped the two separate parts of the app in two separate instances of ResultsState which weren't communicating. Did this:
const App = () => {
return (
<Fragment>
<UsedDataState>
<Header />
</UsedDataState>
<main>
<ExportPanelContainer />
<ResultsState>
<SendQueryState>
<OrQueryState>
<AndQueryState>
<QueryPanelContainer />
</AndQueryState>
</OrQueryState>
</SendQueryState>
</ResultsState>
<ResultsState>
<ResultsPanelContainer />
</ResultsState>
</main>
</Fragment>
);
};
Instead of this:
const App = () => {
return (
<Fragment>
<UsedDataState>
<Header />
</UsedDataState>
<main>
<ExportPanelContainer />
<ResultsState>
<SendQueryState>
<OrQueryState>
<AndQueryState>
<QueryPanelContainer />
</AndQueryState>
</OrQueryState>
</SendQueryState>
<ResultsPanelContainer />
</ResultsState>
</main>
</Fragment>
);
};
Hope this is useful for someone else...
I am trying to use the react.js spinner component and I can't figure out how I to hide it once my task is complete.
Here is a simple codepen where I am using hidden property. I will set it up to false or true depending on if I want to show it or not:
https://codepen.io/manish_shukla01/pen/GLNOyw
<Spinner hidden={true} size={SpinnerSize.large} label="manish's large spinned" />
You need to use conditional rendering to hide/show the spinner. You can define a component state then can set it up to false or true depending on if you want to show it or not.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
hidden: false
};
}
render() {
return (
<div className="App">
{!this.state.hidden && <SpinnerBasicExample />}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
For more you can read this https://reactjs.org/docs/conditional-rendering.html
Conditional rendering using state.
In React, you can create distinct components that encapsulate behavior you need. Then, you can render only some of them, depending on the state of your application.
Working example (click on the Dashboard tab):
containers/Dashboard
import isEmpty from "lodash/isEmpty";
import React, { Component } from "react";
import api from "../../utils/api";
import DisplayUser from "../../components/DisplayUser";
import DisplaySignUp from "../../components/DisplaySignUp";
import Spinner from "../../components/Spinner";
class Dashboard extends Component {
state = {
isLoading: true,
currentUser: {}
};
componentDidMount = () => {
this.fetchUsers();
};
fetchUsers = async () => {
try {
const res = await api.get(`/users/1`);
setTimeout(() => {
this.setState({ currentUser: res.data, isLoading: false });
}, 1500);
} catch (e) {
console.error(e.toString());
}
};
// the below can be read like so:
// if "isLoading" is true... then display a spinner
// else if "currentUser" is not empty... then display the user details
// else show a signup message
render = () =>
this.state.isLoading ? (
<Spinner />
) : !isEmpty(this.state.currentUser) ? (
<DisplayUser currentUser={this.state.currentUser} />
) : (
<DisplaySignUp />
);
}
export default Dashboard;
For what you intend to do, just adding the hidden prop won't work as that is not an expected attribute of the Spinner component. I think what you need to do is to introduce conditional rendering in your component. Kindly see implementation below:
import * as React from 'react';
import { render } from 'react-dom';
import {
PrimaryButton,
Spinner,
SpinnerSize,
Label,
IStackProps,
Stack
} from 'office-ui-fabric-react';
import './styles.css';
const { useState } = React;
const SpinnerBasicExample: React.StatelessComponent = () => {
// This is just for laying out the label and spinner (spinners don't have to be inside a Stack)
const rowProps: IStackProps = { horizontal: true, verticalAlign: 'center' };
const tokens = {
sectionStack: {
childrenGap: 10
},
spinnerStack: {
childrenGap: 20
}
};
return (
<Stack tokens={tokens.sectionStack}>
<Stack {...rowProps} tokens={tokens.spinnerStack}>
<Label>Extra small spinner</Label>
<Spinner size={SpinnerSize.xSmall} />
</Stack>
<Stack {...rowProps} tokens={tokens.spinnerStack}>
<Label>Small spinner</Label>
<Spinner size={SpinnerSize.small} />
</Stack>
</Stack>
);
};
function App() {
const [hidden, setHidden] = useState(false);
return (
<div className="App">
{hidden && <SpinnerBasicExample />}
<PrimaryButton
data-automation-id="test"
text={!hidden ? 'Show spinner' : 'Hide spinner'}
onClick={() => setHidden(!hidden)}
allowDisabledFocus={true}
/>
</div>
);
}
How can I set a delay function on React.js? Is there any way to add or remove class in react routing so that page could be transition? Add, remove or toggle class should work every time. Is it possible to add, remove or toggle class on routing with a delay function? or can I use a third party library for that?
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
const BasicExample = () => (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<hr />
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/topics" component={Topics} />
</div>
</Router>
);
const Home = () => (
<div>
<h2>Home</h2>
</div>
);
const About = () => (
<div>
<h2>About</h2>
</div>
);
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${match.url}/components`}>Components</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>Props v. State</Link>
</li>
</ul>
<Route path={`${match.url}/:topicId`} component={Topic} />
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
</div>
);
const Topic = ({ match }) => (
<div>
<h3>{match.params.topicId}</h3>
</div>
);
export default BasicExample;
I modified the above answer from Paul into a functional component that is more up to date:
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import { Link, useHistory, useLocation } from "react-router-dom";
// Functional link component which delays page navigation
export const DelayLink = props => {
const { delay, onDelayStart, onDelayEnd, replace, to, ...rest } = props;
let timeout = null;
let history = useHistory();
let location = useLocation();
useEffect(() => {
return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}, [timeout]);
const handleClick = e => {
// if trying to navigate to current page stop everything
if (location?.pathname === to) return;
onDelayStart(e, to);
if (e.defaultPrevented) {
return;
}
e.preventDefault();
timeout = setTimeout(() => {
if (replace) {
history.replace(to);
} else {
history.push(to);
}
onDelayEnd(e, to);
}, delay);
};
return <Link {...rest} to={to} onClick={handleClick} />;
};
DelayLink.propTypes = {
// Milliseconds to wait before registering the click.
delay: PropTypes.number,
// Called after the link is clicked and before the delay timer starts.
onDelayStart: PropTypes.func,
// Called after the delay timer ends.
onDelayEnd: PropTypes.func,
// Replace history or not
replace: PropTypes.bool,
// Link to go to
to: PropTypes.string
};
DelayLink.defaultProps = {
replace: false,
delay: 0,
onDelayStart: () => {},
onDelayEnd: () => {}
};
export default DelayLink;
Gist
https://gist.github.com/headzoo/8f4c6a5e843ec26abdcad87cd93e3e2e
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
/**
* Wraps the React Router Link component and creates a delay after the link is clicked.
*/
export default class DelayLink extends React.Component {
static propTypes = {
/**
* Milliseconds to wait before registering the click.
*/
delay: PropTypes.number,
/**
* Called after the link is clicked and before the delay timer starts.
*/
onDelayStart: PropTypes.func,
/**
* Called after the delay timer ends.
*/
onDelayEnd: PropTypes.func
};
static defaultProps = {
delay: 0,
onDelayStart: () => {},
onDelayEnd: () => {}
};
static contextTypes = Link.contextTypes;
constructor(props) {
super(props);
this.timeout = null;
}
componentWillUnmount() {
if (this.timeout) {
clearTimeout(this.timeout);
}
}
/**
* Called when the link is clicked
*
* #param {Event} e
*/
handleClick = (e) => {
const { replace, to, delay, onDelayStart, onDelayEnd } = this.props;
const { history } = this.context.router;
onDelayStart(e, to);
if (e.defaultPrevented) {
return;
}
e.preventDefault();
this.timeout = setTimeout(() => {
if (replace) {
history.replace(to);
} else {
history.push(to);
}
onDelayEnd(e, to);
}, delay);
};
render() {
const props = Object.assign({}, this.props);
delete props.delay;
delete props.onDelayStart;
delete props.onDelayEnd;
return (
<Link {...props} onClick={this.handleClick} />
);
}
}
Recently had the same problem I needed to solve. Made this package to help anyone that needs to do the same.
https://www.npmjs.com/package/delay-react-route-exit
for me this is the most clear way I use always
import { useNavigate } from "react-router-dom";
function Component() {
const navigate = useNavigate()
// this is a function which returning a promise in the requested time
function wait(time) {
return new Promise(resolve => {
setTimeout(resolve, time);
});
}
// this is the routing function
async function goToPage() {
// it will navigate to the page 500ms after clicing the div
await wait(500);
navigate(`/movie/${whatever}`);
}
return <div onClick={goToPage}>
}
I modified the above answer from Kim into a v6 hook (useNavigate) from v5 hook(useHistory).
import { Link, useNavigate, useLocation } from "react-router-dom";
...
...
let navigate = useNavigate();
...
...
timeout = setTimeout(() => {
if (replace) {
navigate(to, { replace: true });
} else {
navigate(to);
}
onDelayEnd(e, to);
}, delay);
Gist