how to reload a page only once react? - reactjs

I have a page that I need to refresh, but only one time after it loads. If I put window.location.reload in useeffect(), then it refreshes an infinite amount of times. How do I make it stop reloading the page after one time? I tried boolean values and other stuff but nothing works. It keeps refreshing an infinite amount of times. The route which is linking to page is market.
app.tsx
import React from "react";
import Market from "containers/Market";
import Coins from "containers/Coins";
import PoweredBy from "components/PoweredBy";
import MarketProvider from "store/MarketProvider";
import Exchanges from "components/Exchanges";
import Exchange from "components/Exchange";
import { BrowserRouter as Router, Route, Redirect, Switch, Link, LinkProps } from 'react-router-dom';
import AppBar from '#material-ui/core/AppBar';
import Tabs from '#material-ui/core/Tabs';
import { default as Tab, TabProps } from '#material-ui/core/Tab';
import HomeIcon from '#material-ui/icons/Home';
import CodeIcon from '#material-ui/icons/Code';
import TimelineIcon from '#material-ui/icons/Timeline';
const LinkTab: React.ComponentType<TabProps & LinkProps> = Tab as React.ComponentType<TabProps & LinkProps>;
function NavBar() {
const [value, setValue] = React.useState(0);
// console.log(value);
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setValue(newValue);
};
return (
<div >
<AppBar position="static">
<Tabs>
<Tab label="Exchanges" to="/exchange" component={Link} />
<Tab label="Coins" to="/" component={Link} />
<Tab label="Home" to="/pm" component={Link} />
</Tabs>
</AppBar>
</div>
)
};
const App = () => {
return (
<Router>
<div>
<NavBar />
<Switch>
<Route exact path="/" component={Coins} />
<Route exact path="/exchange" component={Exchanges} />
<Route exact path="/market" >
<MarketProvider>
<Market />
</MarketProvider>
</Route>
<Redirect from="/" to="/" />
</Switch>
</div>
</Router>
);
};
file.tsx
import React from "react";
import { Grid, Snackbar, SnackbarCloseReason } from "#material-ui/core";
import { Skeleton, Alert } from "#material-ui/lab";
import useAxios from "axios-hooks";
import PrimaryChart from "components/PrimaryChart";
import SecondaryChart from "components/SecondaryChart";
import TimeFilterButtons from "components/TimeFilterButtons";
import { SC } from "./styled";
import { DataProps } from "interfaces/DataProps";
import useWindowDimensions from "hooks/useWindowDimensions";
import { useQueryParams, StringParam } from "use-query-params";
import { MarketContext } from "store/MarketProvider";
var x = true;
const Market = () => {
const {
filteredDataState: { filteredData },
} = React.useContext(MarketContext);
const [queryParams] = useQueryParams({
id: StringParam,
name: StringParam,
});
const [timeFilter, setTimeFilter] = React.useState<string>("1");
const [isErrorMessage, setIsErrorMessage] = React.useState<string>("");
const [boxWidth, setBoxWidth] = React.useState<number>(0);
const { height } = useWindowDimensions();
const [{ data, loading, error }, fetch] = useAxios(
{
url: `https://api.coingecko.com/api/v3/coins/${queryParams?.id}/market_chart?vs_currency=usd&days=${timeFilter}`,
method: "GET",
},
{ manual: true }
);
const gridItemRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (queryParams.id && queryParams.name) {
fetch();
}
}, [fetch, queryParams, queryParams.id, queryParams.name]);
React.useEffect(() => {
if (error) {
setIsErrorMessage(error.message);
}
}, [error]);
React.useEffect(() => {
const handleResize = (width?: number) => {
setBoxWidth(width || 0);
};
handleResize(gridItemRef.current?.clientWidth || 0);
window.addEventListener("resize", () =>
handleResize(gridItemRef?.current?.clientWidth || 0)
);
return () => {
window.removeEventListener("resize", () => handleResize());
};
}, [gridItemRef]);
const mappedData: DataProps[] = React.useMemo(() => {
return data
? data?.prices.map((ele: any) => ({
date: new Date(ele[0]),
price: ele[1],
}))
: [];
}, [data]);
const handleError = (
e: React.SyntheticEvent<any>,
reason?: SnackbarCloseReason
) => {
setIsErrorMessage("");
};
return (
<Grid container justify="center">
<Grid ref={gridItemRef} item xs={12} md={10} lg={8}>
<SC.MarketHeader>
<SC.Title>{queryParams?.name}</SC.Title>
<TimeFilterButtons
value={timeFilter}
onChange={(v) => setTimeFilter(v || "")}
/>
</SC.MarketHeader>
{loading ? (
<Skeleton
variant="rect"
height={Math.floor(height * 0.6)}
width={boxWidth}
/>
) : mappedData?.length ? (
<>
<PrimaryChart
data={filteredData ?? []}
height={Math.floor(height * 0.4)}
width={boxWidth}
margin={{
top: 16,
right: 16,
bottom: 40,
left: 48,
}}
/>
<SecondaryChart
data={mappedData ?? []}
height={Math.floor(height * 0.1)}
width={boxWidth}
margin={{
top: 0,
right: 16,
bottom: 24,
left: 48,
}}
/>
</>
) : null}
</Grid>
<Snackbar open={!!isErrorMessage} onClose={handleError}>
<Alert onClose={handleError} severity="error">
{isErrorMessage}
</Alert>
</Snackbar>
</Grid>
);
};
export default Market;

I can't see any reason of reloading bth but I'm thinking if you do conditional rendering and handle with boolean when should reload that's would be ok
something like:
const [hasAlreadyReload, setHasAlreadyReload] = React.useState(false);
...
hasAlreadyReload && <Redirect from="/" to="/" />

Related

How to redirect from page to page by id?

There is a page on which there are many Stories, when you click on any story, a new page with this Story should open. But when I open a new page, I don't get the "id".json of this Story, instead I get undefined.json . Help me please
Code with home page
`
import React, { useEffect, useState } from 'react';
import { Story } from '../components/Story';
import { getStoryIds } from '../services/hnApi';
import { useInfiniteScroll } from '../hooks/useInfiniteScroll';
export const Home = () => {
const {count} = useInfiniteScroll();
const [storyIds, setStoryIds] = useState([]);
useEffect(() => {
getStoryIds().then(data => setStoryIds(data));
}, []);
return storyIds.slice(0, count).map(storyId => (
<Story key={storyId} storyId={storyId} />
));
};
export default Home;
`
Code with stories
`
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect} from 'react';
import { getStory } from '../services/hnApi';
import { Card, Container, Nav } from 'react-bootstrap'
import { mapTime } from '../mappers/mapTime';
import { Link } from 'react-router-dom';
export const Story = ({storyId}) => {
const[story, setStory] = useState({});
const {id} = story;
useEffect(() => {
const timer = setTimeout(() => {
window.location.reload();
}, 60000);
return () => clearTimeout(timer);
}, []);
useEffect(() => {
getStory(storyId).then((data) => data.url && setStory(data))
}, []);
return story && story.url ? (
<Container data-id={id} fluid="xl" style={{marginTop: "1%", marginBottom: "1%"}}>
<Nav.Item>
<Link to={`/storyPage/${id}`} state={{ id: 'id' }}
style={{ textDecoration: 'none' }}>
<Card style={{ paddingLeft: "1%", paddingTop: "1%", paddingBottom: "1%"}}>
<Card.Title>{story.title}</Card.Title>
<br/>
<Card.Text style={{color: 'black', fontStyle: 'italic', fontSize: '15px'}}>Author: {story.by}</Card.Text>
<Card.Text style={{color: 'black', fontStyle: 'italic', fontSize: '15px'}}>Posted: {mapTime(story.time)}</Card.Text>
<Card.Text style={{color: 'black', fontStyle: 'italic', fontSize: '15px'}}>Rating: {story.score}</Card.Text>
</Card>
</Link>
</Nav.Item>
</Container>
):null;
};
export default Story;
`
The code that should open the story I need
`
import React, { useEffect, useState } from 'react';
import { getStoryIds } from '../services/hnApi';
import { Button, Container } from 'react-bootstrap';
import { ArrowLeft } from 'react-bootstrap-icons';
import { StorySingular } from './StorySingular';
export const StoryPage = ( ) => {
const [storyId, setStoryId] = useState([]);
const {id} = storyId;
useEffect(() => {
getStoryIds().then(data => setStoryId(data));
}, []);
return storyId.slice(0, 1).map(storyId => (
<Container key={storyId}>
<Button style={{marginLeft: '1%', marginTop:'1%'}} variant="outline-info" href='/'><ArrowLeft/></Button>
<br/>
<StorySingular storyId={id} />
</Container>
));
};
export default StoryPage;
`
`
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect, useState } from 'react';
import { getStory } from '../services/hnApi';
import { Card, Container, Nav } from 'react-bootstrap';
import { Comments } from '../comments/Comments';
import { mapTime } from '../mappers/mapTime';
export const StorySingular = ({ storyId }) => {
const [story, setStory] = useState({});
const { kids, id, url } = story;
useEffect(() => {
getStory(storyId).then((data) => {
if (data && data.url) {
setStory(data);
}
});
}, []);
return story && url ? (
<>
<Container data-id={id}>
<Card style={{paddingLeft: "1%", paddingTop: "1%"}}>
<Card.Title style={{paddingLeft: "1%"}}>{story.title}</Card.Title>
<Nav.Link href={story.url}>{url}</Nav.Link>
<br/>
<Card.Text style={{color: 'black', fontStyle: 'italic',paddingLeft: "1%", fontSize: '14px'}}>Author: {story.by}</Card.Text>
<Card.Text style={{color: 'black', fontStyle: 'italic',paddingLeft: "1%", fontSize: '14px'}}>Posted: {mapTime(story.time)}</Card.Text>
<Card.Text style={{color: 'black', fontStyle: 'italic',paddingLeft: "1%", fontSize: '14px'}}>Comments: {story.descendants}</Card.Text>
<Card.Text>{kids && <Comments commentIds={kids} root />}</Card.Text>
</Card>
</Container>
</>
): null;
};
export default StorySingular;
`
<Router>
<Switch>
<Route exact path="/storyPage/:id" component={StoryPage} />
</Switch>
</Router>
Issue
From what I can understand of the code, the Home component fetches the story ids and maps them to an array of Story components.
export const Home = () => {
const {count} = useInfiniteScroll();
const [storyIds, setStoryIds] = useState([]);
useEffect(() => {
getStoryIds().then(data => setStoryIds(data));
}, []);
return storyIds.slice(0, count).map(storyId => (
<Story key={storyId} storyId={storyId} />
));
};
The Story component then takes the passed storyId prop and fetches the specified story by id and renders it to a link and some basic details in a card component.
export const Story = ({ storyId }) => {
const[story, setStory] = useState({});
const {id} = story;
useEffect(() => {
const timer = setTimeout(() => {
window.location.reload();
}, 60000);
return () => clearTimeout(timer);
}, []);
useEffect(() => {
getStory(storyId).then((data) => data.url && setStory(data))
}, []);
return story && story.url ? (
<Container data-id={id} fluid="xl" style={{ marginTop: "1%", marginBottom: "1%" }}>
<Nav.Item>
<Link
to={`/storyPage/${id}`}
state={{ id: 'id' }}
style={{ textDecoration: 'none' }}
>
....
</Link>
</Nav.Item>
</Container>
) : null;
};
export default Story;
The StoryPage component is rendered on the path being linked to:
<Route exact path="/storyPage/:id" component={StoryPage} />
It's from here where it looks like the logic goes a little sideways. The StoryPage component requests all the story ids again instead of fetching a specific story. This should be the same array of ids that was fetched by the Home component.
export const StoryPage = () => {
const [storyId, setStoryId] = useState([]);
const { id } = storyId;
useEffect(() => {
getStoryIds().then(data => setStoryId(data)); // <-- fetches ids again
}, []);
return storyId.slice(0, 1).map(storyId => (
<Container key={storyId}>
<Button
style={{ marginLeft: '1%', marginTop: '1%' }}
variant="outline-info"
href='/'
>
<ArrowLeft />
</Button>
<br/>
<StorySingular storyId={id} />
</Container>
));
};
The StorySingular component then appears to fetch the specific story by id.
export const StorySingular = ({ storyId }) => {
const [story, setStory] = useState({});
const { kids, id, url } = story;
useEffect(() => {
getStory(storyId).then((data) => {
if (data && data.url) {
setStory(data);
}
});
}, []);
return story && url ? (
<>
....
</>
) : null;
};
Solution
I believe your UI could be simplified a bit. The Home component fetches the story ids and rendered them with links to specific story.
<Router>
<Switch>
<Route path="/storyPage/:id" component={StoryPage} />
<Route path="/" component={Home} />
</Switch>
</Router>
StoryPage is updated to read the story id value from the route path params and fetch the specific story to be rendered.
import { useParams } from 'react-router-dom';
export const StoryPage = () => {
const { id } = useParams();
const [story, setStory] = useState();
useEffect(() => {
getStory(id)
.then((data) => {
if (data && data.url) {
setStory(data);
}
});
}, [id]); // <-- fetch story when id changes
return (
<Container>
<Button
style={{ marginLeft: '1%', marginTop: '1%'}}
variant="outline-info"
href='/'
>
<ArrowLeft />
</Button>
<br/>
{story && <StorySingular story={story} />}
</Container>
);
};
...
export const StorySingular = ({ story }) => {
const { kids, id, url } = story;
return story && url ? (
<Container data-id={id}>
<Card .... >
<Card.Title .... >
{story.title}
</Card.Title>
<Nav.Link href={story.url}>{url}</Nav.Link>
<br/>
<Card.Text .... >
Author: {story.by}
</Card.Text>
<Card.Text .... >
Posted: {mapTime(story.time)}
</Card.Text>
<Card.Text .... >
Comments: {story.descendants}
</Card.Text>
<Card.Text>
{kids && <Comments commentIds={kids} root />}
</Card.Text>
</Card>
</Container>
): null;
};

How can i pass a state to the file app.js in react?

I am new to react, i have this code which i got from my manager, i have to add an event, actually on button click i have to add a class to other component, the button is in Header component, and the class i have to add is in LeftNav.
Here is my header.js
export class Header extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
anchorEl: null,
gettingUser: false,
loggingOut: false,
menuOpen: false
};
this.cookies = new Cookies();
}
i made a state menuOpen.
and tried setting state to true like this in header.js
<Button className="menu-btn-wrapper header-button" onClick={() => self.setState({ menuOpen: true })}>
<i className="icon circle zf-cs-icon-burger"></i>
</Button>
and moving on to my app.js, i have this
<Header history={history} />
<LeftNav className="leftFixed leftNav" />
How can i get the state of menuOpen from Header here?
i tried
<Header history={history} menuOpen={this.state.menuOpen} />
but i am getting a stateless error.
There is no class present in app.js
Here is my full app.js
import React, { useEffect } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import IdleTimer from 'react-idle-timer';
import Cookies from 'universal-cookie';
import { LinearProgress, IconButton, Tooltip } from '#material-ui/core';
import { Cancel as CancelIcon } from '#material-ui/icons';
import { getToken, updateMaxAge, expirySet } from '../auth';
import { cancelFileUpload, setSnackbar } from '../actions/appActions';
import { logoutUser, saveUserSettings, setAccountActivationCode, setShowConnectingDialog } from '../actions/userActions';
import { privateRoutes, publicRoutes } from '../config/routes';
import { onboardingSteps } from '../constants/onboardingConstants';
import SharedSnackbar from '../components/common/sharedsnackbar';
import ErrorBoundary from '../errorboundary';
import { getOnboardingState } from '../selectors';
import { colors } from '../themes/mender-theme';
import Tracking from '../tracking';
import { getOnboardingComponentFor } from '../utils/onboardingmanager';
import LiveChatBox from './livechatbox';
import ConfirmDismissHelptips from './common/dialogs/confirmdismisshelptips';
import DeviceConnectionDialog from './common/dialogs/deviceconnectiondialog';
import Header from './header/header';
import LeftNav from './leftnav';
import 'react-perfect-scrollbar/dist/css/styles.css';
import PerfectScrollbar from 'react-perfect-scrollbar';
const activationPath = '/activate';
const timeout = 900000; // 15 minutes idle time
const cookies = new Cookies();
export const AppRoot = ({
cancelFileUpload,
currentUser,
history,
logoutUser,
onboardingState,
setAccountActivationCode,
setShowConnectingDialog,
showDeviceConnectionDialog,
showDismissHelptipsDialog,
setSnackbar,
snackbar,
trackingCode,
uploadProgress
}) => {
useEffect(() => {
if (trackingCode) {
if (!cookies.get('_ga')) {
Tracking.cookieconsent().then(({ trackingConsentGiven }) => {
if (trackingConsentGiven) {
Tracking.initialize(trackingCode);
Tracking.pageview();
}
});
} else {
Tracking.initialize(trackingCode);
}
}
history.listen(trackLocationChange);
trackLocationChange(history.location);
}, []);
const trackLocationChange = location => {
// if we're on page whose path might contain sensitive device/ group/ deployment names etc. we sanitize the sent information before submission
let page = location.pathname || '';
if (page.includes('=') && (page.startsWith('/devices') || page.startsWith('/deployments'))) {
const splitter = page.lastIndexOf('/');
const filters = page.slice(splitter + 1);
const keyOnlyFilters = filters.split('&').reduce((accu, item) => `${accu}:${item.split('=')[0]}&`, ''); // assume the keys to filter by are not as revealing as the values things are filtered by
page = `${page.substring(0, splitter)}?${keyOnlyFilters.substring(0, keyOnlyFilters.length - 1)}`; // cut off the last & of the reduced filters string
} else if (page.startsWith(activationPath)) {
setAccountActivationCode(page.substring(activationPath.length + 1));
history.replace('/settings/my-profile');
}
Tracking.pageview(page);
};
const onIdle = () => {
if (expirySet() && currentUser) {
// logout user and warn
return logoutUser('Your session has expired. You have been automatically logged out due to inactivity.').catch(() => updateMaxAge());
}
};
const onboardingComponent = getOnboardingComponentFor(onboardingSteps.ARTIFACT_CREATION_DIALOG, onboardingState);
const containerProps = getToken() ? { id: 'app' } : { className: 'flexbox centered', style: { minHeight: '100vh' } };
return (
<div {...containerProps}>
{getToken() ? (
<>
<IdleTimer element={document} onAction={updateMaxAge} onIdle={onIdle} timeout={timeout} />
<Header history={history} />
<LeftNav className="leftFixed leftNav" />
<PerfectScrollbar className="rightFluid container main">
<ErrorBoundary>{privateRoutes}</ErrorBoundary>
</PerfectScrollbar>
{onboardingComponent ? onboardingComponent : null}
{showDismissHelptipsDialog && <ConfirmDismissHelptips />}
{showDeviceConnectionDialog && <DeviceConnectionDialog onCancel={() => setShowConnectingDialog(false)} />}
{
// eslint-disable-next-line no-undef
ENV === 'production' && <LiveChatBox />
}
</>
) : (
publicRoutes
)}
<SharedSnackbar snackbar={snackbar} setSnackbar={setSnackbar} />
{Boolean(uploadProgress) && (
<div id="progressBarContainer">
<p className="align-center">Upload in progress ({Math.round(uploadProgress)}%)</p>
<LinearProgress variant="determinate" style={{ backgroundColor: colors.grey, gridColumn: 1, margin: '15px 0' }} value={uploadProgress} />
<Tooltip title="Abort" placement="top">
<IconButton onClick={cancelFileUpload}>
<CancelIcon />
</IconButton>
</Tooltip>
</div>
)}
</div>
);
};
const actionCreators = { cancelFileUpload, logoutUser, saveUserSettings, setAccountActivationCode, setShowConnectingDialog, setSnackbar };
const mapStateToProps = state => {
return {
currentUser: state.users.currentUser,
onboardingState: getOnboardingState(state),
showDismissHelptipsDialog: !state.onboarding.complete && state.onboarding.showTipsDialog,
showDeviceConnectionDialog: state.users.showConnectDeviceDialog,
snackbar: state.app.snackbar,
trackingCode: state.app.trackerCode,
uploadProgress: state.app.uploadProgress
};
};
export default compose(withRouter, connect(mapStateToProps, actionCreators))(AppRoot);
and in main.js
export const Main = () =>
render(
<Provider store={store}>
<MuiThemeProvider theme={theme}>
<ErrorBoundary>
<Router basename="/ui/#">
<App />
</Router>
</ErrorBoundary>
</MuiThemeProvider>
</Provider>,
document.getElementById('main') || document.createElement('div')
);
Main();
The root component is AppRoot, that is a Functional Component.
define the menuOpen state in AppRoot with useState:
const [menuOpen,setMenuOpen]=useState(false)
then define a function in AppRoot to handle menuOpen changes:
const handleMenuOpen=()=>{
setMenuOpen(true)
}
then pass the handler function to the Header via props:
<Header history={history} onButtonClick={handleMenuOpen} />
inside the Header:
<Button className="menu-btn-wrapper header-button" onClick={() => this.props.onButtonClick()}>
<i className="icon circle zf-cs-icon-burger"></i>
</Button>
AppRoot after all above steps:
export const AppRoot = ({
cancelFileUpload,
currentUser,
history,
logoutUser,
onboardingState,
setAccountActivationCode,
setShowConnectingDialog,
showDeviceConnectionDialog,
showDismissHelptipsDialog,
setSnackbar,
snackbar,
trackingCode,
uploadProgress
}) => {
const [menuOpen,setMenuOpen]=useState(false)
const handleMenuOpen=()=>{
setMenuOpen(true)
}
useEffect(() => {
if (trackingCode) {
if (!cookies.get('_ga')) {
Tracking.cookieconsent().then(({ trackingConsentGiven }) => {
if (trackingConsentGiven) {
Tracking.initialize(trackingCode);
Tracking.pageview();
}
});
} else {
Tracking.initialize(trackingCode);
}
}
history.listen(trackLocationChange);
trackLocationChange(history.location);
}, []);
const trackLocationChange = location => {
// if we're on page whose path might contain sensitive device/ group/ deployment names etc. we sanitize the sent information before submission
let page = location.pathname || '';
if (page.includes('=') && (page.startsWith('/devices') || page.startsWith('/deployments'))) {
const splitter = page.lastIndexOf('/');
const filters = page.slice(splitter + 1);
const keyOnlyFilters = filters.split('&').reduce((accu, item) => `${accu}:${item.split('=')[0]}&`, ''); // assume the keys to filter by are not as revealing as the values things are filtered by
page = `${page.substring(0, splitter)}?${keyOnlyFilters.substring(0, keyOnlyFilters.length - 1)}`; // cut off the last & of the reduced filters string
} else if (page.startsWith(activationPath)) {
setAccountActivationCode(page.substring(activationPath.length + 1));
history.replace('/settings/my-profile');
}
Tracking.pageview(page);
};
const onIdle = () => {
if (expirySet() && currentUser) {
// logout user and warn
return logoutUser('Your session has expired. You have been automatically logged out due to inactivity.').catch(() => updateMaxAge());
}
};
const onboardingComponent =
getOnboardingComponentFor(onboardingSteps.ARTIFACT_CREATION_DIALOG, onboardingState);
const containerProps = getToken() ? { id: 'app' } : { className: 'flexbox centered', style: { minHeight: '100vh' } };
return (
<div {...containerProps}>
{getToken() ? (
<>
<IdleTimer element={document} onAction={updateMaxAge} onIdle={onIdle} timeout={timeout} />
<Header history={history} onButtonClick={handleMenuOpen} />
<LeftNav className="leftFixed leftNav" />
<PerfectScrollbar className="rightFluid container main">
<ErrorBoundary>{privateRoutes}</ErrorBoundary>
</PerfectScrollbar>
{onboardingComponent ? onboardingComponent : null}
{showDismissHelptipsDialog && <ConfirmDismissHelptips />}
{showDeviceConnectionDialog && <DeviceConnectionDialog onCancel={() => setShowConnectingDialog(false)} />}
{
// eslint-disable-next-line no-undef
ENV === 'production' && <LiveChatBox />
}
</>
) : (
publicRoutes
)}
<SharedSnackbar snackbar={snackbar} setSnackbar={setSnackbar} />
{Boolean(uploadProgress) && (
<div id="progressBarContainer">
<p className="align-center">Upload in progress ({Math.round(uploadProgress)}%)</p>
<LinearProgress variant="determinate" style={{ backgroundColor: colors.grey, gridColumn: 1, margin: '15px 0' }} value={uploadProgress} />
<Tooltip title="Abort" placement="top">
<IconButton onClick={cancelFileUpload}>
<CancelIcon />
</IconButton>
</Tooltip>
</div>
)}
</div>
);
};

Passing props through multiple components (four), parent State not updating

I have a React ecommerce site that is currently integrated with Stripe. When a successful payment is submitted, the cart is emptied (managed in localStorage), however the Cart quantity in the Navbar isn't resetting to 0.
The cart quantity is being managed in state in the <App /> component, setQty. The stripe payment is submitted in the <PaymentForm> component which is nested 4 components deep:
<App /> > <Checkout /> > <PaymentForm /> > <CheckoutForm />
In CheckoutForm, I'm using setQty({quantity: 0}); which I thought would pass "0" up to <App /> and reset the Cart quantity, instead I get an error of "Unhandled Rejection (TypeError): setQty is not a function". Why is this? How can I get this to work? Also, is there an easier way of resetting the Cart without passing props through so many components?
A breakdown of each component so you can see how I'm passing setQty through each component:
App
import React, { useState, useEffect } from 'react';
import './App.css';
import Nav from './Nav';
import Shop from './Components/Shop';
import Info from './Components/Info';
import Cart from './Components/Cart';
import Item from './Components/Item';
import Checkout from './Components/CheckoutForm/Checkout';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import { getQuantity } from './helpers/helperTools';
function App() {
const storageItems = JSON.parse(localStorage.getItem('product'));
const [qty, setQty] = useState({quantity: getQuantity(storageItems || [])});
console.log("Apppp", qty)
return (
<Router>
<div className="App">
<Nav qty={qty.quantity} />
<Route path="/" exact component={Shop} />
<Route path="/Info" component={Info} />
<Route path="/Cart/" render={(props) => <Cart {...props} setQty={setQty} />} />
<Route path="/Item/:item" component={Item} />
<Route path="/Checkout" component={Checkout} setQty={setQty} />
</div>
</Router>
)
}
export default App;
Checkout
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import {
Paper,
Stepper,
Step,
StepLabel,
Typography,
CircularProgress,
Divider,
Button,
} from '#material-ui/core';
import useStyles from './styles';
import AddressForm from './AddressForm';
import PaymentForm from './PaymentForm';
const steps = ['Shipping address', 'Payment details'];
function Checkout({setQty}) {
const classes = useStyles();
const [activeStep, setActiveStep] = useState(0);
const [shippingData, setShippingData] = useState({});
const nextStep = () => setActiveStep((prevActiveStep) => prevActiveStep + 1);
const backStep = () => setActiveStep((prevActiveStep) => prevActiveStep - 1);
const next = (data) => {
setShippingData(data);
nextStep();
};
const Form = () =>
activeStep === 0 ? (
<AddressForm next={next} />
) : (
<PaymentForm shippingData={shippingData} backStep={backStep} nextStep={nextStep} setQty={setQty} />
);
const Confirmation = () => <div>Confirmation</div>;
return (
<div>
<div className={classes.toolbar} />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Typography variant='h4' align='center'>
Checkout
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((step) => (
<Step key={step}>
<StepLabel>{step}</StepLabel>
</Step>
))}
</Stepper>
{activeStep === steps.length ? <Confirmation /> : <Form />}
</Paper>
</main>
</div>
);
}
export default Checkout;
PaymentForm AND CheckoutForm, both in the same file
import React, { useState } from 'react';
import { Typography, Button, Divider } from '#material-ui/core';
import {
Elements,
CardElement,
ElementsConsumer,
useStripe,
useElements,
} from '#stripe/react-stripe-js';
import { loadStripe } from '#stripe/stripe-js';
import axios from 'axios';
import { getTotal } from '../../helpers/helperTools';
import Review from './Review';
const stripePromise = loadStripe(
'pk_HIDDEN_FOR_NOW'
);
const CheckoutForm = ({ shippingData, backStep, nextStep, setQty }) => {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (event) => {
event.preventDefault();
if (!stripe || !elements) {
return;
}
const storageItems = JSON.parse(localStorage.getItem('product'));
const products = storageItems || [];
const totalPrice = getTotal(products);
let productTitle = '';
products.map((item, index) => {
productTitle = `${productTitle} | ${item.title}`;
});
const cardElement = elements.getElement(CardElement);
const { error, source } = await stripe.createSource(cardElement);
console.log(error, source);
const order = await axios.post('http://localhost:7000/api/stripe/charge', {
amount: totalPrice * 100,
source: source.id,
receipt_email: shippingData.email,
title: productTitle,
customerName: `${shippingData.firstName} ${shippingData.lastName}`,
address: {
city: shippingData.City,
country: shippingData.shippingCountry,
line1: shippingData.address1,
postal_code: shippingData.ZIP,
state: shippingData.shippingState,
},
});
if (error) {
console.log('[error]', error);
} else {
console.log('[PaymentMethod]', order);
localStorage.setItem('product', JSON.stringify([]));
nextStep();
setQty({quantity: 0});
}
};
return (
<form onSubmit={handleSubmit}>
<CardElement
options={{
style: {
base: {
fontSize: '16px',
color: '#424770',
'::placeholder': {
color: '#aab7c4',
},
},
invalid: {
color: '#9e2146',
},
},
}}
/>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button variant='outlined' onClick={backStep}>
Back
</Button>
<Button type='submit' variant='contained' disabled={!stripe} color='primary'>
Pay
</Button>
</div>
</form>
);
};
function PaymentForm({ shippingData, backStep, nextStep, setQty }) {
return (
<Elements stripe={stripePromise}>
<Review />
<br />
<br />
<CheckoutForm shippingData={shippingData} nextStep={nextStep} backStep={backStep} setQty={setQty} />
</Elements>
);
}
export default PaymentForm;
Screenshot of my file structure
In the App component, you need to pass setQty as below. Props that are mentioned in the Route component would not be transmitted to the component by itself, and we need to use the render function to pass props.
<Route path="/Checkout" render={(props) => <Checkout setQty={setQty} {...props}/>

Logout button in custom usermenu does nothing

I have custom user menu
import React from "react";
import { UserMenu, MenuItemLink} from "react-admin";
import SettingsIcon from "#material-ui/icons/Settings";
import jwt from "jwt-decode";
const AdminUserMenu = ({ logout }) => {
var user = jwt(localStorage.getItem("token"));
return (
<UserMenu>
<MenuItemLink
to={"/Admins/" + user.id}
primaryText="Profile"
leftIcon={<SettingsIcon />}
/>
{logout}
</UserMenu>
);
};
export default AdminUserMenu;
const AdminAppBar = (props) => (
<AppBar {...props} userMenu={<AdminUserMenu />} />
);
Profile link works. But logout button does nothing. How can I make it work so that user could logout?
Try this:
const AdminUserMenu = (props) => {
var user = jwt(localStorage.getItem("token"));
return (
<UserMenu {...props} >
<MenuItemLink
to={"/Admins/" + user.id}
primaryText="Profile"
leftIcon={<SettingsIcon />}
/>
</UserMenu>
);
};
I had the same behavior that you did using the latest version. The above code from MaxAlex also didn't work for some reason.
I was able to get it work work as follows. Notice the placement of the {logout} in the ConfigurationMenu object versus in the CustomUserMenu.
import * as React from 'react';
import { forwardRef } from 'react';
import { AppBar, UserMenu, MenuItemLink, useTranslate } from 'react-admin';
import Typography from '#material-ui/core/Typography';
import SettingsIcon from '#material-ui/icons/Settings';
import { makeStyles } from '#material-ui/core/styles';
import Logo from '../Logo';
const useStyles = makeStyles({
title: {
flex: 1,
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
},
spacer: {
flex: 1,
}
});
const ConfigurationMenu = ({logout, props}) => {
const translate = useTranslate();
return (
<div>
<MenuItemLink
to="/profile"
primaryText={translate("cw.profile.title")}
leftIcon={<SettingsIcon />}
{...props}
/>
{logout}
</div>
);
};
const CustomUserMenu = (props) => (
<UserMenu {...props} >
<ConfigurationMenu />
</UserMenu>
);
const CustomAppBar = (props) => {
const classes = useStyles();
return (
<AppBar {...props} elevation={1} userMenu={<CustomUserMenu />}>
<Typography
variant="h6"
color="inherit"
className={classes.title}
id="react-admin-title"
/>
<Logo />
<span className={classes.spacer} />
</AppBar>
);
};
export default CustomAppBar;

Get query parameters from react-router-dom into a functional component?

I have a router file to which I am passing query parameters to a functional component. I am using react-router-dom how can I get the query parameters in a functional component?
The line in question which sends parameters is
<Route path="/reports/daily-sales-single/:date_from" component={DailySalesSingle} />
Here I am sending a date_from parameter. I have seen various ways but none of them see conclusive in how to do this or seem out of date. Also every tutorial seems to be people using class components.
Router file:
import * as React from 'react';
import {
BrowserRouter as Router,
Route,
Switch,
} from 'react-router-dom';
import { asyncComponent } from '#hoc/index';
import { useSpring, useTransition, animated } from 'react-spring';
import useRouter from '#hooks/useRouter';
import Error from '#containers/Error/index';
const Home = asyncComponent(() => import(/* webpackChunkName: "js/chunks/home" */ '#containers/home').then(
module => module.default,
));
const RatesOverview = asyncComponent(() => import(/* webpackChunkName: "js/chunks/ratesoverview" */ '#containers/RatesOverview').then(
module => module.default,
));
const CreateRate = asyncComponent(() => import(/* webpackChunkName: "js/chunks/createrates" */ '#containers/CreateRates').then(
module => module.default,
));
const CopyRate = asyncComponent(() => import(/* webpackChunkName: "js/chunks/createrates" */ '#containers/CreateRates/copyRate').then(
module => module.default,
));
const RateHurdleOverview = asyncComponent(() => import(/* webpackChunkName: "js/chunks/hurdleOverview" */ '#containers/RateHurdleOverview').then(
module => module.default,
));
const DailySalesSingle = asyncComponent(() => import(/* webpackChunkName: "js/chunks/dailysalessingle" */ '#containers/Reports/DailySalesSingle').then(
module => module.default,
));
const DailySalesGroup = asyncComponent(() => import(/* webpackChunkName: "js/chunks/dailysalesgroup" */ '#containers/Reports/DailySalesGroup').then(
module => module.default,
));
const WeeklySalesSingle = asyncComponent(() => import(/* webpackChunkName: "js/chunks/weeklysalessingle" */ '#containers/Reports/WeeklySalesSingle').then(
module => module.default,
));
const WeeklySalesGroup = asyncComponent(() => import(/* webpackChunkName: "js/chunks/weeklysalesgroup" */ '#containers/Reports/WeeklySalesGroup').then(
module => module.default,
));
const Settings = asyncComponent(() => import(/* webpackChunkName: "js/chunks/settings" */ '#containers/Settings').then(
module => module.default,
));
const CreateRateHurdle = asyncComponent(() => import(/* webpackChunkName: "js/chunks/hurdlecreaterate" */ '#components/Forms/CreateRateHurdle').then(
module => module.default,
));
const RatesGrid = asyncComponent(() => import(/* webpackChunkName: "js/chunks/ratesgrid" */ '#containers/RatesGrid').then(
module => module.default,
));
const GroupDasboard = asyncComponent(() => import(/* webpackChunkName: "js/chunks/groupdashboard" */ '#containers/Group/Dashboard').then(
module => module.default,
));
const Routes = () => {
/* eslint-disable no-shadow */
const { location } = useRouter();
const transitions = useTransition(location, location => location.pathname, {
from: {
opacity: 0,
position: 'absolute',
width: '100%',
transform: 'translate3d(-100%, 0, 0)',
},
enter: {
opacity: 1,
transform: 'translate3d(0, 0, 0,)',
},
leave: { opacity: 0, transform: 'translate3d(150%, 0, 0)' },
});
/* eslint-enable no-shadow */
return transitions.map(({ item, key, props }) => (
<>
<div className="app-page">
<animated.div className="app-content" key={key} style={props}>
<Error />
<Switch location={item}>
<Route exact path="/" component={GroupDasboard} />
<Route path="/rates/overview" component={RatesOverview} />
<Route path="/rates/create" component={CreateRate} />
<Route path="/rates/grid" component={RatesGrid} />
<Route path="/rates/copy/:rate_id" component={CopyRate} />
<Route path="/rates/hurdle/create" component={CreateRateHurdle} />
<Route path="/rates/hurdle" component={RateHurdleOverview} />
<Route path="/reports/daily-sales-single/:date_from" component={DailySalesSingle} />
<Route path="/reports/daily-sales-group" component={DailySalesGroup} />
<Route path="/group/dashboard" component={GroupDasboard} />
<Route path="/reports/weekly-sales-single" component={WeeklySalesSingle} />
<Route path="/reports/weekly-sales-group" component={WeeklySalesGroup} />
{ /* <Route path="/settings" component={Settings} /> */}
</Switch>
</animated.div>
</div>
</>
));
};
export default Routes;
Functional Component:
import * as React from 'react';
import { connect } from 'react-redux';
import * as actions from '#actions/index';
import { QuickSearchMenu, WeeklyQuickSearchMenu } from '#constants/DateLists';
import { SearchBarDatesSchema } from '#constants/Yup/SearchBarDatesSchema.ts';
import { ToggleWithLabel } from '#components/inputs/index';
import PageHeader from '#components/Header/PageHeader';
import {
Jumbotron,
Container,
Row,
} from 'react-bootstrap';
import {
Formik,
Form,
Field,
} from 'formik';
import { log } from 'util';
import Title from './Title';
import SearchBy from './SearchBy';
import QuickSearch from './QuickSearch';
import Dates from './Dates';
import Properties from './Properties';
import DepartmentToggle from './DepartmentToggle';
import DepartmentTitle from './DepartmentTitle';
import RunReport from './RunReport';
import AdvSearch from './AdvSearch';
import AdvSearchToggle from './AdvSearchToggle';
import Options from './Options';
interface StateProps {
isModalOpen: boolean;
isSidebarOpen: boolean;
}
interface InitialProps {
page: any;
title: any;
subTitle: string;
dataLocal: any;
type: string;
}
interface DispatchProps {
getAllDepartments: (payload: any) => void;
getAllPaymentMethods: (payload: any) => void;
}
type Props = InitialProps & StateProps & DispatchProps;
function mapDispatchToProps(dispatch: React.Dispatch<any>): DispatchProps {
return {
getAllDepartments: payload => dispatch(actions.Reports.getAllDepartments(payload)),
getAllPaymentMethods: payload => dispatch(actions.Reports.getAllPaymentMethods(payload)),
};
}
const Header = ({
page,
title,
subTitle,
dataLocal,
getAllDepartments,
getAllPaymentMethods,
type,
}: Props) => {
const [department, setDepartment] = React.useState({ status: true });
const [advSearch, setAdvSearch] = React.useState({ status: false });
const [quickSearchDates, setQuickSearchDates] = React.useState({
dateName: null,
fromDate: null,
toDate: null,
});
const [apiDates, setApiDates] = React.useState({
fromDate: null,
toDate: null,
});
const [netOrGross, setNetOrGross] = React.useState(true);
const updateQuickSearchDates = () => {
if (apiDates.fromDate === null && apiDates.toDate === null) {
const today = new Date();
setQuickSearchDates({
dateName: 'Today',
fromDate: today,
toDate: today,
});
}
};
const getQuickSearchMenu = () => ((
page === 'weekly-sales-single'
|| page === 'weekly-sales-group')
? WeeklyQuickSearchMenu
: QuickSearchMenu);
const disableComponent = () => ((
page === 'weekly-sales-single'
|| page === 'weekly-sales-group')
// eslint-disable-next-line
? true
: false);
const onChange = (e: MouseEvent | KeyboardEvent) => {
setNetOrGross(e.target.value);
};
return (
<div className="daily-sales-header">
<PageHeader title={title} helpPage="help">
<Container fluid>
<Formik
validateOnChange
initialValues={{}}
validationSchema={SearchBarDatesSchema}
onSubmit={(values, { setSubmitting }) => {
updateQuickSearchDates();
getAllPaymentMethods({
type,
apiDates,
});
getAllDepartments({
type,
department,
apiDates,
});
setSubmitting(false);
}}
>
{({
isSubmitting,
handleSubmit,
}) => (
<Form onSubmit={handleSubmit}>
<Row>
{/* { page === 'daily-sale-single' && (
<SearchBy
colClass="search-by-col"
buttonId="search-by-button"
buttonLabel="Search by"
/>
)} */}
<QuickSearch
buttonLabel="Quick Search"
eleClass="quick-search"
eleIdBtn="quick-search-button"
quickSearchMenu={getQuickSearchMenu()}
setQuickSearchDates={setQuickSearchDates}
getQuickSearchDates={quickSearchDates}
disable={disableComponent()}
/>
<Field
fromClass="from-date"
fromLabel="From"
toClass="to-date"
toLabel="To"
setQuickSearchDates={quickSearchDates}
setApiDates={setApiDates}
component={Dates}
disable={disableComponent()}
/>
{ page === 'daily-sale-groups' && (
<Properties
colClass="search-by-col"
buttonId="search-by-button"
buttonLabel="Properties"
/>
)}
{ (page === 'daily-sale-single'
|| page === 'weekly-sales-single'
|| page === 'weekly-sales-group') && (
<DepartmentToggle
buttonOneLabel="Department"
buttonTwoLabel="Sub-Department"
variantSelected="primary"
variantUnSelected="outline-primary"
setDepartment={setDepartment}
status={department.status}
isSubmitting={isSubmitting}
disable={disableComponent()}
/>
)}
<RunReport
buttonLabel="Run Report"
isSubmitting={isSubmitting}
departmentToggle={department.status}
disable={disableComponent()}
/>
</Row>
<AdvSearch
advSearchToggle={advSearch.status}
/>
<Row>
<AdvSearchToggle
buttonClass="adv-search-btn pull-right"
buttonLabel="Advanced Search"
setAdvSearch={setAdvSearch}
status={advSearch.status}
/>
</Row>
</Form>
)}
</Formik>
</Container>
<Row>
<DepartmentTitle
title="Department"
departmentToggle={department.status}
departmentClass="display-title"
/>
<div className="col-md-auto">
{ (page === 'weekly-sales-single'
|| page === 'weekly-sales-group') && (
<div className="col-md-auto">
<ToggleWithLabel
label="Net / Gross"
field={{
onChange,
value: netOrGross,
name: 'net_or_gross',
}}
/>
</div>
)}
</div>
<Options
data={dataLocal}
/>
</Row>
</PageHeader>
</div>
);
};
export default connect(null, mapDispatchToProps)(Header);
<main>
<Routes>
<Route path="/cart/:id" element={<CartScreen />} />
<Route path="/" element={<HomeScreen />} exact />
<Route path="/product/:id" element={<ProductScreen />} />
</Routes>
</main>
this code is based on new react update make sure you dont pass any query string into route such as
<Route path="/cart/:id?xyz" element={<CartScreen />} /> //dont do this
on the other file where you want to get your query string:
import React from 'react';
import { useParams } from 'react-router-dom';
import { useLocation } from 'react-router-dom';
const CartScreen = () => {
const params = useParams()
const productId = params.id;
const search = useLocation().search;
const qty = new URLSearchParams(search).get('qty');
return <div><h1>Cart Screen</h1>
<p>Product: {productId}, Qty: { qty}</p></div>;
};
I use this way:
const MyComponent = (props) => {
console.log(props.match.params.id)
}
You need to add in routes:
<Route path="/reports/daily-sales-single/:from?/:to?" component={DailySalesSingle} />
Then in the functional component:
interface RouterProps {
match: any;
}
type Props = RouterProps;
const functionalComponent = ({
match,
}: Props) => {
console.log(match.params);
})
If you are using functional components and typescript you may use the following to get your router parameters:
In the router:
<Route exact path="/portal/welcome2/:sms" component={Welcome2}></Route>
In the component:
import React, { FC } from 'react';
import { useParams } from 'react-router-dom';
type WelcomeParams = {
sms: string;
};
const Welcome2: FC = () => {
const { sms } = useParams<WelcomeParams>();
return (
<h1>Welcome! here {sms}</h1>
);
};
export default Welcome2;
I did this:
const MyComponent = (props) => {
console.log("query",props.location.search)
}

Resources