I am working on project which is based on Firebase and React JS, i want to set ID in Link in Route Component but its returning me undefined,
It goes like, first List of Trainers Page and then Edit Trainer (specifically)
Main Component Look like this where i am giving my routes
<Route path= '/listOfTrainers' component={ListOfztrainers} />
<Route path='/editTrainer/:UID' component={EditTrainer} />
Then there is a list of trainer page, where i am showing list of trainer in table. One of its column in Edit Profile where i am giving link like below
Cell: <Link to= {`/editTrainer/${trainer.UID}`}>Edit</Link>
When i click on any trainer's row Edit Profile, it return me undefined in url
And when my self add ID which is firebase UID in url, it gives me correct result like
I am dropping code below of edit Trainer,
componentDidMount(){
const id = this.props.match.params.UID;
console.log(id);
firebase.database().ref(`Users/Trainers/` + id).on('value', (snapshot) => {
var trainer = snapshot.val();
this.setState ({
trainer
}, console.log(trainer))
})
}
Can anybody explain what I am doing wrong here in routing
to solve this problem you need to make sure that your trainer data fetched and parsed on DOM correctly like so :
componentDidMount(){
const id = this.props.match.params.UID;
console.log(id);
firebase.database().ref(`Users/Trainers/` + id).on('value', (snapshot) => {
var trainer = snapshot.val();
this.setState ({
trainer
}, console.log(trainer))
})
}
render() {
let {trainer} = this.state;
return (
<>
// in this way you can make sure that data parsed correctly on your dom elements
{trainer && <Link to= {`/editTrainer/${trainer.UID}`}>Edit</Link>}
</>
)
}
Related
I have a route like this one
<Route
path="/post/:post_id"
exact
component={PostPage}
/>
Which loads my <PostPage/> component.
In that component, I load my post data (including its name) using the post_id parameter.
Once the data is successfully loaded based on that post_id; I would like to use the name of that post to make the browser URL prettier.
Example:
/post/123* : goes to my <PostPage/> component
which loads the post datas for post ID 123; including its name: post-foo-bar
I would like the browser URL to change to /post/123/post-foo-bar
I guess that using a wrong name should redirect to the good one :
/post/123/post-not-so-foo-bar would redirect to /post/123/post-foo-bar
How could I achieve this ?
Thanks !
Assuming you are using react-router v6 (it'd be easier/different in v5)
I went through the base tutorial for react-router, so it's a pretty basic app example:
https://stackblitz.com/edit/github-ylwf81?file=src%2Froutes%2Finvoice.jsx
Resulting webpage: https://github-ylwf81--3000.local.webcontainer.io/invoices/1995/Santa%20Monica
The main things to note in this, are the route definitions
For the invoices which would be analogous to your posts:
<Route path="invoices" element={<Invoices />}>
<Route
index
element={
<main style={{ padding: '1rem' }}>
<p>Select an invoice</p>
</main>
}
/>
<Route path=":invoiceId">
<Route index element={<Invoice />} />
<Route path=":invoiceName" index element={<Invoice />} />
</Route>
</Route>
Note the use of an index route for just the invoiceId, and a separate route for invoiceId + invoiceName.
Because they are both output to the same element, they aren't treated as different by React, so it retains state properly. You can see that in the example by the fact that it doesn't go back to the loading state.
As for the navigation to add in the invoiceName to the URL, it's just a basic navigation in a useEffect when the name of your object is defined (after an async call). And because this always triggers when the invoice.name changes, and how we aren't actually using the :invoiceName variable, it doesn't matter at all what the user inputs for that as long as the :invoiceId is correct
useEffect(() => {
if (!invoice?.name) {
return;
}
navigate(`/invoices/${invoice.number}/${invoice.name}`);
}, [invoice?.name, invoice?.number]);
In react-router-dom v5 you can declare an array of matching paths to render a routed component on. I suggest adding a matching path that includes a route param for the pretty name.
Example:
<Route
path={["/post/:post_id/:postName", "/post/:post_id"]}
component={PostPage}
/>
Then use an useEffect hook on the `PostPage component to check the following cases:
If post_id param and post id mismatch, then fetch post by id, then redirect to path with post name.
If postName param, current fetched post, and pretty name
mismatch, then redirect to current path with correct post name.
If no postName param, current fetched post, then redirect to path with post name.
Code
const { params, path } = props.match;
const { post_id, postName } = params;
const [post, setPost] = useState();
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchPost = async () => {
setLoading(true);
try {
const post = await fetchPostById(post_id);
setPost(post);
history.replace(
generatePath(`${path}/:postName`, {
post_id: post.id,
postName: post.name
})
);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
if (post_id && post_id !== post?.id) {
fetchPost();
} else if (postName && postName !== post?.name) {
history.replace(
generatePath(path, {
post_id: post.id,
postName: post.name
})
);
} else if (!postName && post?.name) {
history.replace(
generatePath(`${path}/:postName`, {
post_id: post.id,
postName: post.name
})
);
}
}, [history, post, post_id, postName, path]);
I have a problem when I'm trying to load a picture from my Flamelink schema that is connected to my Firebase DB.
I have a simple component that displays a puppy for sale:
const PuppyCard = (props) => {
return (
<div className='PuppyCard'>
<div className='PuppyCard__img'>
<img src={props.data.picture} alt={props.data.name} />
</div>
<div className='PuppyCard__text'>
<h2>{props.data.name}</h2>
<h3>{props.data.sex}</h3>
<h4>{props.data.age}</h4>
<h4>{props.data.price}</h4>
</div>
</div>
)
}
And as you can see there is a props.data.picture as my src for the image, but when i try to load it on the website i get everything but the picture. After looking into my DB i saw that the picture is not a file but a reference to the other folder created by flamelink:
1
This is what i am fetching from the DB right now.
getPuppies = () => {
db
.collection('fl_content')
.get()
.then(docs => {
if (!docs.empty) {
let allPuppies = []
docs.forEach(function (doc) {
const puppy = {
id: doc,
...doc.data()
}
allPuppies.push(puppy)
})
this.setState({
puppies: allPuppies
}, () => {
this.setState({
isLoaded: true
})
})
}
})
}
Any help is appreciated!
I've managed to solve this problem, although now i realize that the question is not accurate.
What I was trying to do was to get the picture that was uploaded in the Flamelink CMS to display on the website, however i was only getting a file ID when i tapped into the property of the props.
My solution to this problem was:
store the fileID in a variable
Query the folder created by the flamelink CMS inside the Cloud Firestore called "fl_files" =>
db.collection('fl_files').doc(fileID).get().then(function (doc) {
var fileName = doc.Nf.nn.proto.mapValue.fields.file.stringValue;
console.log(fileName);
storageRef.child('flamelink/media/' + fileName).getDownloadURL().then(function (url) {
console.log(url);
var img = document.getElementById('an id for the image inside a react child component');
img.src = url
})
Please note that 'flamelink/media/' is inside the Storage, not inside Cloud Firestore.
Hope this helps somebody with the same problem I've had!
Issue closed :)
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 ;)
I'm fetching data from an endpoint and trying to create routes for each individual li element. It's currently rendering the data, but I'm having trouble going to a different page whenever I select the li element.
Where can I set up the route for each element so that when I select li with the onClick method I can go to a completely different page that shows a component with the current data selected similar to an commerce website that displays all the items and goes to a detailed screen when selected? The previous list of sneakers shouldn't be visible when I go to a new page and should be an entire component on its own.
I've set up a route that has the path="/detailView/" component={DetailView} and have an onSelect() method that updates the state with the current li element selected using this.setState(state => ({name : entry.name})). this.props.history.push is commented out since it pushes a blank page without configuring the component.
enter image description here
Currently how the page is being displayed.
class Api extends Component {
componentDidMount() {
const url = 'https://yawndry-heroku.herokuapp.com/sneakers'
fetch(url)
.then(result => result.json())
.then(result => {
this.setState({
data: result
})
})
}
onSelect(entry) {
let path = entry.name
console.log(entry.name)
this.setState(state => ({
name : path
}))
// this.props.history.push('/detailView/')
}
render() {
const {data} = this.state
const result = data.map((entry, index) => {
return <h3>
<li key={index} onClick={() => this.onSelect(entry)} >{entry.name} {entry.condition}</li>
<Link to="/detailView/" onClick={() => this.onSelect(entry)}>More Details</Link>
</h3>
})
return <Router>
<Route exact path="/detailView/"
render={(props) => <DetailView {...props} name={this.state.name}/>} />
<ul>{result}</ul>
</Router>
}
export default withRouter(Api)
I have a similar thing where we send the user to a specific ticket page when clicking on the title. Here is how it is set up :
we have a Link with a path and something that identifies the item
<Link to={`/detailView/id`} target={'_blank'}>
In the Router :
<Route path="/detailView/:key" component={DetailView} />
In the Component, I have a call to my API that retrieves the data :
componentDidMount() {
// Set title page
document.title = this.props.match.params.key;
//Retrieve the data
this.props.getAll(this.props.match.params.key)
}
This depends on where you get your data from, but then you can populate your component with your retrieved data, depending on what you put in your render.
Hope it helps.
so I have a redux store with has this basic structure:
{
user: {
id: 1,
currentCompanyId: 2,
}
companyDetails: {
id: 2,
name: 'Joes Company'
},
otherCompanies: [2,3,4],
}
I have a parent page which in the header has a dropdown that allows the user to link / switch to another company.
The parent component needs to know which company is selected so it can show the name in the heading.
The child component displays the details of the current company.
There will be various types of companies and the url's / sections will be different for each type. So in the child component I was trying to set the user's company and then load the details of that company.
The child component doesn't need to directly reference the user or current company.
So what I was doing was in the child component I would listen in willmount and willreceiveprops for a change to the url, then fire an action to update the user company. This will then cause the parent to re render as the user object has changed. Which in turn will create a new / remount the child component. So far this seemed logical.
The issue is that when I have selected company 2 and try to switch to company 3, it will set the company to 3, but then reset it back to 2 again.
I am not sure if this is to do with the URL having not updated or something. I have gone around in circles so much now that I am not sure what the workflow should be anymore.
edit
if I comment out this.loadContractorInfo(contractorId); from ContractorHome.js in componentWillMount() it will work correctly (i.e. the URL stays with the number in the link, vs reverting to the old one. I assume this is to do with redux actions are async, and although I am not doing any network calls it is some race condition between getting data for the contractor page and the contextheader wanting to display / update the current company
edit 2
so to confirm. I have loaded the page at the root of the site, all fine. I select a company / contractor from the dropdown. this loads fine. I go to change that selection to a different contractor from the dropdown. The first component to be hit will be the parent (contextheader), the location prop in nextprops will have updated to have the correct ID in the URL. the method execution at this point will NOT update any thing, no actions are fired. It then hits the child (contractorhome) willreceiveprops method, again the URL in location is good as is the match params. I have commented out all code in willrecieveprops so it does not do anything here either. It will then go back to the parent willreceive props and the location will have gone back to the previous ID in the URL.
app.js snippet:
render() {
return (
<div className="app">
<Router>
<div>
<div className="bodyContent">
<ContextHeader />
<Switch>
{/* Public Routes */}
<Route path="/" exact component={HomePage} />
<Route path="/contractor" component={ContractorRoute} />
<Route path="/building" component={BuildingRoute} />
</Switch>
</div>
<Footer />
</div>
</Router>
</div>
);
}
contextheader:
componentWillReceiveProps(nextProps) {
const { setCompany } = this.props;
const currentInfo = this.props.sharedInfo && this.props.sharedInfo.currentCompany;
const newInfo = nextProps.sharedInfo && nextProps.sharedInfo.currentCompany;
if (newInfo && newInfo.id && (!currentInfo || currentInfo.id !== newInfo.id)) {
setCompany(newInfo.id, newInfo.type);
}
}
render() {
const { user, companies, notifications } = this.props;
/* render things here */
}
);
}
}
const mapStateToProps = state => ({
user: state.user,
sharedInfo: state.sharedInfo,
companies: state.companies,
notifications: state.notifications,
});
const mapDispatchToProps = dispatch => ({
setCompany: (companyId, type) => dispatch(setCurrentCompany(companyId, type)),
});
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ContextHeader));
contractorRoute:
const ContractorRoute = ({ match }) => (
<Switch>
<Route path={`${match.path}/:contractorId`} component={ContractorHome} />
</Switch>
);
contractorHome
componentWillMount() {
const contractorId = parseInt(this.props.match.params.contractorId, 10);
this.setSharedCompany(contractorId);
this.loadContractorInfo(contractorId);
}
componentWillReceiveProps(nextProps) {
const newContractorId = parseInt(nextProps.match.params.contractorId, 10);
if (this.props.match.params.contractorId !== nextProps.match.params.contractorId) {
this.setSharedCompany(newContractorId);
}
}
setSharedCompany(contractorId) {
const { sharedInfo, setCompany } = this.props;
if (typeof contractorId === 'number') {
if (!sharedInfo || !sharedInfo.currentCompany || !sharedInfo.currentCompany.id || sharedInfo.currentCompany.id !== contractorId) {
setCompany(contractorId);
}
}
}
loadContractorInfo(contractorId) {
const { sharedInfo, getContractorInfo, busy } = this.props;
if (!busy && sharedInfo && sharedInfo.currentCompany && sharedInfo.currentCompany.id === contractorId) {
getContractorInfo(contractorId);
}
}
render() { /*render lots of things here*/};
const mapStateToProps = (state) => {
const selector = state.contractor.details;
return {
sharedInfo: state.sharedInfo,
details: selector.info,
error: selector.request != null ? selector.request.error : null,
busy: selector.request && selector.request.busy,
};
};
const mapDispatchToProps = dispatch => ({
getContractorInfo: contractorId => dispatch(getContractor(contractorId)),
setCompany: contractorId => dispatch(setSharedinfoCurrentCompany(contractorId, 'contractor')),
});
export default connect(mapStateToProps, mapDispatchToProps)(ContractorHome);