I'm using webpack 2 / code splitting so I have my routes like so
import App from './components/App';
import Portal from './components/portal/Portal';
const componentRoutes = {
component: App,
path: '/',
indexRoute: { component: Portal },
childRoutes: [
{
path: 'login',
getComponent(location, cb) {
System.import('./components/login/Login')
.then(module => cb(null, module.default));
}
},
{
path: 'home/:id',
getComponent(location, cb) {
System.import('./components/homepage/HomeContainer')
.then(module => cb(null, module.default));
}
},
{
path: 'home/:id/assessments',
getComponent(location, cb) {
System.import('./components/assessment/AssessmentContainer')
.then(module => cb(null, module.default));
}
},
{
path: 'home/:id/manage_classes',
getComponent(location, cb) {
System.import('./components/manageClasses/manageClassesContainer')
.then(module => cb(null, module.default));
}
}
]
};
But I want to nest my routes like this:
import Login from './components/login/Login';
import HomeContainer from './components/homepage/HomeContainer';
import Unit from './components/homepage/Units'
import AssessmentContainer from './components/assessment/AssessmentContainer';
import ManageClassesContainer from './components/manageClasses/manageClassesContainer';
const componentRoutes = (
<Route path='/' component={App}>
<Route path='login' component={Login} />
<Route path='home/:id' component={HomeContainer}>
<IndexRoute component={Unit} />
<Route path='home/:id/assessments' component={AssessmentContainer} />
<Route path='home/:id/manage_classes' component={ManageClassesContainer} />
</Route>
</Route>
);
How do you have nested routes inside the nested routes array? When I navigate to home/:id I don't want to lose all the JSX HomeContainer provides and I just want to call use {props.children} to render the child routes. When you use code splitting you can't use JSX so that's why I'm trying to refactor.
Related
How to create a protected route with react-router-dom and storing the response in localStorage, so that when a user tries to open next time they can view their details again. After login, they should redirect to the dashboard page.
All functionality is added in ContextApi.
Codesandbox link : Code
I tried but was not able to achieve it
Route Page
import React, { useContext } from "react";
import { globalC } from "./context";
import { Route, Switch, BrowserRouter } from "react-router-dom";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
import PageNotFound from "./PageNotFound";
function Routes() {
const { authLogin } = useContext(globalC);
console.log("authLogin", authLogin);
return (
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
export default Routes;
Context Page
import React, { Component, createContext } from "react";
import axios from "axios";
export const globalC = createContext();
export class Gprov extends Component {
state = {
authLogin: null,
authLoginerror: null
};
componentDidMount() {
var localData = JSON.parse(localStorage.getItem("loginDetail"));
if (localData) {
this.setState({
authLogin: localData
});
}
}
loginData = async () => {
let payload = {
token: "ctz43XoULrgv_0p1pvq7tA",
data: {
name: "nameFirst",
email: "internetEmail",
phone: "phoneHome",
_repeat: 300
}
};
await axios
.post(`https://app.fakejson.com/q`, payload)
.then((res) => {
if (res.status === 200) {
this.setState({
authLogin: res.data
});
localStorage.setItem("loginDetail", JSON.stringify(res.data));
}
})
.catch((err) =>
this.setState({
authLoginerror: err
})
);
};
render() {
// console.log(localStorage.getItem("loginDetail"));
return (
<globalC.Provider
value={{
...this.state,
loginData: this.loginData
}}
>
{this.props.children}
</globalC.Provider>
);
}
}
Issue
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
The Switch doesn't handle rendering anything other than Route and Redirect components. If you want to "nest" like this then you need to wrap each in generic routes, but that is completely unnecessary.
Your login component also doesn't handle redirecting back to any "home" page or private routes that were originally being accessed.
Solution
react-router-dom v5
Create a PrivateRoute component that consumes your auth context.
const PrivateRoute = (props) => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin ? (
<Route {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
);
};
Update your Login component to handle redirecting back to the original route being accessed.
export default function Login() {
const location = useLocation();
const history = useHistory();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
history.replace(from);
}
}, [authLogin, history, location]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
Render all your routes in a "flat list"
function Routes() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute path="/dashboard" component={Dashboard} />
<PrivateRoute path="/About" component={About} />
<Route path="/login" component={Login} />
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
react-router-dom v6
In version 6 custom route components have fallen out of favor, the preferred method is to use an auth layout component.
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoutes = () => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />;
}
...
<BrowserRouter>
<Routes>
<Route path="/" element={<PrivateRoutes />} >
<Route path="dashboard" element={<Dashboard />} />
<Route path="about" element={<About />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
or
const routes = [
{
path: "/",
element: <PrivateRoutes />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "*",
element: <PageNotFound />
},
];
...
export default function Login() {
const location = useLocation();
const navigate = useNavigate();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
navigate(from, { replace: true });
}
}, [authLogin, location, navigate]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
For v6:
import { Routes, Route, Navigate } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/public" element={<PublicPage />} />
<Route
path="/protected"
element={
<RequireAuth redirectTo="/login">
<ProtectedPage />
</RequireAuth>
}
/>
</Routes>
);
}
function RequireAuth({ children, redirectTo }) {
let isAuthenticated = getAuth();
return isAuthenticated ? children : <Navigate to={redirectTo} />;
}
Link to docs:
https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f
import { v4 as uuidv4 } from "uuid";
const routes = [
{
id: uuidv4(),
isProtected: false,
exact: true,
path: "/home",
component: param => <Overview {...param} />,
},
{
id: uuidv4(),
isProtected: true,
exact: true,
path: "/protected",
component: param => <Overview {...param} />,
allowed: [...advanceProducts], // subscription
},
{
// if you conditional based rendering for same path
id: uuidv4(),
isProtected: true,
exact: true,
path: "/",
component: null,
conditionalComponent: true,
allowed: {
[subscription1]: param => <Overview {...param} />,
[subscription2]: param => <Customers {...param} />,
},
},
]
// Navigation Component
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Switch, Route, useLocation } from "react-router-dom";
// ...component logic
<Switch>
{routes.map(params => {
return (
<ProtectedRoutes
exact
routeParams={params}
key={params.path}
path={params.path}
/>
);
})}
<Route
render={() => {
props.setHideNav(true);
setHideHeader(true);
return <ErrorPage type={404} />;
}}
/>
</Switch>
// ProtectedRoute component
import React from "react";
import { Route } from "react-router-dom";
import { useSelector } from "react-redux";
const ProtectedRoutes = props => {
const { routeParams } = props;
const currentSubscription = 'xyz'; // your current subscription;
if (routeParams.conditionalComponent) {
return (
<Route
key={routeParams.path}
path={routeParams.path}
render={routeParams.allowed[currentSubscription]}
/>
);
}
if (routeParams.isProtected && routeParams.allowed.includes(currentSubscription)) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
if (!routeParams.isProtected) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
return null;
};
export default ProtectedRoutes;
Would like to add highlight never forget to give path as prop to ProtectedRoute, else it will not work.
Here is an easy react-router v6 protected route. I have put all the routes I want to protect in a routes.js:-
const routes = [{ path: "/dasboard", name:"Dashboard", element: <Dashboard/> }]
To render the routes just map them as follows: -
<Routes>
{routes.map((routes, id) => {
return(
<Route
key={id}
path={route.path}
exact={route.exact}
name={route.name}
element={
localStorage.getItem("token") ? (
route.element
) : (
<Navigate to="/login" />
)
}
)
})
}
</Routes>
If you want an easy way to implement then use Login in App.js, if user is loggedin then set user variable. If user variable is set then start those route else it will stuck at login page. I implemented this in my project.
return (
<div>
<Notification notification={notification} type={notificationType} />
{
user === null &&
<LoginForm startLogin={handleLogin} />
}
{
user !== null &&
<NavBar user={user} setUser={setUser} />
}
{
user !== null &&
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/adduser" element={<AddUser />} /> />
<Route exact path="/viewuser/:id" element={<ViewUser />} />
</Routes>
</Router>
}
</div>
)
How to create a protected route with react-router-dom and storing the response in localStorage, so that when a user tries to open next time they can view their details again. After login, they should redirect to the dashboard page.
All functionality is added in ContextApi.
Codesandbox link : Code
I tried but was not able to achieve it
Route Page
import React, { useContext } from "react";
import { globalC } from "./context";
import { Route, Switch, BrowserRouter } from "react-router-dom";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
import PageNotFound from "./PageNotFound";
function Routes() {
const { authLogin } = useContext(globalC);
console.log("authLogin", authLogin);
return (
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
export default Routes;
Context Page
import React, { Component, createContext } from "react";
import axios from "axios";
export const globalC = createContext();
export class Gprov extends Component {
state = {
authLogin: null,
authLoginerror: null
};
componentDidMount() {
var localData = JSON.parse(localStorage.getItem("loginDetail"));
if (localData) {
this.setState({
authLogin: localData
});
}
}
loginData = async () => {
let payload = {
token: "ctz43XoULrgv_0p1pvq7tA",
data: {
name: "nameFirst",
email: "internetEmail",
phone: "phoneHome",
_repeat: 300
}
};
await axios
.post(`https://app.fakejson.com/q`, payload)
.then((res) => {
if (res.status === 200) {
this.setState({
authLogin: res.data
});
localStorage.setItem("loginDetail", JSON.stringify(res.data));
}
})
.catch((err) =>
this.setState({
authLoginerror: err
})
);
};
render() {
// console.log(localStorage.getItem("loginDetail"));
return (
<globalC.Provider
value={{
...this.state,
loginData: this.loginData
}}
>
{this.props.children}
</globalC.Provider>
);
}
}
Issue
<BrowserRouter>
<Switch>
{authLogin ? (
<>
<Route path="/dashboard" component={Dashboard} exact />
<Route exact path="/About" component={About} />
</>
) : (
<Route path="/" component={Login} exact />
)}
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
The Switch doesn't handle rendering anything other than Route and Redirect components. If you want to "nest" like this then you need to wrap each in generic routes, but that is completely unnecessary.
Your login component also doesn't handle redirecting back to any "home" page or private routes that were originally being accessed.
Solution
react-router-dom v5
Create a PrivateRoute component that consumes your auth context.
const PrivateRoute = (props) => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin ? (
<Route {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
);
};
Update your Login component to handle redirecting back to the original route being accessed.
export default function Login() {
const location = useLocation();
const history = useHistory();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
history.replace(from);
}
}, [authLogin, history, location]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
Render all your routes in a "flat list"
function Routes() {
return (
<BrowserRouter>
<Switch>
<PrivateRoute path="/dashboard" component={Dashboard} />
<PrivateRoute path="/About" component={About} />
<Route path="/login" component={Login} />
<Route component={PageNotFound} />
</Switch>
</BrowserRouter>
);
}
react-router-dom v6
In version 6 custom route components have fallen out of favor, the preferred method is to use an auth layout component.
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoutes = () => {
const location = useLocation();
const { authLogin } = useContext(globalC);
if (authLogin === undefined) {
return null; // or loading indicator/spinner/etc
}
return authLogin
? <Outlet />
: <Navigate to="/login" replace state={{ from: location }} />;
}
...
<BrowserRouter>
<Routes>
<Route path="/" element={<PrivateRoutes />} >
<Route path="dashboard" element={<Dashboard />} />
<Route path="about" element={<About />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
or
const routes = [
{
path: "/",
element: <PrivateRoutes />,
children: [
{
path: "dashboard",
element: <Dashboard />,
},
{
path: "about",
element: <About />
},
],
},
{
path: "/login",
element: <Login />,
},
{
path: "*",
element: <PageNotFound />
},
];
...
export default function Login() {
const location = useLocation();
const navigate = useNavigate();
const { authLogin, loginData } = useContext(globalC);
useEffect(() => {
if (authLogin) {
const { from } = location.state || { from: { pathname: "/" } };
navigate(from, { replace: true });
}
}, [authLogin, location, navigate]);
return (
<div
style={{ height: "100vh" }}
className="d-flex justify-content-center align-items-center"
>
<button type="button" onClick={loginData} className="btn btn-primary">
Login
</button>
</div>
);
}
For v6:
import { Routes, Route, Navigate } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/public" element={<PublicPage />} />
<Route
path="/protected"
element={
<RequireAuth redirectTo="/login">
<ProtectedPage />
</RequireAuth>
}
/>
</Routes>
);
}
function RequireAuth({ children, redirectTo }) {
let isAuthenticated = getAuth();
return isAuthenticated ? children : <Navigate to={redirectTo} />;
}
Link to docs:
https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f
import { v4 as uuidv4 } from "uuid";
const routes = [
{
id: uuidv4(),
isProtected: false,
exact: true,
path: "/home",
component: param => <Overview {...param} />,
},
{
id: uuidv4(),
isProtected: true,
exact: true,
path: "/protected",
component: param => <Overview {...param} />,
allowed: [...advanceProducts], // subscription
},
{
// if you conditional based rendering for same path
id: uuidv4(),
isProtected: true,
exact: true,
path: "/",
component: null,
conditionalComponent: true,
allowed: {
[subscription1]: param => <Overview {...param} />,
[subscription2]: param => <Customers {...param} />,
},
},
]
// Navigation Component
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Switch, Route, useLocation } from "react-router-dom";
// ...component logic
<Switch>
{routes.map(params => {
return (
<ProtectedRoutes
exact
routeParams={params}
key={params.path}
path={params.path}
/>
);
})}
<Route
render={() => {
props.setHideNav(true);
setHideHeader(true);
return <ErrorPage type={404} />;
}}
/>
</Switch>
// ProtectedRoute component
import React from "react";
import { Route } from "react-router-dom";
import { useSelector } from "react-redux";
const ProtectedRoutes = props => {
const { routeParams } = props;
const currentSubscription = 'xyz'; // your current subscription;
if (routeParams.conditionalComponent) {
return (
<Route
key={routeParams.path}
path={routeParams.path}
render={routeParams.allowed[currentSubscription]}
/>
);
}
if (routeParams.isProtected && routeParams.allowed.includes(currentSubscription)) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
if (!routeParams.isProtected) {
return (
<Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
);
}
return null;
};
export default ProtectedRoutes;
Would like to add highlight never forget to give path as prop to ProtectedRoute, else it will not work.
Here is an easy react-router v6 protected route. I have put all the routes I want to protect in a routes.js:-
const routes = [{ path: "/dasboard", name:"Dashboard", element: <Dashboard/> }]
To render the routes just map them as follows: -
<Routes>
{routes.map((routes, id) => {
return(
<Route
key={id}
path={route.path}
exact={route.exact}
name={route.name}
element={
localStorage.getItem("token") ? (
route.element
) : (
<Navigate to="/login" />
)
}
)
})
}
</Routes>
If you want an easy way to implement then use Login in App.js, if user is loggedin then set user variable. If user variable is set then start those route else it will stuck at login page. I implemented this in my project.
return (
<div>
<Notification notification={notification} type={notificationType} />
{
user === null &&
<LoginForm startLogin={handleLogin} />
}
{
user !== null &&
<NavBar user={user} setUser={setUser} />
}
{
user !== null &&
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/adduser" element={<AddUser />} /> />
<Route exact path="/viewuser/:id" element={<ViewUser />} />
</Routes>
</Router>
}
</div>
)
I created cards and I added to evrey card the option mark like but I want that after the client mark like in some card, this card will save in another page of favorites. So I wrote the next codes and from some reson I get error 404 from the server (GET /api/cards/my-favorite-cards 404 4.389 ms)
MyFavoriteCard.jsx:
import React from "react";
import PageHeader from "../common/pageHeader";
import CardExtends from "../common/Cards/CardExtends";
import Cards from "../common/Cards/cards";
import { Navigate } from "react-router-dom";
import { getMyFavorite } from "../../services/cardService";
class Fav extends CardExtends {
state = {
data: [],
cards: [],
isMount: false,
};
async componentDidMount() {
try {
const { data } = await getMyFavorite();
this.setState({ data, cards: data, isMount: true });
} catch (error) {
console.log(error.message);
}
}
render() {
const { user } = this.props;
if (!user || (user && !user.biz)) return <Navigate replace to="/" />;
const cards = [...this.state.cards];
// const { isMount } = this.state;
// if (!isMount) return null;
return (
<React.Fragment>
<PageHeader
title="My Favorite Cards"
subTitle="Here you can find your favorite cards"
/>
<div className="container">
<Cards
cards={cards}
handleDelete={this.handleDelete}
changeLikeStatus={this.changeLikeStatus}
/>
</div>
</React.Fragment>
);
}
}
export default Fav;
cardService.js:
import http from "./httpService";
const apiUrl = process.env.REACT_APP_API_URL;
export const getCard = (cardId) => http.get(`${apiUrl}/cards/card/${cardId}`);
export const getCards = () => http.get(`${apiUrl}/cards/cards`);
export const getMyCards = () => http.get(`${apiUrl}/cards/my-cards`);
export const createCard = (card) => http.post(`${apiUrl}/cards/`, card);
export const deleteCard = (cardId) => http.delete(`${apiUrl}/cards/${cardId}`);
export const editCard = (card) => http.put(`${apiUrl}/cards/${card._id}`, card);
export const changeLikeStatus = async (cardId) =>
http.patch(`${apiUrl}/cards/card-like/${cardId}`);
export const getMyFavorite = () =>
http.get(`${apiUrl}/cards/my-favorite-cards`);
App.js:
import HomePage from "./layout/main/Home";
import About from "./layout/main/About.jsx";
import { Routes, Route } from "react-router-dom";
import Error404 from "./layout/main/Error404";
import Header from "./layout/header/Header.jsx";
import Footer from "./layout/footer/footer.jsx";
import BizSignup from "./layout/main/BizSignup";
import Logout from "./layout/main/Logout";
import MyCards from "./layout/main/MyCards";
import MyFavoriteCards from "./layout/main/MyFavoriteCards";
import Login from "./layout/main/Login";
import Signup from "./layout/main/Signup";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { getCurrentUser } from "./services/userService";
import CreateCard from "./layout/main/CreateCard";
import EditCardConvertor from "./layout/main/editCardConvertor";
import CardDetails from "./layout/main/CardDetails";
import CardDetailsConvertor from "./layout/main/CardDetailsConvertor";
function App() {
const user = getCurrentUser();
return (
<div className="App">
<Header user={user} />
<ToastContainer />
<main style={{ minHeight: "85vh" }}>
<Routes>
<Route path="/about" element={<About />} />
<Route path="/biz-signup" element={<BizSignup />} />
<Route path="/logout" element={<Logout />} />
<Route path="/my-cards" element={<MyCards user={user} />} />
<Route path="/create-card" element={<CreateCard user={user} />} />
<Route
path="/card-details-converotr"
element={<CardDetailsConvertor user={user} />}
/>
<Route
path="/edit-card/:id"
element={<EditCardConvertor user={user} />}
/>
<Route
path="/card-details/:id"
element={<CardDetails user={user} />}
/>
<Route
path="/my-fav-cards"
element={<MyFavoriteCards user={user} />}
/>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} />
<Route path="/" element={<HomePage />} />
<Route path="*" element={<Error404 />} />
</Routes>
</main>
<Footer />
</div>
);
}
export default App;
cardsRouter.js
router.get("my-fav-cards", auth, async (req, res) => {
try {
const { _id } = req.user;
const favoriteCards = await Card.find({ likes: _id });
return res.send(favoriteCards);
} catch (error) {
console.log(chalk.redBright("Error: You can't save", error.message));
return res.status(500).send(error.message);
}
});
I would happy to know what the problem
Thnak you
Important to know: the page is exsit, there is title and subtitle in the page, but the card arent appear in the page.
I am using "react-router-dom" v6. When i try to redirect to another page using history object i got the following ERROR:
Cannot read properties of undefined (reading 'push')
Here is my code:
const Search = ({ history }) => {
const [keyword, setKeyword] = useState("");
const searchSubmitHandler = (e) => {
e.preventDefault();
if (keyword.trim()) {
history.push(`/products/${keyword}`);
} else {
history.push("/products");
}
};
}
function App() {
return (
<Router>
<div className="App">
<Routes>
<Route exact path="/" element={<Home />} />
<Route exact path="/product/:id" element={<ProductDetails />} />
<Route exact path="/products" element={<Products />} />
<Route exact path="/search" element={<Search />} />
</Routes>
<Footer />
</div>
</Router>
);
}
The history object was replaced by a navigate function in react-router-dom version 6, accessed via the useNavigate hook.
import { useNavigate } from 'react-router-dom';
const Search = () => {
const navigate = useNavigate();
const [keyword, setKeyword] = useState("");
const searchSubmitHandler = (e) => {
e.preventDefault();
navigate(keyword.trim() ? `/products/${keyword}` : "/products");
};
};
I am trying to use Redux and redux-thunk middleware to modify state change and make use of async actions to send data and receive data from the server. I created my own rails api which is returning the JSON data in the console.log of my webpage but Im wondering how to render this data in a div in my html. I realize this may not be coded in the best way, any help with re-writing would be appreciated too!
Here is the start of my app.js file
import React from "react";
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom'
import "./assets/style.css";
import Quiz from "./components/quiz"
import Home from "./components/home";
import Result from "./components/Result";
import { useEffect } from "react";
function App() {
useEffect (() => {
fetch("http://127.0.0.1:3000/questions")
.then(resp => resp.json())
.then(console.log);
},[])
return (
<div className="App">
<Router>
<Switch >
<Route exact path="/">
<Home />
</Route>
{ <Route exact path="/quiz">
<Quiz/>
</Route>}
<Route exact path="/result">
<Result />
</Route>
</Switch>
</Router>
</div>
);
}
export default App
Here is my index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import { createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import quizReducer from './store/reducers/reducer'
import "./assets/style.css";
import App from "./App";
const store = createStore(quizReducer, applyMiddleware(thunk))
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
)
Here are my action/reducer
actions.js
export const getQuestion = (question) => ({type: "GOT_QUIZ", payload: question})
export const fetchQuestion = () => {
return (dispatch) => {
fetch(url)
.then(resp => resp.json())
.then(question => {
dispatch(getQuestion(question))
})
}
}
export const createQuestion = (question) => {
return () => {
const configObj = {
method: 'POST',
question: JSON.stringify(question)
}
fetch(url, configObj)
.then(resp => resp.json())
.then(json => {
console.log(json)
})
}
}
reducer.js
export default function quizReducer(state = {questionBank: []}, action){
switch (action.type){
case "GOT_QUIZ":
return {...state, questionBank: action.payload}
default:
return state
}
}
Assuming that your reducer and actions are correct, you need to do two things in your components.
1) Initialize the Fetch
import {useDispatch} from 'react-redux';
import {fetchQuestion} from './actions';
const dispatch = useDispatch();
useEffect (() => {
dispatch(fetchQuestion());
}, []);
This will call your fetchQuestions thunk once when the component is first mounted.
2) Access the Data
import {useSelector} from 'react-redux';
const questions = useSelector(state => state.questionBank);
This will access the array of questions from your store and automatically respond to changes when the store state is updated.
Perhaps step 1 goes in App and step 2 goes in Quiz and Result. Or perhaps both steps go in App and you pass down the data through props.