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)
}
Related
I have been using React for close to a year know and understand a majority of the basics, however I have some questions regarding best practices (General or industry) when it comes to passing functions/ref/hooks and how it affects things like state flow and tests. I have been instantiating hooks such as useDispatch or useNavigation(React-router) in the App.tsx(js) file and then passing it down to all of my components who need to use it. I have been using this same concept for things like Axios and then within my components, I've been trying out passing MUI components(Grid, Card, etc) to my created component (i.e. LoginForm.tsx/js) where the initial rendering of the main component brings in those hooks instead of repeated instantiation throughout my project (Below for example). Is this breaking in standards or practices, such as SOLID OOP, and would this make testing harder down the line?
App.tsx
import { Dispatch, FC, Suspense, lazy } from "react";
import {
Navigate,
NavigateFunction,
Route,
Routes,
useNavigate,
useSearchParams,
} from "react-router-dom";
import {
HOMEPAGE,
LOGIN,
REDIRECT,
ROOM,
SEARCH,
SETUPROOM,
SIGNUP,
} from "./component/UI/Constatns";
import Layout from "./component/UI/Layout/Layout";
import { User } from "./types/types";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "./store/store";
import { Theme, useMediaQuery, useTheme } from "#mui/material";
import axios from "axios";
import LoadingSpinner from "./component/UI/LoadingSpinner";
const Homepage = lazy(() => import("./pages/Homepage"));
const Login = lazy(() => import("./pages/Login"));
const Signup = lazy(() => import("./pages/Signup"));
const Room = lazy(() => import("./pages/Room"));
const Search = lazy(() => import("./pages/subpages/Search"));
const CreateRoom = lazy(() => import("./pages/subpages/CreateRoom"));
const App: FC = () => {
const USER: User = useSelector((state: RootState) => state.user);
const theme: Theme = useTheme();
const isMobile: boolean = useMediaQuery(theme.breakpoints.down("md"));
const [params] = useSearchParams();
const dispatch: Dispatch<any> = useDispatch();
const navigation: NavigateFunction = useNavigate();
return (
<Suspense fallback={<LoadingSpinner />}>
<Layout>
<Routes>
<Route
path={HOMEPAGE}
element={
<Homepage
user={USER}
isMobile={isMobile}
axios={axios}
dispatch={dispatch}
param={params}
/>
}
/>
<Route
path={SEARCH}
element={
<Search
axios={axios}
dispatch={dispatch}
params={params}
nav={navigation}
isMobile={isMobile}
/>
}
/>
<Route
path={ROOM}
element={
<Room
isMobile={isMobile}
nav={navigation}
dispatch={dispatch}
param={params}
/>
}
/>
<Route
path={SETUPROOM}
element={
<CreateRoom
params={params}
axios={axios}
nav={navigation}
isMobile={isMobile}
user={USER}
/>
}
/>
<Route
path={LOGIN}
element={
<Login
nav={navigation}
isMobile={isMobile}
params={params}
axios={axios}
dispatch={dispatch}
/>
}
/>
<Route
path={SIGNUP}
element={
<Signup nav={navigation} isMobile={isMobile} axios={axios} />
}
/>
<Route path={REDIRECT} element={<Navigate replace to={HOMEPAGE} />} />
</Routes>
</Layout>
</Suspense>
);
};
export default App;
Example of MUI hooks
import { Button, Card, CardContent, Grid, TextField } from "#mui/material";
import { AxiosStatic } from "axios";
import { Dispatch, FC, FormEvent, useEffect, useRef, useState } from "react";
import { NavigateFunction, NavLink } from "react-router-dom";
import { FETCHLOGIN, HOMEPAGE, LOGGEDIN } from "../component/UI/Constatns";
import { userActions } from "../store/user/user-slice";
import LoginForm from "../component/forms/login/LoginForm";
import classes from "../styles/LoginStyles.module.css";
const Login: FC<{
dispatch: Dispatch<any>;
isMobile: boolean;
params: URLSearchParams;
axios: AxiosStatic;
nav: NavigateFunction;
}> = ({ axios, dispatch, isMobile, params, nav }) => {
const [userPassword, setUserPassword] = useState<string>("");
const username = useRef<HTMLInputElement | undefined>();
const password = useRef<HTMLInputElement | undefined>();
const userSearchParam: string | null = params.get("username");
useEffect(() => {
if (userSearchParam) {
const fetchUser: (
axios: AxiosStatic,
username: string,
password: string
) => void = async (axios, username, password) => {
await axios
.post(FETCHLOGIN, { username: username, password: password })
.then((response) => {
dispatch(userActions.login({ username: response.data.username }));
nav(LOGGEDIN, { replace: true });
})
.catch(() => {
nav(HOMEPAGE, { replace: true });
});
};
fetchUser(axios, userSearchParam, userPassword);
}
}, [nav, axios, userPassword, userSearchParam, dispatch]);
const submitHandler: (e: FormEvent<HTMLFormElement>) => void = (e) => {
e.preventDefault();
setUserPassword(password.current?.value as string);
nav(`?username=${username.current?.value}`, { replace: true });
};
return (
<Grid className={classes.loginContainer} container>
<Card className={!isMobile ? classes.card : classes.mobCard}>
<div className={classes.cardHeader}>
<p>Please login</p>
</div>
<CardContent>
<LoginForm
Link={NavLink}
Submit={submitHandler}
TextField={TextField}
Button={Button}
Grid={Grid}
username={username}
password={password}
/>
</CardContent>
</Card>
</Grid>
);
};
export default Login;
import React from 'react';
import PropTypes from 'prop-types';
import { Route } from 'react-router-dom';
import { SelectModal } from 'ux-components';
const ItemSelectRoute = (props) => {
console.log('1111111', props);
return (
<Route
path="/item-select/:label"
render={(routeProps) => (
<SelectModal
isOpen
label={routeProps.match.params.label}
onCloseClick={() => (routeProps.history.push(props.background.pathname))}
/>
)}
/>
);
}
export default ItemSelectRoute;
SelectModal.js
import React from 'react';
import PropTypes from 'prop-types';
import { Dialog } from 'styleguide-react-components';
import ModalHeader from 'ux-components/src/ModalHeader';
import ModalBody from '../../ModalBody/ModalBody';
const SelectModal = ({
onCloseClick, isOpen, itemSummaries,
}) => {
const itemList = itemSummaries;
return (
<Dialog
appearance="lite"
open={isOpen}
title={<ModalHeader header="Please select" />}
type="modal"
hasCloseButton
clickOffToClose
width={750}
onClose={onCloseClick}
>
<ModalBody items={itemList} />
</Dialog>
);
};
export default SelectModal;
I am writing the test case as for ItemSelectRoute
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const state = {
settings: {
configuration: {},
featureToggle: {},
properties: {},
},
};
const store = mockStore(state);
const newProps = {
appData: {
background: {
pathname: '/',
},
};
const wrapper = mount(<ReduxProvider store={store}>
<MemoryRouter initialEntries={['/item-select/test']}>
<Switch>
<ItemSelectRoute
store={store}
dispatch={jest.fn()}
{...newProps}
render={() => (<SelectModal
isOpen
label="track-my-item"
onCloseClick={() => jest.fn()}
/>)}
/>
</Switch>
</MemoryRouter>
</ReduxProvider>);
console.log(wrapper.debug());
When I run the test, I am getting the following error
Cannot read property 'addEventListener' of undefined
I want to write the test case, where if the route is correct, then SelectModal should be present in the elements tree. I tried few options, but I am unable to resolve the error.
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="/" />
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}/>
I am using the react-admin.
My project is working on TypeScript.
I am trying to make a Custom Menu, by updating the Layout as follows:
const AppLayout = (props: JSX.IntrinsicAttributes) => (
<Layout
{...props}
appBar={AppBar}
sidebar={Sidebar}
menu={Menu}
notification={Notification}
/>
);
Unfortunately, I have TypeScript errors in my custom menu implementation. I have "Object is of type unknown" error on the line {resources.map((resource) => (:
import { Theme, useMediaQuery } from "#material-ui/core";
import LabelIcon from "#material-ui/icons/Label";
import DefaultIcon from "#material-ui/icons/ViewList";
import React, { FC } from "react";
import { getResources, MenuItemLink } from "react-admin";
import { useSelector } from "react-redux";
import { AppState } from "../../../consts/types";
interface Props {
logout: () => void;
onMenuClick: () => void;
}
const Menu: FC<Props> = ({ onMenuClick, logout }) => {
const isXSmall = useMediaQuery((theme: Theme) =>
theme.breakpoints.down("xs")
);
const open = useSelector((state: AppState) => state.admin.ui.sidebarOpen);
const resources = useSelector(getResources);
return (
<div>
{resources.map((resource) => (
<MenuItemLink
key={resource.name}
to={`/${resource.name}`}
primaryText={
(resource.options && resource.options.label) || resource.name
}
leftIcon={resource.icon ? <resource.icon /> : <DefaultIcon />}
onClick={onMenuClick}
sidebarIsOpen={open}
/>
))}
<MenuItemLink
to="/custom-route"
primaryText="Miscellaneous"
leftIcon={<LabelIcon />}
onClick={onMenuClick}
sidebarIsOpen={open}
/>
{isXSmall && logout}
</div>
);
};
export default Menu;