So what im trying to do, is that i have a route which is /product defined in my app.js and it has a parameter of id
App.js
<Route path='/product' element={<Product />} />
Product.js
export const Product = ({ ID }) => {
what im trying to navigate to the page /product using 'useNavigate()' and pass an id i have so i can use it there by doing so, however it's not working and showing as undefined.Does anyone have any clue about how to fix this?
const handleOpen = (id) => {
navigate('/product',{ state: { id: id } }); };
You can use useLocation to get state.
export const Product = () => {
const location = useLocation()
const id = location.state.id
}
You code is correct, the path to your prop should be props.location.state.id in the <Product /> page.
Related
In an app I'm currently working on, the authentification has been done like this, to pass user's data into children components (in App.js, I would have rather used useContext to access user data in whatever component) [App.js]:
const RequireAuth = ({ children }) => {
// fetch user data from database, ...
return <>{React.cloneElement(children, { user: {...user, ...userExtraData} })}</>;
};
Then, an example of a Route is specified as [App.js]:
<Route
path="/addgame"
element={
<RequireAuth>
<FormAddGame />
</RequireAuth>
}
/>
However, my current problem is the following:
From a ComponentA, I want to navigate to /addgame (=FormAddGame component, see below) while setting an existingGame prop. Thus, I use [ComponentA.js]:
let navigate = useNavigate()
navigate('addgame', { existingGame: game })
The said FormAddGame component is [FormAddGame.js]:
function FormAddGame({ user }) {
const { existingGame } = useLocation()
console.log(existingGame)
...
}
export default FormAddGame;
However, while I correctly navigate to /addgame, the existingGame prop stays undefined once in FormAddGame while it should be not (as game is not undefined)
Try passing props like this:
navigate("addgame", { state: { existingGame: game } });
and destructure like this:
const { state: { existingGame } = {} } = useLocation();
I'm using React and React Router. I have all my data fetching and routes defined in App.js.
I'm clicking the button in a nested child component <ChildOfChild /> which refreshes my data when clicking on a button (passed a function down with Context API) with a fetch request happening in my top component App.js (I have a console.log there so it's fetching on that click for sure). But the refreshed state of data never arrives at the <ChildOfChild /> component. Instead, it refreshes the old state. What am I doing wrong. And how can I ensure my state within <Link>is refreshing on state update.
I expect the item.name value to be updated on button click.
App component
has all the routes and data fetching
uses Reacts Context API, which I use to pass my fetching to child components
below the basic shape of the App component.
import React, {useEffect, useState} from "react";
export const FetchContext = React.createContext();
export const DataContext = React.createContext();
const App = () => {
const [data, setData] = useState([false, "idle", [], null]);
useEffect(() => {
fetchData()
}, []);
const fetchData = async () => {
setData([true, "fetching", [], null]);
try {
const res = await axios.get(
`${process.env.REACT_APP_API}/api/sample/`,
{
headers: { Authorization: `AUTHTOKEN` },
}
);
console.log("APP.js - FETCH DATA", res.data)
setData([false, "fetched", res.data, null]);
} catch (err) {
setData([false, "fetched", [], err]);
}
};
return (
<Router>
<DataContext.Provider value={data}>
<FetchContext.Provider value={fetchData}>
<Switch>
<Route exact path="/sample-page/" component={Child} />
<Route exact path="/sample-page/:id" component={ChildOfChild} />
</Switch>
</FetchContext.Provider>
</DataContext.Provider>
</Router>
)
}
Child component
import { DataContext } from "../App";
const Child = () => {
const [isDataLoading, dataStatus, data, dataFetchError] = useContext(DataContext);
const [projectsData, setProjectsData] = useState([]);
{
data.map((item) => (
<Link
to={{
pathname: `/sampe-page/${item.id}`,
state: { item: item },
}}
>
{item.name}
</Link>
));
}
Child of Child component
import { FetchContext } from "../App";
const ChildOfChild = (props) => {
const getData = useContext(FetchContext);
const [item, setItem] = useState({});
const [isItemLoaded, setIsItemLoaded] = useState(false);
useEffect(() => {
if (props.location.state.item) {
setItem(props.location.state.item);
setIsItemLoaded(true);
}
}, [props]);
return (
<div>
<button onClick={() => getData()}Refresh Data</button>
<div>{item.name}</div>
</div>
)
}
Issue
The specific data item that ChildOfChild renders is only sent via the route transition from "/sample-page/" to "/sample-page/:id" and ChildOfChild caches a copy of it in local state. Updating the data state in the DataContext won't update the localized copy held by ChildOfChild.
Suggestion
Since you are already rendering ChildOfChild on a path that uniquely identifies it, (recall that Child PUSHed to "/sample-page/${item.id}") you can use this id of the route to access the specific data item from the DataContext. There's no need to also send the entire data item in route state.
Child
Just link to the new page by item id.
<Link to={`/sampe-page/${item.id}`}>{item.name}</Link>
ChildOfChild
Add the DataContext to the component via useContext hook.
Use props.match to access the route's id match param.
import { FetchContext } from "../App";
import { DataContext } from "../App";
const ChildOfChild = (props) => {
const getData = useContext(FetchContext);
const [,, data ] = useContext(DataContext);
const [item, setItem] = useState({});
const [isItemLoaded, setIsItemLoaded] = useState(false);
useEffect(() => {
const { match: { params: { id } } } = props;
if (id) {
setItem(data.find(item => item.id === id));
setIsItemLoaded(true);
}
}, [data, props]);
return (
<div>
<button onClick={getData}Refresh Data<button />
<div>{item?.name}<div>
</div>
)
}
The useEffect will ensure that when either, or both, the data from the context or the props update that the item state will be updated with the latest data and id param.
Just a side-note about using the Switch component, route path order and specificity matter. The Switch will match and render the first component that matched the path. You will want to order your more specific paths before less specific paths. This also allows you to not need to add the exact prop to every Route. Now the Switch can attempt to match the more specific path "/sample-page/123" before the less specific path "/sample-page".
<Router>
<DataContext.Provider value={data}>
<FetchContext.Provider value={fetchData}>
<Switch>
<Route path="/sample-page/:id" component={ChildOfChild} />
<Route path="/sample-page/" component={Child} />
</Switch>
</FetchContext.Provider>
</DataContext.Provider>
</Router>
I've just rewrote your code here, I've used randomuser.me/api to fetch data
Take a look here, it has small typo errors but looks ok here
https://codesandbox.io/s/modest-paper-nde5c?file=/src/Child.js
I have a component that takes a beerId from the url params, fetches a product and render.
If you refresh, renders again, no problem - in DEV MODE.
In production, the component only works ONCE, when you get to it by clicking though to this page from another Browser page. If you refresh, it's a 404. What's wrong here?
NOTE: I am deploying with Github Pages.
Component:
const BeerDetails = () => {
const [selectBeerData, setSelectBeerData] = useState({
// omit default state values
})
// get id from URL
const params = useParams();
const { beerId } = params;
// some Redux stuff
const dispatch = useDispatch()
const currencySign = useSelector(state => state.search.currencySign);
const currencyCode = useSelector(state => state.search.currencyCode);
const { beer } = useFetch(beerId)
useEffect(() => {
if(beer) {
const beerPricedInGBP = addPrice(beer)
const beerWithCurrency = applyCurrency(beerPricedInGBP, currencyCode);
const selectBeerData = getSelectedBeerDetails(beerWithCurrency);
setSelectBeerData(selectBeerData)
window.scrollTo(0, 0);
}
}, [beer])
useEffect(() => {
if(beer) {
const beerPricedInGBP = addPrice(beer)
const beerWithCurrency = applyCurrency(beerPricedInGBP, currencyCode);
const selectBeerData = getSelectedBeerDetails(beerWithCurrency);
setSelectBeerData(selectBeerData)
}
}, [currencyCode])
const { name, image_url, abv, ibu, price, tagline, description } = selectBeerData;
// return
Routes:
function App() {
return (
<Router>
<div className="App">
<div className="container">
<Header />
<Switch>
<Route exact path="/punk-beer" component={Browse} />
<Route path="/punk-beer/beers/:query" component={SearchResult} />
<Route path="/punk-beer/basket" component={Basket} />
<Route path="/punk-beer/beer-details/:beerId" component={BeerDetails} />
</Switch>
<Footer />
</div>
</div>
</Router>
);
}
export default App;
Many thanks for looking at this folks!
If you have the same issue, here is a comprehensive guide to fix it:
https://github.com/rafgraph/spa-github-pages
But it's not very easy. In the end, I deployed on Netlify instead of gh pages. It has the same problem re:client-side routing but it's waaaaay easier to fix. Here is all you need:
https://hiddedevries.nl/en/blog/2020-06-27-how-deployment-services-make-client-side-routing-work
(scroll to the Netlify section)
Additional guides on the netlify.toml file and syntax:
https://docs.netlify.com/routing/redirects/
https://docs.netlify.com/configure-builds/file-based-configuration/#redirects
Good luck!
In my app, I have a list of university departments. When you click a specific department, you are taken to the department landing page (/department/:deptId). I am using React Router's useParams hook to get the department id from the URL and then find that specific department object from my array of departments passed down as props.
This works fine when navigating from the list of departments to the individual department page, but if I refresh the page, I get the following error: Uncaught TypeError: can't access property "Name", dept is undefined
My code is below:
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
const Department = props => {
const { id } = useParams();
const [dept, setDept] = useState({});
useEffect(() => {
const unit = props.depts.find(item => item.Id === Number(id));
setDept(unit);
}, [id]);
return (
<div>
<h1>{dept.Name}</h1>
</div>
);
};
export default Department;
I'm not sure why this happens. My understanding is that the props should remain the same, and the useEffect should run when the page is refreshed. Any idea what I'm missing?
More code below:
The depts array is passed as props from the App component, which is getting it from an axios call in a Context API component.
import { UnitsContext } from './contexts/UnitsContext';
function App() {
const { units } = useContext(UnitsContext);
return (
<>
<Navigation />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/people" component={Persons} />
<Route exact path="/department/:id">
<Department depts={units} />
</Route>
</Switch>
</>
);
}
// Context Component. Provider wraps `index.js`
export const UnitsContext = createContext();
export const UnitsContextProvider = props => {
const url = 'http://localhost:5000';
const [units, setUnits] = useState([]);
useEffect(() => {
axios
.get(`${url}/api/units`)
.then(res => {
setUnits(res.data);
})
.catch(err => {
console.log(err);
});
}, []);
return (
<UnitsContext.Provider value={{ units }}>
{props.children}
</UnitsContext.Provider>
);
};
the problem is most probably with this,
useEffect(() => {
const unit = props.depts.find(item => item.Id === Number(id));
setDept(unit); // <<
}, [id]);
Nothing else in ur code sets State except setDept(unit);
So, My best guess is props.depth find matches nothing and returns null. Thats why dept.Name results with the error
From MDN,
The value of the first element in the array that satisfies the provided testing function. Otherwise, undefined is returned
I am trying to set up a react app where a list of buttons are displayed, the user can press a button and be taken to a page with information about a country. I am creating the buttons programmatically using a .map function. I am using a SQL database to store country names, and information about the countries, and then calling a flask route to pull the data into my react app. For that, I am using an async function.
This is the process that I would like to have happen:
I set up some stateful variables in my App.js main router component. I then pass as props my setState functions to my component with the buttons and the .map function. For each button, there is the option to set the state of the variables in the App.js component. I would then set the variables in App.js to the values associated with the button clicked. From there, I could pass those stateful variables to my country page component for display.
What actually happens:
I pass the props to my country component, expecting a country and country details to pass along with it, but I end up getting undefined. It looks like undefined might be the last element of the dataset, as I have gotten Zimbabwe as the result before. Here is my code for the App.js router:
export default function App() {
const [cname, setCName] = useState('')
const [pdf, setPdf] = useState('')
const [details, setDetails] = useState('')
return (
<div className="App">
<BrowserRouter>
{/* <Route exact path="/" component = { Home }/> */}
<Route path="/cia" component = {(props) => <CIALanding {...props} setCName={setCName} setPdf={setPdf} setDetails={setDetails}/>}/>
<Route path="/country" component={(props) => <Country {...props} setCName={setCName} details={details} cname={cname}/>}/>
<Route path="/countrypage" component={CountryPage}/>
</BrowserRouter>
</div>
);
}
Here is the code for my landing page (with the .map function)
export default function CIALanding(props) {
const [countriesList, setCountriesList] = useState([])
const getCountries = async () => {
const response = await fetch('http://127.0.0.1:5000/countries');
const data = await response.json();
setCountriesList(data['country_list'].map((country) => {return (
<Link to={{pathname:'/country',
}}>
<Country cname1={country[0]} details={country[2]} setCName={props.setCName}>{country[0]}</Country>
</Link>
)}))
}
useEffect(() => {
getCountries()
},[])
return (
<div>
{countriesList}
</div>
)
}
Here is my code for the Country Component
export default function Country(props) {
return (
<div>
{console.log(props.cname)}
<Button onClick={props.setCName(props.cname1)}>{props.cname1}</Button>
</div>
)
}
Thanks a lot for the help!
I will not exactly anwser to your question but I propose some refactoring and maybe that will solve your problem.
Firstly I will move fetching code to the App component, it will allow easier access to this data by components (I added some nice handling of fetching status change). Here you will render proper Routes only if data is fetched successfully.
const App = () => {
const [status, setStatus] = useState(null);
const [countries, setCountries] = useState([]);
const getCountries = async () => {
setStatus('loading');
try {
const response = await fetch('http://127.0.0.1:5000/countries');
const data = await response.json();
setCountriesList([...data['country_list']]);
setStatus('success')
} catch (error) {
setSatus('error');
}
}
useEffect(() => {
getCountries();
}, [])
if (!status || status === 'error') {
return <span>Loading data error</span>
}
if (status === 'loading') {
return <span>Loading...</span>
}
return (
<div className="App">
<BrowserRouter>
<Route path="/cia" component={(props) => <CIALanding {...props} countries={countries} />
<Route path="/country/:countryId" component={(props) => <Country {...props} countries={countries} />
</BrowserRouter>
</div>
);
}
Second thing - to display proper country page you don't need to set any data into state, only thing you need is to set route /country/:countryId and Links with proper paths where countryId can be unique country identyficator as number or code. With setup like this only data needed in component is array of countries and which country is loaded is decided by routing
Landing component will be nice and simple (you definitely shouldn't keep React components in state, only data)
const CIALanding = ({countries}) => (
<div>
{
countries.map(({countryName, countryId}) => (
<Link to={`/country/${countryId}`}>{countryName}</Link>
))
}
</div>
)
So now we have nice list of countries with proper links. And then country page will know which data to display by param countryId
const Country = ({match, countries}) => {
//match object is passed by Route to this component and inside we have params object with countryId
const {countryId} = match.params;
const country = countries.find(country => country.countryId === countryId);
if (country) {
return (
<div>
Show info about selected country
</div>
);
}
return (
<div>
Sorry, cannot find country with id {countryId}
</div>
)
}
And you can access proper country page by clicking on Link and additionally by entering path for example .../country/ENG in browser (I don't know your data structure so remeber to use correct data for countryId) ;)
Sorry if this don't resolve your problems but I hope it contains at least some nice ideas for refactoring ;)