react router dom: id destructured from useParams is undefined [duplicate] - reactjs

This question already has answers here:
How to pass data from a page to another page using react router
(5 answers)
Closed 5 months ago.
for learning purposes I'm trying to create an eshop, but I'm facing issues when adding a product to the cart page. Product's 'id' is undefined in the cart. Products are fetched from my MongoDB database.
Routes component:
const PageRoutes = () => (
<Routes>
<Route path="/" element={<MainLayout />}>
<Route index element={<HomePage />} />
<Route path="/about-us" element={<AboutUs />} />
<Route path="/product-catalog" element={<ProductCatalog />} />
<Route path="/order" element={<OrderPage />} />
<Route path="/product/:id" element={<ProductInformation />} />
<Route path="auth/" element={<AuthLayout />}>
<Route path="login" element={<LoginPage />} />
<Route path="register" element={<RegisterPage />} />
</Route>
<Route path="/cart" element={<CartPage />} />
</Route>
</Routes>
);
Service file which fetches products:
const domain = 'http://localhost:8000';
const databaseCollection = 'api/products';
const relationsParams = 'joinBy=categoryId&joinBy=woodTypeId';
const fetchProducts = async () => {
const response = await fetch(`${domain}/${databaseCollection}/?${relationsParams}`);
const products = await response.json();
return products;
};
const fetchProductById = async (id) => {
const response = await fetch(`${domain}/${databaseCollection}/${id}?${relationsParams}`);
const product = await response.json();
console.log('fetchProductById', id); // it prints the id correctly, not as undefined
return product;
};
const ProductService = {
fetchProducts,
fetchProductById,
};
export default ProductService;
Cart component where I face the issue:
import * as React from 'react';
import { Container } from '#mui/material';
import { useParams } from 'react-router-dom';
import ProductService from '../../services/product-service';
const CartPage = () => {
const { id } = useParams();
const [productsInCart, setProductsInCart] = React.useState([]);
console.log('id from useParams', id);
React.useEffect(() => {
(async () => {
const fetchedProduct = await ProductService.fetchProductById(id);
setProductsInCart(fetchedProduct);
})();
}, [id]);
return (
<Container>
<pre>
{JSON.stringify(productsInCart, null, 4)}
</pre>
</Container>
);
};
export default CartPage;
Button which should add the product to the cart from ProductInformation component (it also logs the id in the console correctly):
<CustomButton
onClick={() => {
navigate('/cart');
addToCart({ id });
console.log(`add to cart ${id}`);
}}
>
Add to cart
</CustomButton>
Error that I can see in server's terminal when it breaks:
GET /api/products/632db73759073e4cb274e011?joinBy=categoryId&joinBy=woodTypeId 200 1178 -
336.686 ms
stringValue: '"undefined"',
kind: 'ObjectId',
value: 'undefined',
path: '_id',
reason: BSONTypeError: Argument passed in must be a string of 12 bytes or a string of 24 hex
characters or an integer
View model for the product:
const categoryViewModel = require('./category-view-model');
const woodTypeViewModel = require('./wood-type-view-model');
const productEverythingPopulatedViewModel = (productDocument) => ({
id: productDocument._id.toString(),
title: productDocument.title,
description: productDocument.description,
category: categoryViewModel(productDocument.categoryId),
price: productDocument.price,
img: productDocument.img,
woodType: woodTypeViewModel(productDocument.woodTypeId),
createdAt: productDocument.createdAt,
updatedAt: productDocument.updatedAt,
})
module.exports = productEverythingPopulatedViewModel;
Can anyone see the mistake I made?
Thanks!

You should add the param to the route like you did in product route.
<Route path="/cart/:id" element={<CartPage />} />
And then add the id to the navigate url.
<CustomButton onClick={() => {
navigate(`/cart/${id}`);
addToCart({ id });
console.log(`add to cart ${id}`); }}
> Add to cart </CustomButton>

Related

Accessing url only with valid token

I'm using React and firebase, where I have an admin, and there I generate a token, which I can already register in the database. So I wanted to copy this generated url, for example: localhost:5173/avaliacao/a57078f588e, where a57078f588e is the generated token id for each survey
So the user would access the Rating page only if this token is valid, otherwise it would go to a 404 page.
I'm trying, however everything I try the undefined token.
Here is my App.jsx
export function App() {
return (
<Router>
<Routes>
<Route element={<PrivateRoutes />}>
<Route element={<Admin />} path="/admin" />
<Route element={<Dashboard />} path="/dashboard" />
<Route element={<Collaborator />} path="/collaborators" />
<Route element={<Service />} path="/services" />
</Route>
<Route para element={<TokenRoutes />}>
<Route element={<Rating />} path="/avaliacao/:token" />
<Route element={<Thanks />} path="/thanks" />
<Route element={<NotFound />} path="*" />
</Route>
<Route element={<Login />} path="/" />
</Routes>
</Router>
);
}
And here is my TokenRoutes:
import { Navigate, Outlet } from "react-router-dom";
import { useToken } from "../hooks/useToken";
export function TokenRoutes() {
const { token } = useToken();
console.log(token);
return token != "undefined" ? <Outlet /> : <Navigate to="/notfound" />;
}
And my Rating page:
export function Rating() {
const { token } = useToken();
console.log(token);
let { id } = useParams();
return (
<div className="containerRating">
<span className="login100-form-title p-b-48">
<i className="zmdi zmdi-font"></i>
<img src={imgLogo} alt="Imagem" />
</span>
Param: {id}
<Form />
</div>
);
}
My useToken:
import { addDoc, collection, getDocs } from "firebase/firestore";
import { createContext, useContext, useEffect, useState } from "react";
import { toast } from "react-toastify";
import { uid } from "uid";
import { db } from "../services/firebaseConfig";
const TokenContext = createContext();
export function TokenProvider({ children }) {
const [tokens, setTokens] = useState([]);
const [token, setToken] = useState();
const tokensCollectionRef = collection(db, "tokens");
useEffect(() => {
const getTokens = async () => {
const data = await getDocs(tokensCollectionRef);
setTokens(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getTokens();
}, []);
async function generateToken() {
await addDoc(tokensCollectionRef, {
id: uid(),
createdAt: new Date().toString(),
expiredIn: new Date(
new Date().setDate(new Date().getDate() + 7)
).toString(),
used: false,
})
.then(() => {
toast.success("Avaliação gerada com sucesso!", {
theme: "colored",
});
console.log("Gerado no firebase 🔥");
})
.catch((error) => {
toast.error("Ocorreu um erro ao gerar", {
theme: "colored",
});
console.log(error.message);
});
}
return (
<TokenContext.Provider value={{ generateToken, token, tokens }}>
{children}
</TokenContext.Provider>
);
}
export function useToken() {
const context = useContext(TokenContext);
return context;
}
In order not to put all the code here, any other doubt, follow the complete code on github:
https://github.com/eltonsantos/evaluation-system-react
Resuming:
I just want to get the URL generated by me containing the token, share it with the user and the user was able to access it, but if he changes the url it gives a 404 page. url that I shared the token comes undefined

Render dynamic page in React.Js with GraphQL

I'm stuck on trying to build a blog, and I have no idea how to fetch specific data from GraphQL API to a dynamic page.
What I try to build:
There's a page called /blog with multiple Card Components. Each card is made of an image, a title and a datePosted. Each of these Cards is a Blog Post. When a user tries to click on a blog post, it clicks on a card and it's being taken to a page like /blog/posts/slug here. So far so good.
The issue:
I have no idea how to make a page dynamic and fetch the specific data from the blog post that has been clicked to a page that can be dynamic. I figure I need to use useParams in React.Js or something like that, but I have no idea how to get the specific post that's been clicked. I can only get all of them.
The code:
1) The /blog page (where I fetch all the posts - this is working properly):
import React from 'react';
import './Blog.styles.scss';
import {GraphQLClient, gql} from 'graphql-request';
import { useState } from 'react';
import { useEffect } from 'react';
import {Link} from 'react-router-dom';
const graphcms = new GraphQLClient('https://api-eu-central-1.hygraph.com/v2/cl66hwcamapj001t76qqlhkl8/master');
const QUERY = gql`
{
posts {
id,
title,
datePublished,
slug,
content{
html
},
coverPhoto{
url
}
}
}
`;
const Blog = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const getPosts = async () => {
const {posts} = await graphcms.request(QUERY);
setPosts(posts);
}
getPosts();
}, [])
return (
<div className='blog-container'>
<div className='posts-wrapper'>
{posts && posts.map((post) => (
<div className='blog-card'>
<Link to={'/posts/' + post.slug}>
<div className='blog-card-img-container'>
{post.coverPhoto && <img src={post.coverPhoto.url} alt='blog-card-cover-img'/>}
</div>
<div className='blog-card-title-container'>
<h1 className='blog-card-title'>{post.title}</h1>
</div>
</Link>
</div>
))}
</div>
</div>
)
}
export default Blog
2) The dynamic page that should display ONLY the post that has been previously clicked ->
import React, { useState } from 'react';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import {GraphQLClient, gql} from 'graphql-request';
const graphcms = new GraphQLClient('https://api-eu-central-1.hygraph.com/v2/cl66hwcamapj001t76qqlhkl8/master');
const QUERY = gql`
query Post($slug: String!) {
post(where: {slug: $slug}) {
id,
title,
slug,
datePublished,
content{
html
}
coverPhoto{
url
}
}
}
`;
// const SLUGLIST = gql`
// {
// posts {
// slug
// }
// }
// `;
const BlogPost = () => {
const {slug} = useParams();
const [postData, setPostData] = useState({});
useEffect(() => {
const getPost = async () => {
// const slugs = await graphcms.request(SLUGLIST);
const data = await graphcms.request(QUERY);
setPostData(data);
}
getPost();
}, [slug])
useEffect(() => {
console.log(postData);
}, [postData, slug]);
return (
<div>BlogPost</div>
)
}
export default BlogPost
3) The Routes page:
function App() {
return (
<Routes>
<Route path='/' element={<Navbar />}>
<Route index element={<Homepage />}/>
<Route path='/despre-noi' element={<DespreNoi />} />
<Route path='/galerie' element={<Galerie />}/>
<Route path='/contact' element={<Contact />} />
<Route path='/blog' element={<Blog />} />
<Route path='/animatori' element={<Animatori />} />
<Route path='/ursitoare' element={<Ursitoare />} />
<Route path='/oglinda-magica' element={<OglindaMagica />} />
<Route path='/loc-de-joaca' element={<LocDeJoaca />} />
<Route path='/posts/*' element={<BlogPost />} />
</Route>
</Routes>
)
}
export default App;
NOTE: I know the styles and everything is not refined yet, I'm just trying to get it to work. Any help would be much, much appreciated! Thank you!
Can't actually run your example to see fully what's up but I did notice these 2 things:
1). You want to setup the routes to be based so that you can grab the post id/slug from the url. In react-router this can be done with path=":id" syntax.
<Route path="posts" >
<Route path=":slug" element={<BlogPost />} />
</Route>
https://reactrouter.com/docs/en/v6/components/route
2). Graphql request needs to send the slug along as a variable.
The query Post($slug: String!) expects to be given the slug as a variable.
const variables = {
slug: slug
}
const data = await graphcms.request(QUERY, variables);
Example:
https://github.com/prisma-labs/graphql-request#using-graphql-document-variables

How to create Private Routes with firebase v9 reactjs [duplicate]

This question already has an answer here:
React-Router-Dom unable to render page but routes back due to PrivateRoute
(1 answer)
Closed 8 months ago.
My problem is that the moment i navigate to the homepage and the user is not authenticated the page shows for a split second and then move on to the login page. I want it to redirect to login only and not show the homepage for a split second
I already created a private route in my project but for a split second the protected routes shows when i navigate on to it.
here is my code:
AuthContextProvider
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => unsubscribe();
}, []);
const value = { user };
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
PrivateRoute.js
const PrivateRoute = ({ children }) => {
let { user } = useAuth();
if (user) {
return <Outlet />;
} else {
return <Navigate to="/login" />;
}
};
App.js
function App() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/" element={<Layout />}>
<Route element={<PrivateRoute />}>
<Route path="/" element={<Home />} />
<Route path="/home" element={<Home />} />
<Route path="/reminders" element={<Reminders />} />
<Route path="/archive" element={<Archive />} />
<Route path="/trash" element={<Trash />} />
</Route>
</Route>
</Routes>
);
}
Loginpage.js
const LoginPage = () => {
const { user } = useAuth();
const navigate = useNavigate();
const signIn = async () => {
const provider = new GoogleAuthProvider();
await signInWithRedirect(auth, provider);
};
useEffect(() => {
onAuthStateChanged(auth, (currentUser) => {
if (currentUser) {
navigate("/");
}
});
}, []);
return (
<>
<button
onClick={signIn}
className="bg-blue-500 p-3 text-white hover:bg-blue-600"
>
Sign In With Google
</button>
</>
);
};
Code for hindering the Loggedin User to go to the Login Route
I have created a SpecialRoute which will redirect the loggedIn User to the mainpage if the user tries to go the login page.
SpecialRoute.js
import { Login } from '../pages/Login';
import { useAuth } from '../firebase-config';
import React from 'react';
import { Outlet, Navigate } from 'react-router-dom';
// import { useAuth } from '../firebase-config';
export const SpecialRoute = () => {
const user = useAuth();
return user ? <Outlet /> : <Navigate to="/" replace />;
};
App.js
<Route element={<SpecialRoute />}>
<Route path="/login" element={<Login />} />
</Route>
In your Private Route Component, do this :-
const PrivateRoute = ({ children }) => {
let { user } = useAuth();
return typeof user === 'undefined' ? (
<h1>Loading.....</h1>
) : user ? (
<Outlet />
) : (
<Navigate to="/login" />
);
};
Below is how I created my private routes (in Firebase v9):-
useAuth Hook
// custom hook
export function useAuth() {
//
const [currentUser, setCurrentUser] = useState<any>();
useEffect(() => {
const unSubscribe = onAuthStateChanged(auth, (user) =>
setCurrentUser(user)
);
return unSubscribe;
}, []);
return currentUser;
}
My PrivateRoute/ProtectedRouted Component
import { Login } from '../pages/Login';
import React from 'react';
import { Outlet } from 'react-router-dom';
import { useAuth } from '../firebase-config';
export const ProtectedRoute = () => {
const user = useAuth();
console.log('/////user autheticated', user);
return typeof user === 'undefined' ? (
<h1>Loading.....</h1>
) : user ? (
<Outlet />
) : (
<Login />
);
};
App.js
function App() {
return (
<>
<Router>
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/" element={<Home />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signin />} />
</Routes>
</Router>
</>
);
}

undefiined useContext destructure

i am trying save user object in context but i am gettin g undefined
this is my context:
import { createContext } from "react";
export const UserContext = createContext(null)
this is routs :
import { UserContext } from './contexts/UserContext.js';
const [user, setUser] = useState();
<UserContext.Provider value={{user:user , setUser:setUser}}>
<Routes>
<Route path="/login" exact element={ <Login />} />
<Route path="/numberinput" exact element={<NumberInput />} />
<Route path="/VerifyPhone" exact element={<VerifyPhone />} />
<Route path="/Register" exact element={<Register />} />
<Route path="/ChangePassword" exact element={<ChangePassword />} />
<Route path="/" exact element={<PrivateRoute><Home /></PrivateRoute>} />
{/* <Route path="/Answers" exact element={<Answers />} />
<Route path="/results/:id/:quizzes_id" exact element={<Results />} /> */}
<Route path="/payment" element={<PrivateRoute><Payment /></PrivateRoute>} />
<Route path="/*" element={<FourOFour />} />
</Routes>
</UserContext.Provider>
and this is how i want to store data in another file:
import { UserContext } from '../../contexts/UserContext.js';
const { setUser } = useContext(UserContext);
baseUrl
.post('api/v1/login', data)
.then((response) => {
setUser(response.data.data);
console.log(response.data.data);
Swal.fire({
icon: 'success',
title: response.data.data.message,
showConfirmButton: false,
timer: 1000,
}).then(() => {
window.location.pathname = '/';
});
})
and when i log the user in '/' rout i am getting undefiend
You should initialize properties of the context in first parameter of the createContext function as follows,
const userContext = createContext({ user: null, setUser: () => {} })
You forgot to add an initial value to the useState hook.
const [user, setUser] = useState(null);
And,
Don't use only a console.log() to log the user as it runs only once when your App mounts.
Do this instead to log the user every time it changes:
// state
const [user, setUser] = useState(null);
// log user every time it changes
useEffect(()=> {
console.log(user, "user from effect hook")
}, [user])
Set user in Login component
import React from "react";
// import UserContext correctly
import { UserContext } from "<path-to-UserContext>";
export default function Login() {
const { user, setUser } = useContext(UserContext);
// set the user
useEffect(() => {
setUser("something");
}, []);
return <></>;
}
Note: I'm assuming that you are getting the data from API correctly.

reactJs App js - passing incoming data as props

How do I pass data from the app.js file as json to the app.js file as props. I want to display the incoming data as json in the whole project.
I want to pass the incoming data as props.
{id: 1, first_name: "", last_name: "", profile: {…}}
{id: 1, userKey: "0245abb9-2837-4f37-ae02-9be1b88887ef", gsmNo: "05442221111", phone: ""}
Thank you from now
import React, { Component } from 'react';
import {BrowserRouter, Route, Switch } from 'react-router-dom';
// import { renderRoutes } from 'react-router-config';
import './App.scss';
import {updateCustomer} from "../components/helpers/actions/customerActions";
import { connect } from "react-redux";
const loading = () => <div className="animated fadeIn pt-3 text-center">Loading...</div>;
// Containers
const DefaultLayout = React.lazy(() => import('../containers/DefaultLayout'));
// Pages
const Login = React.lazy(() => import('../views/Pages/Login'));
const Register = React.lazy(() => import('../views/Pages/Register'));
const Page404 = React.lazy(() => import('../views/Pages/Page404'));
const Page500 = React.lazy(() => import('../views/Pages/Page500'));
class App extends Component {
// eslint-disable-next-line no-useless-constructor
constructor(props) {
super(props);
this.state = {
profile_items: [ ]
}
}
componentDidMount() {
this.props.onUpdateCustomer({ID: "-1", customerKey: "-1"});
console.log("app.js");
return fetch('http://127.0.0.1:8000/api/account/me',
{
headers: {
Authorization: `Bearer ${localStorage.getItem("id_token")}`,
"Content-Type": "application/json"
},
})
.then((response) => response.json() )
.then((responseData) => {
console.log(responseData);
this.setState({
profile_items: responseData
});
//console.log(this.state.profile_items)
return responseData;
})
.catch(error => console.warn(error));
}
render() {
return (
<BrowserRouter>
<React.Suspense fallback={loading()}>
<Switch >
<Route exact path="/login" name="Login Page" render={props => <Login {...props}/>} />
<Route exact path="/register" name="Register Page" render={props => <Register {...props}/>} />
<Route exact path="/404" name="Page 404" render={props => <Page404 {...props}/>} />
<Route exact path="/500" name="Page 500" render={props => <Page500 {...props}/>} />
<Route path="/" name="Home" render={props => <DefaultLayout {...props}/>} />
</Switch>
</React.Suspense>
</BrowserRouter>
);
}
}
const mapStateToProps = (state, props) => {
return state;
};
const mapDispatchToProps = {
onUpdateCustomer: updateCustomer,
};
export default connect(mapStateToProps, mapDispatchToProps) (App );
You can use HOC to pass props into Lazy Component. With my option, I think it work.
Create HOC:
const LazyHOC = ({ component: Component, ...rest }) => (
<Component {...rest} />
)
import your component:
const ComponentExample = React.lazy(() => import('./components/ComponentExample'));
Wrap Component with HOC:
const LazyComponentExample = props => <LazyHOC component={ComponentExample} {...props}/>
And you can pass props like this:
<React.Suspense fallback={loading()}>
<Switch >
<Route
exact path="/login"
name="Component example"
render={<LazyComponentExample props={...} />} />
</Switch>
</React.Suspense>

Resources