sending props via the react-router Link component - reactjs

I have a problem with sending props through the Link component of the react-router.
This is my component that is responsible for displaying a specific element:
const ItemRecipe = ({ recipe }) => {
const { label, image } = recipe.recipe;
return (
<li>
<p> {label} </p>
<img src={image} alt="food" />
<Link to={{pathname: `/meals/${label}`, params: recipe }}>
<p>next</p>
</Link >
</li>
);
}
After clicking on I want to open the page with a specific recipe. This component looks like this
class DetailsRecipe extends Component {
constructor(props) {
super(props);
this.state = {
recipe: props.match.params.recipe
}
console.log(this.state.recipe);
}
render () {
<div>
lorem lorem
</div>
)
}
}
console.log(this.state.recipe) displays undefined.
how to fix this error? How to correctly send data through the Link component of the react-router?
I looked at similar topics on a stackoverflow but it did not help to solve my problem

Have another look at the documentation of the Link component. The properties allowed inside the to object are:
pathname: A string representing the path to link to.
search: A string representation of query parameters.
hash: A hash to put in the URL, e.g. #a-hash.
state: State to persist to the location.
I guess you want to use state instead of params.
EDIT:
So your code would look like this:
<Link to={{pathname: `/meals/${label}`, state: {"recipe": recipe} }}>
Then you can access the state inside the linked component like this:
this.state = {
recipe: props.location.state.recipe
};

Related

React Component crashes on reload

I have a notes-list component that gets the notes data as props from the main component.
Inside the notes-listcomponent, there is a notes-item component which has a dynamic route that loads notesItem-page. So, for every notes-item there is a dynamic url for it's respective notesItem-pagewhich has all the details about the notesItem object. I use Link from react-router-dom
The notes-list component looks like this:
export class NotesList extends Component {
constructor(props) {
super(props);
}
render() {
const { isLoggedIn } = this.props.loggedInContext;
return (
<div className="notes-list">
{this.props.notes.map((notesItem) => (
<Link
to={{
pathname: `${notesItem.slug}`,
id: notesItem._id,
}}
style={{
textDecoration: "none",
color: "#fea82f",
}}
>
<NotesItem notes={notesItem} />
</Link>
))}
</div>
);
}
}
export default loggedInContext(NotesList);
This successfully redirects me to the NotesItem-page with the correct props and inside the notesItem-page I get the receive the id of the object that I had passed as props and make an API call with that particular id in ComponentDidMount() method.
This works perfectly. However, it crashes on reload. It gives the following error:
I am guessing it is because of ComponentDidMount works only once,but I do not seem to find an alternate solution to this problem.
The notesItem-page component looks like this:
export class notesItemPage extends Component {
constructor(props) {
super(props);
this.state = {
notesItem: [],
};
}
componentDidMount() {
fetch(`http://localhost:5000/api/v1/notes/fetch/${this.props.location.id}`)
.then((notesItem) => notesItem.json())
.then((notesItem) =>
this.setState({ notesItem: notesItem.data, isLoaded: true })
);
}
render() {
const { notesItem } = this.state;
return (
<div className="notesItem-details">
<h1> {notesItem.title} Notes</h1>
</div>
);
}
}
export default notesItemPage;
It would be great if anyone could help me with this, thanks!
Issue is here:
<h1> {notesItem.title} Notes</h1>
here the notesItem is coming from an axios call and this data is not available on the time of first component render and this is causing the issue ( app crash ).
So change this:
<h1> {notesItem.title} Notes</h1>
to
<h1> { notesItem && notesItem.title } Notes</h1> // use it when it is available from axios call

How to pass state to Link from reach-router

UPDATED CODE STILL NOT WORKING
Haven't been able to find an answer on here to help with my problem.
In my Playlist component, I have an image that links to a new path for tracks. I want to pass a Playlist ID to the track so it can display tracks based on the ID it is given but no matter how I do this it wont work for me.
My Playlist Component returns:
<Link to = '/tracks' state = {{playlist:playlist.id}}>
<img alt = "album"src={playlist.images.url}/>
</Link>
In my Track Component, it is doing the following:
export default class Tracks extends React.Component {
constructor() {
super();
this.state = {
playlistid: 0
};
}
componentDidMount() {
this.setState({
playlistid: this.props.location.state.playlistid
});
}
render() {
return (
<div>
<p>PlaylistID: {this.state.playlistid}</p>
</div>
);
}
}
Can anyone tell me which Component is wrong? Sorry I'm still quite new to React
Thanks
In order for you to pass on a state to Link component from Reach router, you can pass on state as a separate prop. The to prop neeeds to be a string
<Link
to='/tracks'
state= {{
playlistid: playlist.id
}}
>
<img alt = "album"src={image.url}/>
</Link>
For more information refer to the documentation of Link from Reach-router
EDIT:
The way you have implemented the Link component works with react-router Link component
You can try this :
<Link to={{
pathname: `/tracks/${playlist.id}`, //I assume you tracked the tracklist state here as a props
}}>
<img alt = "album"src={image.url}/>
</Link>

How to pass data to functional component?

As part of React course I'm making a dummy eshop and want to pass products from state of one component to functional component and display product properties there. How to pass data from state to stateless component?
I've set up a component ProductList which stores the products (fetched from api) and ProductDetail which is supposed to show the details (find the product by id and show it's descr, img etc).
I've set up App.js which has two Routes - to List and to Detail.
App.js:
class App extends Component {
render() {
return (
<div>
<h1>E-Commerce app</h1>
<Route exact path="/" component={ProductList} />
<Route path="/:productId" component={ProductDetail} />
</div>
)
}
}
ProductList.js:
class ProductList extends Component {
state = {
isLoading: true,
products: [],
}
async componentDidMount() {
const products = await getProducts()
this.setState({ products: products.data, isLoading: false })
}
render() {
const { isLoading, products } = this.state
return (
<div>
{isLoading && '...'}
{products && (
<ul>
{products.map(item => (
<li key={item.id}>
<Link to={`${item.id}`}>
<h2>{item.attributes.name}</h2>
<img
src={item.attributes.image_url}
width="60"
alt={item.attributes.description}
/>
</Link>
</li>
))}
</ul>
)}
</div>
)
}
}
ProductDetail.js:
const ProductDetail = ({ match }) => {
return (
<div>
<p>Product ID: {match.params.productId}</p>
<img src="" alt="" />
<p>Product name: </p>
<p>Product description: </p>
</div>
)
}
Your data is in the ProductList component's state, and as i have understood you want to display one product detail in another component after clicking the link, right ?
When switching the route your component will unmount and therefore you lose data.
In order to do that you need to do some state management like Redux, MobX or other state management libraries or you can use the Context API which is a feature that has been introduced lately in React.
You will need another request on the details page /:productId.
Keep in mind that if a user go straight to the /:productId route the product data will not exist, this is why you need a request here.
To optimize you can use Context, or a lib like Redux to manage/store the data. So you can check first if the data of productId exists in the store before doing a new request.
First change Link like below
<Link to={{
pathname: `${item.id}`,
state: {
productObject: item
}
}}>
Then change ProductDetail component params.
const ProductDetail = ({ match,location })
Finally read Product Name like this.
<p>Product name:{location.state.productObject.attributes.name} </p>
Let me know if it helps.
Thanks

Adding an id to React/Gatsby component for hash link

I have a link in a nav-bar that takes me to an anchor on the index page. Currently I don't know how to put an id onto the component, so I have to wrap the component in a div and give it an id for it to work. Ideally, I would like to simply put the anchor on the component itself.
This works fine for me, but I'm wondering if this is the way to do an anchor with React/Gatsby or is there a better way?
//Navbar, which is part of Layout
export default class NavBar extends Component {
render() {
return (
<NavContainer>
<Menu>
<ul>
<li>Home</li>
<li>About</li>
<li>Events</li>
<li>Blog</li>
<li>Mentorship</li>
<li>
<Link to="/#join-us">Join Us</Link>
</li>
</ul>
</Menu>
</NavContainer>
)
}
}
//Homepage
const IndexPage = ({ data, location }) => {
const { site, events, about, features, blogs } = data
const eventsEdges = events.edges
return (
<Layout>
<div id="join-us">
<JoinUs /> //Can't do <JoinUs id="join-us"/>
</div>
<BlogList blogs={blogs} fromIndex={true} />
</Layout>
)
}
You have to pass id as a props to your JoinUs component.
First of all, do <JoinUs id="join-us" />. Now, id is a props of your component.
JoinUs component
const JoinUs = ({ id }) => (
<div id={id}>
...Your component stuff
</div>
);
Other method
import React from 'react'
class JoinUs extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div id={this.props.id}>
... Your component stuff
</div>
);
}
}
export default JoinUs
The two methods are similar but the first one is more concise.
The line JoinUs = ({ id }) ... allows you to access and destructure props. You get property id from your props. Now, you don't have to wrap your component in a div with an anchor
More information here : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

Pass props to React Router's Link Component

I'm passing location props to React Router's Link. It successfully navigates me away, but when I click "back" to go back to the previous page, I get an error:
TypeError Cannot Read property "statusUser" Undefined
It looks like the props are getting messed up when I navigate back.
Sidebar.js
Class Sidebar extends Commponent{
render(){
return(
<Link to={
{
pathname: `/user`,
state:{
statusUser: 'abc',
}
}
}><Button>User Menu</Button>
}
)
}
User.jsx
class user extends Component {
render(){
return(
<UserTable status={this.props.location.state.statusUser}/>
)
}
}
UserTable.jsx
class UserTable extends Component{
render(){
return(
<Link to={
{
pathname: `/user/detail/${this.props.status}`,
state:{
statusUser: this.props.status,
}
}
}><Button>Detail</Button>
)
}
}
UserDetail.jsx
class UserDetail extends Component{
render(){
return(
<p>{this.props.location.state.statusUser}</p>
<Link to={
{
pathname: `/user`,
}
}><Button>Back</Button>
)
}
}
I think the problem is in User.js. How do I make the props fixed like using setState or stuff like that inside User.js file?
Just to be able read the props property, and after reading it go back to the previous page by clicking "back" button.
I'm sorry I just really really new to React so any help would be really appreciate. Thank you.
I believe you're getting that error when you click the Back button in UserDetail.jsx. You aren't passing a state object to User.jsx.
The User.jsx component tries to access this.props.location.state.statusUser, but state doesn't exist, and so it says it can't read the property statusUser on undefined (state is undefined).
Here's what you're missing, with a few typos fixed:
// UserDetail.jsx
class UserDetail extends Component {
render() {
return (
<div>
<p>{this.props.location.state.statusUser}</p>
<Link
to={{
pathname: `/user`
// (You aren't passing `state`)
}}
>
<Button>Back</Button>
</Link> // <-- You weren't closing Link
</div>
);
}
}
// User.jsx
class User extends Component { // <-- Capital 'U'
render() {
return (
<UserTable
status={this.props.location.state.statusUser} // <-- You require `state`
/>
);
}
}
As for how to fix it, the easiest way would be to pass a state object with your "Back" button in UserDetail.jsx.
You can also set defaultProps in React. I'm not sure if that's applicable to you, but it lets you provide a fallback for such things:
Edit: Include example of defaultProps and propTypes.
// User.jsx
class User extends Component {
render() {
return (
<UserTable
status={this.props.location.state.statusUser}
/>
);
}
}
// Maybe something you put in a different file
const defaultLocation = {
state: {
statusUser: '', // <-- Your default statusUser
},
};
User.defaultProps = {
location: defaultLocation,
};
// import PropTypes from 'prop-types';
User.propTypes = {
location: PropTypes.shape({
state: PropTypes.shape({
statusUser: PropTypes.string.isRequired,
})
})
};
I added PropTypes in the above example. It's a way to set checks for the data you're requiring in your components.
Typically, we don't put props into defaultProps if it isRequired, though, because it will never come to that. If you're unsure what default value to give statusUser, I'd recommend starting with making it required in propTypes, and then you can refactor in the future if the need arises.
There were some bugs with your other code samples, too. I've formatted them below, and fixed the bugs. I'll point out what I fixed in comments:
Sidebar.js
class Sidebar extends Component { // <-- Lowercase 'c'; "Component"
render() {
return (
<Link
to={{
pathname: `/user`,
state: {
statusUser: "abc"
}
}}
>
<Button>User Menu</Button>
</Link> // <-- You weren't closing Link
);
}
}
UserTable.jsx
class UserTable extends Component {
render() {
return (
<Link
to={{
pathname: `/user/detail/${this.props.status}`,
state: {
statusUser: this.props.status
}
}}
>
<Button>Detail</Button>
</Link> // <-- You weren't closing Link
);
}
}

Resources