I am doing a typescript assignment, which is an app for a doctor to add patients, diagnoses and so on. I am using react-router. The router is changing the URL, but not rendering the patient view for some reason. I have been trying to figure this out for a while now. Can someone push me into the right direction? Thank you.
App.tsx
import React, { useState } from "react";
import axios from "axios";
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import { Button, Divider, Header, Container } from "semantic-ui-react";
import { apiBaseUrl } from "./constants";
import { useStateValue } from "./state";
import { Patient } from "./types";
import PatientListPage from "./PatientListPage";
import PatientPage from "./components/PatientPage";
const App: React.FC = () => {
//const [, dispatch] = useStateValue();
const [{ patient }, dispatch] = useStateValue();
const [page, setPage] = useState('');
React.useEffect(() => {
axios.get<void>(`${apiBaseUrl}/ping`);
const fetchPatientList = async () => {
try {
const { data: patientListFromApi } = await axios.get<Patient[]>(
`${apiBaseUrl}/api/patients`
);
dispatch({ type: "SET_PATIENT_LIST", payload: patientListFromApi });
} catch (e) {
console.error(e);
}
};
fetchPatientList();
}, [dispatch]);
const showPatient = async (id: string) => {
try {
const { data: patientFromApi } = await axios.get<Patient>(`${apiBaseUrl}/api/patients/${id}`);
dispatch({ type: "GET_PATIENT", payload: patientFromApi });
setPage(patientFromApi.id);
console.log('patient', patient);
} catch (error) {
console.log(error.message);
}
}
return (
<div className="App">
<Router>
<Container>
<Header as="h1">Patientor</Header>
<Button as={Link} to="/" primary>
Home
</Button>
<Divider hidden />
<Switch>
<Route path="/">
<PatientListPage showPatient={showPatient} />
</Route>
<Route path={`/${page}`} >
<PatientPage />
</Route>
</Switch>
</Container>
</Router>
</div>
);
};
export default App;
PatientListPage.tsx
import React from "react";
import axios from "axios";
import { Container, Table, Button } from "semantic-ui-react";
import { PatientFormValues } from "../AddPatientModal/AddPatientForm";
import AddPatientModal from "../AddPatientModal";
import { Patient } from "../types";
import { apiBaseUrl } from "../constants";
import HealthRatingBar from "../components/HealthRatingBar";
import { useStateValue } from "../state";
import { Link } from "react-router-dom";
const PatientListPage: React.FC<{ showPatient: any }> = ({ showPatient }) => {
const [{ patients, patient }, dispatch] = useStateValue();
const [modalOpen, setModalOpen] = React.useState<boolean>(false);
const [error, setError] = React.useState<string | undefined>();
const openModal = (): void => setModalOpen(true);
const closeModal = (): void => {
setModalOpen(false);
setError(undefined);
};
const submitNewPatient = async (values: PatientFormValues) => {
try {
const { data: newPatient } = await axios.post<Patient>(
`${apiBaseUrl}/api/patients`,
values
);
dispatch({ type: "ADD_PATIENT", payload: newPatient });
closeModal();
} catch (e) {
console.error(e.response.data);
setError(e.response.data.error);
}
};
return (
<div className="App">
<Container textAlign="center">
<h3>Patient list</h3>
</Container>
<Table celled>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>Gender</Table.HeaderCell>
<Table.HeaderCell>Occupation</Table.HeaderCell>
<Table.HeaderCell>Health Rating</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{Object.values(patients).map((patient: Patient) => (
<Table.Row key={patient.id} onClick={() => showPatient(patient.id)}>
<Link to={`/${patient.id}`}>
<Table.Cell>{patient.name}</Table.Cell>
</Link>
<Table.Cell>{patient.gender}</Table.Cell>
<Table.Cell>{patient.occupation}</Table.Cell>
<Table.Cell>
<HealthRatingBar showText={false} rating={1} />
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
<AddPatientModal
modalOpen={modalOpen}
onSubmit={submitNewPatient}
error={error}
onClose={closeModal}
/>
<Button onClick={() => openModal()}>Add New Patient</Button>
</div>
);
};
export default PatientListPage;
PatientPage.tsx
import React, { useState } from "react";
import { Patient } from '../types';
import { useStateValue } from "../state";
const PatientPage: React.FC = () => {
const [{ patient }, dispatch] = useStateValue();
return (
<>
name: {patient.name}
ssn: {patient.ssn}
occupation: {patient.occupation}
</>
)
}
export default PatientPage
It might be because you have not used the exact keyword, so its rendering the PatientListPage component instead.
<Switch>
<Route exact path="/">
<PatientListPage showPatient={showPatient} />
</Route>
<Route path={`/${page}`}>
<PatientPage />
</Route>
</Switch>
More information here
First Solution and Best Solution:
If you use are using React Router 5.3.x, check whether it is 5.3.3 in your package.json file.
If it is not 5.3.3 uninstall the last version then install the bug-free version which has been resolved by John and updated in version 5.3.3.
npm uninstall -S react-router-dom
npm install -S react-router-dom#5.3.3
Second Solution:
React has launched its StrictMode in its latest update.
you can see it in an index.js file
index.js
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
So here your React Router is in the child component. And we have to make it a parent component.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<React.StrictMode>
<App />
</React.StrictMode>
</BrowserRouter>
);
Third Solution:
Remove the Strict mode from the index.js file
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
Related
i'm new to react. I found an online course but our versions didn't match. So in router part of the course, he used <Switch> but apparently it is now <Routes>, component is element etc. I run the react app, it's just blank white page. Browser console says this:
Errors
Uncaught TypeError: Cannot read properties of null (reading 'useRef')
Warning: Invalid hook call. Hooks can only be called inside of the body of a function component.
So here is my index.js and app.js; What did i do wrong?
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import "bootstrap/dist/css/bootstrap.min.css";
import "alertifyjs/build/css/alertify.min.css";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
app.js
import React, { Component } from "react";
import { Routes, Route } from "react-router-dom";
import { Container, Row, Col } from "reactstrap";
import CategoryList from "./CategoryList";
import Navi from "./Navi";
import ProductList from "./ProductList";
import alertify from "alertifyjs";
import NotFound from "./NotFound";
import CartList from "./CartList";
export default class App extends Component {
state = { currentCategory: "", products: [], cart: [] };
changeCategory = (category) => {
this.setState({ currentCategory: category.categoryName });
this.getProducts(category.id);
};
componentDidMount() {
this.getProducts();
}
getProducts = (categoryId) => {
var url = "http://localhost:3000/products";
if (categoryId) {
url += "?categoryId=" + categoryId;
}
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ products: data }));
};
addToCart = (product) => {
var newCart = this.state.cart;
var addedItem = newCart.find((c) => c.product.id === product.id);
if (addedItem) {
addedItem.quantity += 1;
} else {
newCart.push({ product: product, quantity: 1 });
}
this.setState({ cart: newCart });
alertify.success(product.productName + " added to cart", 2);
};
removeFromCart = (product) => {
var newCart = this.state.cart.filter((c) => c.product.id !== product.id);
this.setState({ cart: newCart });
};
render() {
var productInfo = { title: "ProductList" };
var categoryInfo = { title: "CategoryList" };
return (
<div>
<Container>
<Navi removeFromCart={this.removeFromCart} cart={this.state.cart} />
<Row>
<Col xs="4">
<CategoryList
currentCategory={this.state.currentCategory}
changeCategory={this.changeCategory}
info={categoryInfo}
/>
</Col>
<Col xs="8">
<Routes>
<Route exact path="/" element={ <ProductList products={this.state.products} addToCart={this.addToCart} currentCategory={this.state.currentCategory} info={productInfo}/>}/>
<Route exact path="/cart" element={<CartList/>}/>
<Route exact path="*" element={<NotFound/>}/>
</Routes>
</Col>
</Row>
</Container>
</div>
);
}
}
enter image description here
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;
I have used useEffect to fetch API & dispatch an action from inside it to update state. I am using context API, useReducer for state management and also react-router in this project.
In Header.js as soon as i import/use CartState and state variable (e.g. cart.length) then the app breaks down and items from api are not rendered and i get this error. In home.js also i am using state, but with only that part it does not give any error.
Below i am attaching two images, one with error and one where it is rendered when i remove CartState from header.js
Or you can see the codesandbox : https://codesandbox.io/s/serene-fast-297j3h?from-embed=&file=/
Website picture when CartState removed from header.js
Error message - website picture
Please tell me what is the issue here and what should be done.
index.js file:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import "bootstrap/dist/css/bootstrap.min.css";
import { BrowserRouter } from "react-router-dom";
import GlobalContext from './context/GlobalContext'
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<GlobalContext>
<App />
</GlobalContext>
</BrowserRouter>
</React.StrictMode>
);
App.js:
import "./App.css";
import Header from "./Components/Header";
import { Routes, Route } from "react-router-dom";
import Home from './Components/Home/Home'
import Cart from './Components/Cart'
function App() {
return (
<>
<Header />
<Routes>
<Route path="/" exact element={<Home/>}></Route>
<Route path="/cart" exact element={<Cart/>}></Route>
</Routes>
</>
);
}
export default App;
GlobalContext.js:
const CartContext = createContext();
const GlobalContext = ({children}) => {
const initialState ={
products: [],
cart: []
};
useEffect( ()=>{
const fetchData = async ()=> {
const res = await axios('https://fakestoreapi.com/products');
const resAdd = res.data.map((item)=> ({...item, inStock: faker.helpers.arrayElement([0,2,5,9,20]), fastDelivery: faker.datatype.boolean() }));
dispatch({type: 'API_CALL_SUCCESS', payload: resAdd});
// console.log(initialState);
}
fetchData();
}, [] )
const [state, dispatch] = useReducer(cartReducer, initialState );
console.log(state);
return (
<CartContext.Provider value={{state, dispatch}}>
{children}
</CartContext.Provider>
);
};
export default GlobalContext;
export const CartState = () => {
return useContext(CartContext);
};
Header.js (part of the code)
import CartState from "../context/GlobalContext";
const Header = () => {
const {
state: { cart },
dispatch,
} = CartState();
return (
<Navbar bg="dark" variant="dark" style={{ height: 100 }}>
<Container>
<Navbar.Brand>
<Link to="/">HOME</Link>
</Navbar.Brand>
<FormControl
className="m-auto"
style={{ width: 500 }}
type="text"
placeholder="Search here the product you want"
/>
<Nav>
<Dropdown
// alignRight
>
<Dropdown.Toggle variant="success">
<FaShoppingCart color="white" fontSize="25px" />
<Badge>
{cart.length}
{/* 1 */}
</Badge>
</Dropdown.Toggle>
Home.js:
import { CartState } from '../../context/GlobalContext'
import SingleProduct from '../SingleProduct';
import './styles.css'
import Filters from '../Filter/Filters';
const Home = () => {
const {state: {products}, } = CartState();
console.log(products);
return (
<div className="home">
<Filters />
<div className="productContainer">
{products.map( (item)=>
<SingleProduct prod = {item} key = {item.id} />
)}
</div>
</div>
)
}
export default Home
You need to modify below code line in Header.js file.
Actual:
import CartState from "../context/GlobalContext";
Expected/correct:
import {CartState} from "../context/GlobalContext";
Reason: CartState is not a default export.
In Header.js:
import CartState from '../context/GlobalContext';
this part of your code is wrong because you didn't export default your CartState function so you should import it like:
import {CartState} from "../context/GlobalContext";
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 am working with react. When I try to play audio from a button I am receiving this error. Everything worked before when the website was only a one page website. Then I turned it into a multi-page website using react-router-dom router, switch, and route everything started to error out with the onClick. I am not sure what went wrong or how to fix it. I am still pretty new with react. Here is the code:
Player.js
import React, { useState } from "react";
import "./Button.css";
import {useTranslation} from "react-i18next";
import teaser from '../sounds/teaser-final.mp3';
const Player = ({ url }) => {
const useAudio = url => {
const [audio] = useState(new Audio(teaser));
const [playing, setPlaying] = useState(true); //nothing is playing on default
const toggle = () => {
setPlaying(!playing);
playing ? audio.play() : audio.pause()
console.log("audio is playing" + toggle);
};
return [playing, toggle];
};
const [toggle] = useAudio(url);
const {t} = useTranslation('common');
return (
<div>
<audio id="player" style={{'display': 'none'}} src={teaser}></audio>
<button
className="btns hero-button btn--outline btn--large"
onClick={toggle}
>
{t('heroSection.button')}
</button>
</div>
);
};
export default Player;
HeroSection.js (file where Player.js is used)
import React from 'react'
import './HeroSection.css'
import Player from '../Player';
function HeroSection() {
return (
<div className="hero-btns">
<Player />
</div>
)
}
export default HeroSection;
App.js
import React, { useState, useEffect } from 'react';
import './index.css';
import {BrowserRouter, Switch, Route} from 'react-router-dom';
import Axios from 'axios';
import AdminHome from './Components/auth/Admin';
import Login from './Components/auth/Login';
import Register from './Components/auth/Register';
import UserContext from './Context/UserContext';
import Navbar from './Components/Navbar/Navbar';
import HeroSection from './Components/HeroSection/HeroSection';
import ShareSection from './Components/ShareSection/ShareSection';
import Subscribe from './Components/Subscribe/Subscribe';
import Footer from './Components/Footer/Footer';
import About from './Components/About/About';
import TheApp from './Components/TheApp/TheApp';
import Contact from './Components/SocialSection/Contact';
import CookiesPopUp from './Components/Cookies/CookiesPopUp';
function Home() {
return (
<>
<CookiesPopUp />
<Navbar />
<HeroSection />
<ShareSection />
<Subscribe />
<TheApp />
<About />
<Contact />
<Footer />
</>
);
};
function App() {
const [userData, setUserData] = useState({
token: undefined,
user: undefined,
});
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
const checkedLoggedIn = async () => {
let token = localStorage.getItem("auth-token");
if(token === null) {
localStorage.setItem("auth-token", "");
token = "";
}
const tokenRes = await Axios.post(
"http://localhost:5000/users/tokenIsValid", null,
{headers: { "x-auth-token": token }}
);
if (tokenRes.data) {
const userRes = await Axios.get("http://localhost:5000/users/", {
headers: { "x-auth-token": token },
});
setUserData({
token,
user: userRes.data,
});
}
return () => setDidMount(false);
};
checkedLoggedIn();
}, [])
if(!didMount) {
return null;
}
return (
<>
<BrowserRouter>
<UserContext.Provider value={{userData, setUserData}}>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/admin" component={AdminHome} exact />
<Route path="/admin/login" component={Login} exact />
<Route path="/admin/register" component={Register} exact />
</Switch>
</UserContext.Provider>
</BrowserRouter>
</>
);
}
export default App;
And this is the error I get in console when I try to play the audio.
You are overriding the definition of toggle with this code :
const [toggle] = useAudio(url);. The Player.js has multiple declarations and definitions of toggle. See:
const toggle = () => {
setPlaying(!playing);
playing ? audio.play() : audio.pause()
console.log("audio is playing" + toggle);
};
return [playing, toggle];
};
..
...
..
const [toggle] = useAudio(url);
Hence the Error Expected OnClick to be a function but provided a boolean
Thank you for your feedback! I managed to get everything to work by changing my code in Player.js to:
import React, { useState } from "react";
import "./Button.css";
import {useTranslation} from "react-i18next";
import teaser from '../sounds/teaser-final.mp3';
const Player = () => {
const [playing, setPlaying] = useState(true);
const [audio] = useState(new Audio(teaser));
const toggle = () => {
setPlaying(!playing);
playing ? audio.play() : audio.pause()
};
const {t} = useTranslation('common');
return (
<div>
<audio id="player" style={{'display': 'none'}} src={teaser}></audio>
<button
className="btns hero-button btn--outline btn--large"
onClick={toggle}
>
{t('heroSection.button')}
</button>
</div>
);
};
export default Player;