Unable to redirect on button click React.js (using context provider) - javascript

I'm new to React and I've been trying to redirect to a different component after getting a response from my API.
I've tried using history, location, and Redirect, but the redirect never happens.
Also, I get undefined when using all of the above.
I'm not sure if this is because my App is defined outside the Router, if it is the reason I'm still unable to fix the issue.
Here is my code:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { AppProvider } from './Context'
import { BrowserRouter as Router } from 'react-router-dom'
ReactDOM.render(
<React.StrictMode>
<AppProvider>
<App />
</AppProvider>
</React.StrictMode>,
document.getElementById('root')
);
App.js
import React from 'react';
import './App.css';
import {Home, JoinRoom, CreateRoom, Room } from './pages';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
function App() {
return (
<div className="App">
<Router>
<Switch>
<Route path="/" exact={true}>
<Home />
</Route>
<Route path="/join">
<JoinRoom />
</Route>
<Route path="/create">
<CreateRoom />
</Route>
<Route path="/room/:roomCode">
<Room />
</Route>
</Switch>
</Router>
</div>
);
}
export default App;
Context.js
Here, in the handleRoomButtonPressed, I'm getting data from the API and trying to redirect.
import React, { useState, useContext } from 'react';
import axios from 'axios';
import { Redirect, useHistory } from "react-router-dom";
const AppContext = React.createContext()
const AppProvider = ({ children }) => {
// const history = useHistory();
const [guestCanPause, setGuestCanPause] = useState(true);
const [votesToSkip, setVotesToSkip] = useState(2);
const [isHost, setIsHost] = useState(false);
const handleVotesChange = (e) => {
e.preventDefault();
setVotesToSkip(e.target.value);
}
const handleGuestCanPauseChange = (e) => {
e.preventDefault();
setGuestCanPause(e.target.value)
}
const handleRoomButtonPressed = async (props) => {
const roomData = { guest_can_pause: guestCanPause, votes_to_skip: votesToSkip };
const response = await axios.post('/api/create-room/', roomData);
console.log(response.data)
const redirectUrl = "/room/" + response.data.code;
console.log(props)
return <Redirect to={redirectUrl} />
}
const getRoomDetails = async (roomCode) => {
axios
.get("/api/get-room?code=" + roomCode)
.then((res) => {
console.log(res.data)
setVotesToSkip(res.data.votes_to_skip);
setGuestCanPause(res.data.guest_can_pause);
setIsHost(res.data.is_host);
})
.catch((err) => console.log(err));
}
return <AppContext.Provider value={{ guestCanPause,
votesToSkip,
isHost,
handleGuestCanPauseChange,
handleVotesChange,
handleRoomButtonPressed,
getRoomDetails, }}>
{children}
</AppContext.Provider>
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
The onClick is called in CreateRoom.js
import React, { useState, } from 'react';
import { useGlobalContext } from '../Context'
import { Link } from 'react-router-dom';
import { Button, Grid, Typography, TextField, FormHelperText, FormControl, Radio, RadioGroup, FormControlLabel } from '#material-ui/core'
function CreateRoom() {
const defaultVotes = 2;
const { handleGuestCanPauseChange, handleVotesChange, handleRoomButtonPressed } = useGlobalContext();
return (
<Grid container spacing={1}>
<Grid item xs={12} align="center">
<Typography component="h4" variant="h4">
Create A Room
</Typography>
</Grid>
<Grid item xs={12} align="center">
<FormControl component="fieldset">
<FormHelperText>
<div align="center">Guest Control of Playback state</div>
</FormHelperText>
<RadioGroup row defaultValue="true" onChange={handleGuestCanPauseChange}>
<FormControlLabel value="true"
control={<Radio color="primary" />}
label="Play/Pause" labelPlacemment="bottom" />
<FormControlLabel value="false"
control={<Radio color="secondary" />}
label="No Control" labelPlacemment="bottom" />
</RadioGroup>
</FormControl>
</Grid>
<Grid item xs={12} align="center">
<FormControl>
<TextField required={true}
type="number" onChange={handleVotesChange}
defaultValue={defaultVotes}
inputProps={{ min: 1,
style: { textAlign: "center" },
}}
/>
<FormHelperText>
<div align="center">Votes Required To Skip Song</div>
</FormHelperText>
</FormControl>
</Grid>
<Grid item xs={12} align="center">
<Button
color="primary"
variant="contained"
onClick={handleRoomButtonPressed}
>
Create A Room
</Button>
</Grid>
<Grid item xs={12} align="center">
<Button color="secondary" variant="contained" to="/" component={Link}>
Back
</Button>
</Grid>
</Grid>
)
}
export default CreateRoom

If I understood a subject correctly your AppProvider is located above the router in the component tree. Thus, the react router cannot inject its dependencies into your AppProvider. If you want to access react-router API, such as useHistory hook or others, you should call it from one of the Router children, then it will works.

Related

React Router Not Working As I would Expect

I am fairly new to React and am having trouble having a component load when my URL changes.
As a little background, I am using Fireact (https://www.fireact.dev/) as a starting point for this project. What I am trying to do is dynamically load a page with two parameters (accountId and analyticsID). The URL sucesffully changes from /account/QQAH7q5Iz1gwOvpJ4WlA/Analytics to http://localhost:3000/account/QQAH7q5Iz1gwOvpJ4WlA/Analytics/NebYvepZw8IFWmWugPHBbut the target component doesn't load (AnalyticsFeatureView). The target component (AnalyticsFeatureView) does load if I debug this to the root route so it doesn't appear to be an error with the target component. Could this have to do with having two parameters within the App.Js file loading <AuthRouter path="/account/:accountId/:path?" component={Feature} template={AccountTemplate} title="Feature" role="*" /> or something like that?
Please let me know any other information you need.
I currently have the following code snippets between what I believe to be the four relevant files:
App.js
import React from 'react';
import { BrowserRouter as Router, Switch } from "react-router-dom";
import { AuthProvider } from './components/FirebaseAuth';
import PublicRouter from './components/routers/PublicRouter';
import PublicTemplate from './components/templates/PublicTemplate';
import AccountTemplate from './components/templates/AccountTemplate';
import AuthRouter from './components/routers/AuthRouter';
import SignIn from './pages/public/SignIn';
import Home from './pages/auth/Home';
import PageNotFound from './pages/public/PageNotFound';
import AppTemplate from './components/templates/AppTemplate';
import UserProfile from './pages/auth/user/UserProfile';
import UpdateEmail from './pages/auth/user/UpdateEmail';
import UpdateName from './pages/auth/user/UpdateName';
import VerifyEmail from './pages/auth/user/VerifyEmail';
import UpdatePassword from './pages/auth/user/UpdatePassword';
import UpdatePhone from './pages/auth/user/UpdatePhone';
import DeleteUser from './pages/auth/user/DeleteUser';
import ViewLogs from './pages/auth/user/ViewLogs';
import Plans from './pages/auth/accounts/Plans';
import NewAccount from './pages/auth/accounts/NewAccount';
// load stripe
import stripeJson from "./inc/stripe.json";
import { Elements } from '#stripe/react-stripe-js';
import { loadStripe } from '#stripe/stripe-js';
import { Feature } from './pages/auth/accounts/Feature';
import UserList from './pages/auth/accounts/UserList';
import UserRole from './pages/auth/accounts/UserRole';
import AddUser from './pages/auth/accounts/AddUser';
import Invite from './pages/auth/user/Invite';
import PaymentList from './pages/auth/accounts/PaymentList';
import PaymentMethod from './pages/auth/accounts/PaymentMethod';
import DeleteAccount from './pages/auth/accounts/DeleteAccount';
import AnalyticsView from './features/fireact-demo-feature-main/AnalyticsFeatureView';
const stripePromise = loadStripe(stripeJson.stripeConfig.public_api_key);
function App() {
return (
<Elements stripe={stripePromise}>
<AuthProvider>
<Router>
<Switch>
<AuthRouter exact path="/" component={Home} template={AppTemplate} title="My Accounts" />
<AuthRouter exact path="/account/:accountId/billing/plan" component={Plans} template={AccountTemplate} title="Select Plan" role="admin" allowInactive={true} />
<AuthRouter exact path="/account/:accountId/billing/payment-method" component={PaymentMethod} template={AccountTemplate} title="Update Payment Method" role="admin" />
<AuthRouter exact path="/account/:accountId/billing/delete" component={DeleteAccount} template={AccountTemplate} title="Delete Account" role="admin" />
<AuthRouter exact path="/account/:accountId/users/change/:userId" component={UserRole} template={AccountTemplate} title="Change Role" role="admin" />
<AuthRouter exact path="/account/:accountId/users" component={UserList} template={AccountTemplate} title="Users" role="admin" />
<AuthRouter exact path="/account/:accountId/users/add" component={AddUser} template={AccountTemplate} title="Add User" role="admin" />
<AuthRouter exact path="/account/:accountId/billing" component={PaymentList} template={AccountTemplate} title="Billing" role="admin" />
<AuthRouter eact path="/account/:accountId/Analytics/:analyticsId" component={Feature} template={AccountTemplate} title="Feature" role="*" />
<AuthRouter path="/account/:accountId/:path?" component={Feature} template={AccountTemplate} title="Feature" role="*" />
<AuthRouter exact path="/new-account" component={NewAccount} template={AppTemplate} title="Create New Account" />
<AuthRouter exact path="/user/profile" component={UserProfile} template={AppTemplate} title="User Profile" />
<AuthRouter exact path="/invite/:code" component={Invite} template={AppTemplate} title="View Invite" />
<AuthRouter exact path="/user/profile/update-email" component={UpdateEmail} template={AppTemplate} title="Change Your Email" />
<AuthRouter exact path="/user/profile/update-name" component={UpdateName} template={AppTemplate} title="Change Your Name" />
<AuthRouter exact path="/user/profile/verify-email" component={VerifyEmail} template={AppTemplate} title="Verify Your Name" />
<AuthRouter exact path="/user/profile/update-password" component={UpdatePassword} template={AppTemplate} title="Change Your Password" />
<AuthRouter exact path="/user/profile/update-phone" component={UpdatePhone} template={AppTemplate} title="Change Your Phone Number" />
<AuthRouter exact path="/user/profile/delete" component={DeleteUser} template={AppTemplate} title="Delete Your Account" />
<AuthRouter exact path="/user/log" component={ViewLogs} template={AppTemplate} title="View Activity Logs" />
<PublicRouter exact path="/signin" component={SignIn} template={PublicTemplate} title="Sign In" />
<PublicRouter component={PageNotFound} template={PublicTemplate} title="Page Not Found" />
</Switch>
</Router>
</AuthProvider>
</Elements>
);
}
export default App;
FeatureRoutes.js
import React from 'react';
import { Route, Switch, useParams } from "react-router-dom";
import Home from '.';
import ModelsTable from './ModelsNew';
import TemplatesTable from './TemplatesNEW';
import Analytics from './AnalyticsFeature';
import NewModel from './NewModel';
import AnalyticsView from './AnalyticsFeatureView';
const FeatureRoutes = () => {
const { accountId, analyticsID } = useParams();
return (
<Switch key="demo">
<Route path={'/account/' + accountId + '/Analytics/' + analyticsID} >
<AnalyticsView />
</Route>
<Route path={'/account/' + accountId +'/templates'}>
<TemplatesTable />
</Route>
<Route path={'/account/' + accountId +'/Models'} >
<ModelsTable />
</Route>
<Route path={'/account/' + accountId + '/Model/New'} >
<NewModel />
</Route>
<Route path={'/account/' + accountId + '/Analytics'} >
<Analytics />
</Route>
<Route path={'/account/' + accountId + '/testing'} role="admin">
<TemplatesTable />
</Route>
<Route>
<Home />
</Route>
</Switch>
)
}
export default FeatureRoutes;
AnalyticsFeature.js
import React, { useState, useContext, useEffect, useRef } from "react";
import { BreadcrumbContext } from '../../components/Breadcrumb';
import { FirebaseAuth } from "../../components/FirebaseAuth/firebase";
import { useHistory, Redirect, useParams } from 'react-router-dom';
import Loader from '../../components/Loader';
import DataTable from "../../components/DataTable";
import { Card, Button, CardActions, Grid, CardHeader } from "#mui/material";
import { AuthContext } from "../../components/FirebaseAuth";
import { CloudFunctions } from "../../components/FirebaseAuth/firebase";
const Analytics = () => {
const title = 'Analytics';
const history = useHistory();
const { userData } = useContext(AuthContext);
const { setBreadcrumb } = useContext(BreadcrumbContext);
const [loading, setLoading] = useState(true);
const [accounts, setAccounts] = useState([]);
const mountedRef = useRef(true);
const showAnalytics = () => {
setLoading(true);
let records = [];
//const accountsRef = FirebaseAuth.firestore().collection('accounts');
//let query = accountsRef.where('access', 'array-contains', FirebaseAuth.auth().currentUser.uid);
const getAnalytics = CloudFunctions.httpsCallable('getAnalytics');
getAnalytics({
accountId: userData.currentAccount.id
}).then(accountSnapshots => {
if (!mountedRef.current) return null
accountSnapshots.data.forEach(account => {
records.push({
'id': account.id,
'analyticsName': account.name,
'analyticsDescription': account.description,
'analyticsURL': account.url
});
});
setAccounts(records);
setLoading(false);
});
}
useEffect(() => {
setBreadcrumb([
{
to: "/",
text: "Home",
active: false
},
{
to: "/account/" + userData.currentAccount.id + "/",
text: userData.currentAccount.name,
active: false
},
{
to: null,
text: title,
active: true
}
]);
showAnalytics();
}, [userData, setBreadcrumb, history]);
useEffect(() => {
return () => {
//mountedRef.current = false
}
}, []);
//~~~~~~~~~THE BELOW NEEDS LOADING INDICATORS
return (
<>
<div style={{ marginTop: '20px', marginBottom: '20px', textAlign: 'right' }}>
<Button variant="contained" onClick={() => history.push("/account/" + userData.currentAccount.id + "/Analytics/New")}><i className="fa fa-plus"></i>Add Report</Button>
</div >
<Grid item xs={12} container spacing={4}>
{accounts.map((account, i) =>
<Grid container item xs={12} md={12} key={i}>
<Card key={account.id} style={{ width: '25%' }}>
<CardHeader title={account.analyticsName} />
<div padding-left='30px'>
{account.analyticsDescription} <br /><br />
{userData.currentAccount.id}<br /><br />
{account.id }
</div>
<CardActions>
<Button size="small" color="primary" onClick={() => history.push('/account/' + userData.currentAccount.id + '/Analytics/' + account.id)}>View Report</Button>
</CardActions>
</Card>
</Grid>
)}
</Grid>
</>
)
}
export default Analytics;
Feature.js
import { Chip } from "#mui/material";
import React, { useContext, useEffect, Suspense, useState } from "react";
import { BreadcrumbContext } from '../../../../components/Breadcrumb';
import { AuthContext } from "../../../../components/FirebaseAuth";
import Loader from "../../../../components/Loader";
const components = [];
// Here: catch and return another lazy (promise)
const requireComponents = require.context(
'../../../../features', // components folder
true, // look subfolders
/\w+\/FeatureRoutes\.(js)$/ //regex for files
);
requireComponents.keys().forEach((filePath) => {
const folder = filePath.split("/")[1];
const name = filePath.split("/")[2];
const Feature = React.lazy(() =>
import("../../../../features/" + folder + "/" + name));
components.push(<Feature key={components.length+1} />);
});
export const titleContext = React.createContext();
export const Feature = () => {
const [ title, setTitle ] = useState("Default Feature");
const { userData } = useContext(AuthContext);
const { setBreadcrumb } = useContext(BreadcrumbContext);
useEffect(() => {
setBreadcrumb([
{
to: "/",
text: "Home",
active: false
},
{
to: "/account/"+userData.currentAccount.id+"/",
text: userData.currentAccount.name,
active: false
},
{
to: null,
text: title,
active: true
}
]);
}, [userData, setBreadcrumb, title]);
return (
<>
{(components.length > 0)?(
<titleContext.Provider value={{title, setTitle}} >
<Suspense fallback={<Loader />}>
{components}
</Suspense>
</titleContext.Provider>
):(
<>
<div>This is the default feature</div>
</>
)}
</>
)
}
I have tried creating different route patterns within the App.Js and FeatureRouter.js files. I have also successfully loaded the target component within the root directory.
FYI - For the offchance that someone is having this error.
I added a new folder called "analytics" and file titled "FeatureRoutes".
I added the following code to the "FeatureRoutes"
import React from 'react';
import { Route, Switch, useParams } from "react-router-dom";
import AnalyticsView from './AnalyticsFeatureView';
const FeatureRoutes = () => {
const { accountId, analyticsID } = useParams();
return (
<Switch key="demo">
<Route exact path={'/account/:accountId/Analytics/:analyticsID'} >
<AnalyticsView />
</Route>
</Switch>
)
}
export default FeatureRoutes;
Added my "AnalyticsFeatureView" component to the "analytics" folder

React app display blank page without any compilation error

I am not sure what went wrong with my react app below, it compile successfully without error but doesn't show anything (just show a blank page). Can anyone point out what went wrong with my code? Sorry I am new to react.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css'
import { BrowserRouter } from 'react-router-dom'
import App from './App';
ReactDOM.render((
// <Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
// </Provider>
), document.getElementById('root'))
App.js
import { BrowserRouter as Router, Route, Link, NavLink } from "react-router-dom";
import Home from "./components/Home";
import NewBuilding from "./components/NewBuilding";
import React, { Component } from "react";
import Web3 from 'web3';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import "./App.css";
import { useEffect, useState } from 'react';
function App() {
const [currentAccount, setCurrentAccount] = useState(null);
const checkWalletIsConnected = async () => {
const { ethereum } = window;
if (!ethereum) {
console.log("Make sure you have Metamask installed!");
return;
} else {
console.log("Wallet exists! We're ready to go!")
}
const accounts = await ethereum.request({ method: 'eth_accounts' });
if (accounts.length !== 0) {
const account = accounts[0];
console.log("Found an authorized account: ", account);
setCurrentAccount(account);
} else {
console.log("No authorized account found");
}
}
const connectWalletHandler = async () => {
const { ethereum } = window;
if (!ethereum) {
alert("Please install Metamask!");
}
try {
const accounts = await ethereum.request({ method: 'eth_requestAccounts' });
console.log("Found an account! Address: ", accounts[0]);
setCurrentAccount(accounts[0]);
} catch (err) {
console.log(err)
}
}
const connectWalletButton = () => {
return (
<button onClick={connectWalletHandler} className='cta-button connect-wallet-button'>
Connect Wallet
</button>
)
}
useEffect(() => {
checkWalletIsConnected();
}, [])
return (
<div>
<Router>
<AppBar position="static" color="default" style={{ margin: 0 }}>
<Toolbar>
<Typography variant="h6" color="inherit">
<NavLink className="nav-link" to="/">Home</NavLink>
</Typography>
<NavLink className="nav-link" to="/new/">New</NavLink>
</Toolbar>
</AppBar>
<Route path="/" exact component={Home} />
<Route path="/new/" component={NewBuilding} />
</Router>
</div>
)
}
export default App;
My Home.js is residing in a components folder
The folder structure is as below
Home.js
import React, { useState, useEffect } from "react";
import { makeStyles } from '#material-ui/core/styles';
import Web3 from 'web3'
const useStyles = makeStyles(theme => ({
button: {
margin: theme.spacing(1),
},
input: {
display: 'none',
},
}));
const Home = () => {
const classes = useStyles();
const [ contract, setContract] = useState(null)
const [ accounts, setAccounts ] = useState(null)
const [ funds, setFunds ] = useState([])
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
useEffect(() => {
}, []);
return (
<div><h2>Home</h2></div>
)
}
export default Home;
Your Route component should have the prop element instead of component, see the migration guide.
<Route path="/" exact element={<Home />} />
In App.js you're wrapping your components again in a Router. Instead you should wrap your Route components in a Routes.
import { Routes, Route, Link, NavLink } from "react-router-dom";
...
<div>
<AppBar position="static" color="default" style={{ margin: 0 }}>
<Toolbar>
<Typography variant="h6" color="inherit">
<NavLink className="nav-link" to="/">
Home
</NavLink>
</Typography>
<NavLink className="nav-link" to="/new/">
New
</NavLink>
</Toolbar>
</AppBar>
<Routes>
<Route path="/" exact element={<Home />} />
<Route path="/new/" element={<NewBuilding />} />
</Routes>
</div>

Uncaught TypeError: Cannot read properties of undefined (reading 'image') BookingCar.js

APP.JS
import './App.css';
import {BrowserRouter as Router,Routes,Route,Navigate } from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import Register from "./pages/Register";
import BookingCar from "./pages/BookingCar";
import "antd/dist/antd.css"
function App() {
return (
<Router>
<Routes>
<Route path='/' element={<Home/>}/>
<Route path="/login" element={<Login/>}></Route>
<Route path="/register" element={<Register />}></Route>
<Route path="/booking/:id" element={<BookingCar />}></Route>
</Routes>
</Router>
);
}
export default App;
BookingCar.js
import React, {useState,useEffect} from "react";
import { useDispatch, useSelector } from "react-redux";
import { getAllcars } from "../redux/action/carsAction";
import { useParams } from 'react-router-dom';
import Spinner from "../components/Spinner";
import DefaultLayout from "../components/DefaultLayout";
import { Row, Col} from "antd";
export default function BookingCar({match}){
const { carid } = useParams();
const {cars} = useSelector(state => state.carsReducer)
const {loading} = useSelector(state => state.alertReducer)
const [car, setcar] = useState({})
const dispatch = useDispatch()
useEffect(() => {
dispatch(getAllcars())
if(cars.length>0){
setcar(cars.find(o=>o._id === carid))
}
}, [cars])
return(
<DefaultLayout>
{loading && (<Spinner/> )}
<Row>
<Col lg={10} sm={24} xs={24}>
<img alt=""src={car.image} className="carimg"/>
</Col>
</Row>
</DefaultLayout>
)
}
Home.js
import React, {useState,useEffect} from "react";
import { useDispatch, useSelector } from "react-redux";
import DefaultLayout from "../components/DefaultLayout";
import { getAllcars } from "../redux/action/carsAction";
import { Button, Row, Col} from "antd";
import {Link} from "react-router-dom";
import Spinner from "../components/Spinner";
export default function Home(){
const {cars} = useSelector(state => state.carsReducer)
const {loading} = useSelector(state => state.alertReducer)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getAllcars())
}, [])
return(
<DefaultLayout>
{loading === true && (<Spinner/> )}
<Row justify="center" gutter={16} className="mt-5">
{cars.map(car=>{
return <Col lg={5} sm={24} xs={24}>
<div className="car p-2 bs1 ">
<img alt=""src={car.image} className="carimg"/>
<div className="car-content d-flex align-items-center justify-content-between">
<div>
<p>{car.name}</p>
<p>{car.rentPerHour} Rent Per Hour</p>
</div>
<div>
<button className="btn1 mt-2"><Link to={`/booking/${car._id}`}>Book now </Link></button>
</div>
</div>
</div>
</Col>
})}
</Row>
</DefaultLayout>
)
}
In BookingCar.js i am trying to get the car details like id (image)but i am getting error
So please help me how to solve this issue.
You're trying to access car before it's loaded/applied to state. Check if it exists first before trying to use it -
{car && (
<Row>
<Col lg={10} sm={24} xs={24}>
<img alt=""src={car.image} className="carimg"/>
</Col>
</Row>
)}
carid is undefined since it's not a defined route param (path="/booking/:id") so the .find function returns undefined.
You've valid car initial state
const [car, setcar] = useState({});
so you should be able to destructure (car.image & <img alt=""src={car.image} className="carimg"/>) from car without issue. The issue comes later when filtering cars by the cardid route param.
useEffect(() => {
dispatch(getAllcars());
if (cars.length > 0) {
setcar(cars.find(o => o._id === carid));
}
}, [cars]);
array.find can potentially return undefined if no match is found, so the UI should handle that. Route match params are also always strings, so if the _id fields are not also "string" type the strict equality won't work. Try doing a type-safe comparison by converting to strings.
cars.find(o => String(o._id) === carid)
...
return (
<DefaultLayout>
{loading && <Spinner />}
{car && (
<Row>
<Col lg={10} sm={24} xs={24}>
<img alt=""src={car.image} className="carimg" />
</Col>
</Row>
)}
</DefaultLayout>
);
Finally, you define the route match param as ":id" but destructure a carid in the component. Ensure the match params match.
If route is:
<Route path="/booking/:id" element={<BookingCar />} />
use const { id } = useParams();
otherwise, update the route param to match the code:
<Route path="/booking/:carid" element={<BookingCar />} />
use const { carid } = useParams();

React component test fails with: TestingLibraryElementError: Found multiple elements by: [data-testid="some-id"]

I couldn't find any solution in any other questions similar to mine so I decided to ask you for help.
I have tests to my <Login/> page:
import React from 'react';
import { render } from '../../__test__/utils';
import Login from '.';
test('Renders the Login page', () => {
const { getByTestId } = render(<Login />);
expect(getByTestId('login-page')).toBeInTheDocument(); //WORKS
});
test('Renders disabled submit button by default', () => {
const { getByTestId } = render(<Login />);
expect(getByTestId('login-button')).toHaveAttribute('disabled'); // ERROR HERE
});
test('should match base snapshot', () => {
const { asFragment } = render(<Login />);
expect(asFragment()).toMatchSnapshot(); //WORKS
});
I am using the 'utils.js' in my tests because I want to wrap every testing component in the providers I am using. Here is how the utils file looks like:
import React, { useState } from 'react';
import { render as rtlRender } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import { renderRoutes } from 'react-router-config';
import { Router } from 'react-router-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { ThemeProvider } from '#material-ui/styles';
import { createMemoryHistory } from 'history';
import StylesProvider from '../components/StylesProvider';
import routes from '../routes';
import reducer from '../reducers';
import { theme } from '../theme';
const history = createMemoryHistory();
const render = (
ui,
{ initialState, store = createStore(reducer, initialState), ...renderOptions } = {}
) => {
const Wrapper = ({ children }) => {
const [direction] = useState('ltr');
return (
<Provider store={store}>
<ThemeProvider theme={theme}>
<StylesProvider direction={direction}>
<Router history={history}>
{children}
{renderRoutes(routes)}
</Router>
</StylesProvider>
</ThemeProvider>
</Provider>
);
};
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
};
export * from '#testing-library/react';
export { render };
Unfortunately, I cannot show the whole app code so I'll make it short. The <Login /> component looks like below:
import React from 'react';
import { makeStyles } from '#material-ui/styles';
import {
Card, CardContent, CardHeader, Typography, Divider, Grid
} from '#material-ui/core';
import Page from 'src/components/Page';
import LoginForm from './LoginForm';
const Login = () => (
<Page className={classes.root} title="Login" dataTestId="login-page">
//here the component body//
<LoginForm
afterLoginRoute="/overview"
loginEndpoint="login"
userRole={userRoles.USER}
className={classes.loginForm}
/>
</Page>
)
In the <LoginForm /> component I have a Material-UI button with data-testid="login-button"
LoginForm:
return (
<div>
<Snackbar open={loginStatus.error} autoHideDuration={6000}>
<Alert severity="error">
Se ha producido un error, comprueba los credenciales y prueba de nuevo.
</Alert>
</Snackbar>
<form {...rest} className={clsx(classes.root, className)} onSubmit={handleSubmit}>
<div className={classes.fields}>
<TextField
className={classes.input}
error={hasError('email')}
fullWidth
helperText={hasError('email') ? formState.errors.email[0] : null}
label="Email"
name="email"
onChange={handleChange}
value={formState.values.email || ''}
variant="outlined"
/>
<TextField
className={classes.input}
error={hasError('password')}
fullWidth
helperText={hasError('password') ? formState.errors.password[0] : null}
label="ContraseƱa"
name="password"
onChange={handleChange}
type="password"
value={formState.values.password || ''}
variant="outlined"
/>
</div>
<Button
className={classes.submitButton}
color="secondary"
disabled={!formState.isValid}
size="large"
type="submit"
variant="contained"
data-testid="login-button"
>
Entrar
</Button>
</form>
</div>
);
Current test result I have is TestingLibraryElementError: Found multiple elements by: [data-testid="login-button"]. I am out of ideas right now. Could this happen because I try to pass the data-testid prop to the <Button /> which is imported from Material-UI library? Then, shouldn't I see other error message? Please help.

The URL with parameters in react app does not work in new browser window or tab

I have react-App with redux using Asp.netWebApi as back-end. The site has tutors.jsx and tutorDetails.jsx components. When the user clicks on the tutors list in tutors.jsx component , the tutorDetails.jsx component displays.
The link has two parameters in url tutorId and fullName. like
https://www.virtualcollege.pk/TutorDetails/1105/Sohail%20Anjum
The problem is that when we copy the url and open it in a new browser tab, it does not work.
here is live link for tutors:
tutors list live link
Here is the live link for detail page: the tutorsDetails page live link
Here is code for tutorDetails.jsx component
import React, { useState, useEffect } from "react";
import { makeStyles } from '#material-ui/core/styles';
import {useDispatch,useSelector} from 'react-redux';
import { Link,useParams} from 'react-router-dom';
import * as actions from "../_actions/tutorActions";
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
const useStyles = makeStyles({
title: {
fontSize: 14,
display: 'flex',
},
pos: {
marginBottom: 12,
},
card: {
padding:10,
marginTop:10,
display: 'flex',
flex:1,
},
cardAction: {
display: 'block',
textAlign: 'initial'
},
cardMedia: {
width: 160,
},
});
export default function TutorDetails(props) {
const [loading, setLoading] = useState(true);
let {tutorId}=useParams(); // get tutorId from url parameter
let {fullName}=useParams();// get fullname from parameter
const dispatch = useDispatch();
const classes = useStyles();
const tutor = useSelector(state=>state.tutor.list)
useEffect(() => {
//call the action creator with dispatch
// and wait until the promise resolves
actions
.fetchById(tutorId)(dispatch)
setLoading(false);
}, []);
return (
<div>{loading === true ? <span>Loading ! Please waite...</span>:<div>
<Card className={classes.card} variant="outlined">
<CardContent>
<Typography variant="h5" component="h2">
{tutor[0].fullName} <br/>
</Typography>
<Typography variant="h5" component="h2">
{tutor[0].Qualification}
</Typography>
<Typography className={classes.title} color="textSecondary" gutterBottom>
category: {tutor[0].category}
</Typography>
<Typography variant="body2" component="p">
experience:{tutor[0].experience}<br/>
subject: {tutor[0].subject}<br/>
Mobile: 0343-3969030<br/>
email: virtualcollegepk01#gmail.com<br/>
city: {tutor[0].city}<br/>
</Typography>
</CardContent>
</Card>
</div>}
</div>
);
}
Here is the link of url in tutors.jsx component.
<Link to={`/TutorDetails/${record.tutorId}/${record.fullName}`}
> {record.fullName}</Link>
Here is the router link in App.js
<Route path="/TutorDetails/:tutorId/:fullName" component={TutorDetails} />
the App.js component is as below:
import React, { useEffect } from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { history } from '../_helpers';
import { alertActions } from '../_actions/alert.actions';
import { PrivateRoute} from '../_components/PrivateRoute';
import {TeacherCourses} from '../_components/TeacherCourses';
import TutorDetails from '../_components/TutorDetails';
import Tutors from '../_components/Tutors';
import { HomePage } from '../HomePage';
import { Container } from "#material-ui/core";
import { ThemeProvider } from '#material-ui/core/styles'
import CssBaseline from '#material-ui/core/CssBaseline'
import theme from '../theme'
import { ToastProvider} from "react-toast-notifications";
import NewNavbar from "../_components/Header/NewNavbar";
import Footer from "../_components/Header/Footer";
import {carousel} from "react-responsive-carousel/lib/styles/carousel.min.css";
function App() {
const alert = useSelector(state => state.alert);
const dispatch = useDispatch();
useEffect(() => {
history.listen((location, action) => {
// clear alert on location change
dispatch(alertActions.clear());
});
}, []);
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<ToastProvider autoDismiss={true}>
<Container maxWidth="lg">
{alert.message &&
<div className={`alert ${alert.type}`}>{alert.message}</div>
}
<Router history={history}>
<div>
<NewNavbar/>
<Switch>
<Route path="/TutorDetails/:tutorId/:fullName" component= {TutorDetails} />
<Route path="/Tutors" component= {Tutors} />
<Route path="/MyCourses/:tutorId/" component= {MyCourses} />
<Route path="/Enroll/:courseId/:courseTitle" component={Enroll} />
<Route path="/" component={HomePage} />
<Redirect from="*" to="/" />
</Switch>
<Footer/>
</div>
</Router>
</Container>
</ToastProvider>
</ThemeProvider>
);
}
export {App} ;

Categories