How to pass data to functional component? - reactjs

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

Related

react recreating a component when I don't want to

I'm super new to react, this is probably a terrible question but I'm unable to google the answer correctly.
I have a component (CogSelector) that renders the following
import React from "react"
import PropTypes from "prop-types"
import Collapsible from 'react-collapsible'
import Cog from './cog.js'
const autoBind = require("auto-bind")
import isResultOk from "./is-result-ok.js"
class CogSelector extends React.Component {
constructor(props) {
super(props)
this.state = {
docs: null,
loaded: false,
error: null
}
autoBind(this)
}
static get propTypes() {
return {
selectCog: PropTypes.func
}
}
shouldComponentUpdate(nextProps, nextState){
if (nextState.loaded === this.state.loaded){
return false;
} else {
return true;
}
}
componentDidMount() {
fetch("/api/docs")
.then(isResultOk)
.then(res => res.json())
.then(res => {
this.setState({docs: res.docs, loaded: true})
}, error => {
this.setState({loaded: true, error: JSON.parse(error.message)})
})
}
render() {
const { docs, loaded, error } = this.state
const { selectCog } = this.props
if(!loaded) {
return (
<div>Loading. Please wait...</div>
)
}
if(error) {
console.log(error)
return (
<div>Something broke</div>
)
}
return (
<>
Cogs:
<ul>
{docs.map((cog,index) => {
return (
<li key={index}>
<Cog name={cog.name} documentation={cog.documentation} commands={cog.commands} selectDoc={selectCog} onTriggerOpening={() => selectCog(cog)}></Cog>
</li>
// <li><Collapsible onTriggerOpening={() => selectCog(cog)} onTriggerClosing={() => selectCog(null)} trigger={cog.name}>
// {cog.documentation}
// </Collapsible>
// </li>
)
})}
{/* {docs.map((cog, index) => { */}
{/* return ( */}
{/* <li key={index}><a onClick={() => selectCog(cog)}>{cog.name}</a></li>
)
// })} */}
</ul>
</>
)
}
}
export default CogSelector
the collapsible begins to open on clicking, then it calls the selectCog function which tells it's parent that a cog has been selected, which causes the parent to rerender which causes the following code to run
class DocumentDisplayer extends React.Component{
constructor(props) {
super(props)
this.state = {
cog: null
}
autoBind(this)
}
selectCog(cog) {
this.setState({cog})
}
render(){
const { cog } = this.state
const cogSelector = (
<CogSelector selectCog={this.selectCog}/>
)
if(!cog) {
return cogSelector
}
return (
<>
<div>
{cogSelector}
</div>
<div>
{cog.name} Documentation
</div>
<div
dangerouslySetInnerHTML={{__html: cog.documentation}}>
</div>
</>
)
}
}
export default DocumentDisplayer
hence the cogSelector is rerendered, and it is no longer collapsed. I can then click it again, and it properly opens because selectCog doesn't cause a rerender.
I'm pretty sure this is just some horrible design flaw, but I would like my parent component to rerender without having to rerender the cogSelector. especially because they don't take any state from the parent. Can someone point me to a tutorial or documentation that explains this type of thing?
Assuming that Collapsible is a stateful component that is open by default I guess that the problem is that you use your component as a variable instead of converting it into an actual component ({cogSelector} instead of <CogSelector />).
The problem with this approach is that it inevitably leads to Collapsible 's inner state loss because React has absolutely no way to know that cogSelector from the previous render is the same as cogSelector of the current render (actually React is unaware of cogSelector variable existence, and if this variable is re-declared on each render, React sees its output as a bunch of brand new components on each render).
Solution: convert cogSelector to a proper separated component & use it as <CogSelector />.
I've recently published an article that goes into details of this topic.
UPD:
After you expanded code snippets I noticed that another problem is coming from the fact that you use cogSelector 2 times in your code which yields 2 independent CogSelector components. Each of these 2 is reset when parent state is updated.
I believe, the best thing you can do (and what you implicitly try to do) is to lift the state up and let the parent component have full control over all aspects of the state.
I solved this using contexts. Not sure if this is good practice but it certainly worked
render() {
return (
<DocContext.Provider value={this.state}>{
<>
<div>
<CogSelector />
</div>
{/*here is where we consume the doc which is set by other consumers using updateDoc */}
<DocContext.Consumer>{({ doc }) => (
<>
<div>
Documentation for {doc.name}
</div>
<pre>
{doc.documentation}
</pre>
</>
)}
</DocContext.Consumer>
</>
}
</DocContext.Provider>
)
}
then inside the CogSelector you have something like this
render() {
const { name, commands } = this.props
const cog = this.props
return (
//We want to update the context object by using the updateDoc function of the context any time the documentation changes
<DocContext.Consumer>
{({ updateDoc }) => (
<Collapsible
trigger={name}
onTriggerOpening={() => updateDoc(cog)}
onTriggerClosing={() => updateDoc(defaultDoc)}>
Commands:
<ul>
{commands.map((command, index) => {
return (
<li key={index}>
<Command {...command} />
</li>
)
}
)}
</ul>
</Collapsible>
)}
</DocContext.Consumer>
)
}
in this case it causes doc to be set to what cog was which is a thing that has a name and documentation, which gets displayed. All of this without ever causing the CogSelector to be rerendered.
As per the reconciliation algorithm described here https://reactjs.org/docs/reconciliation.html.
In your parent you have first rendered <CogSelector .../> but later when the state is changed it wants to render <div> <CogSelector .../></div>... which is a completely new tree so react will create a new CogSelector the second time

How to create a second component on react using the data from the API and create a dynamic URL

I have a component showing the list of blogpost and I create a URL for every post coming from the title, into a string and then split and joint, but I need to create a second component that render the page of the blogpost itself,
my question is, is there any way to pass the state or props from the already created list component into the blogpost component and be access only by that rout URL?
this is the blogdata component that i wanted to use to transfer the state to the other components but only one is working, fetching the data from the API into this.state.blogpost
class BlogData extends Component{
state = {
blogData: []
}
componentDidMount() {
axios.get(`data.json`)
.then(res => {
const blogData = [res.data.blogPosts] ;
this.setState({blogData});
})
};
render(){
const blogData = this.state.blogData.map((value) =>
value.map((val, idx) =>
<BlogBlock
link={val.title.toString().toLowerCase().split(" ").join("-")}
title={val.title}
subtitle={val.subtitle}
thumb={val.thumb}
idx={idx}
/>
)
)
return(
<section id="blog" className="blogList">
<h2>From the Blog</h2>
<div className="blogPosts">
{blogData}
</div>
</section>
)
}
}
const BlogBlock = (props) => {
return (
<div className={`blog-post post-${props.idx}`}>
<Link to={props.link}>
<Img loader={<span className="loading-img">loading....</span>} src={`http://brittdepetter.com/images/${props.thumb}`} alt={props.title} />
<h3>{props.title}<span><br />{props.subtitle}</span></h3>
</Link>
</div>
)
}
and the component that im trying to create but not working is this one, but no lock of making the route works :(
const BlogPost = (props) => {
return (
<div>
<Router path={props.link} />
<Title title={props.title } />
<div className="textBox boxImg">
<div className="story-img-box">
<Img src={props.imgsrc } />
</div>
<div>
<Paragraph body={props.body} />
</div>
</div>
</div>
)
}
There is way to pass a extra parameter on Link. For that you should pass to attribute as object instead of string.
change your Link
<Link to={props.link}>
to
<Link to={{pathname:props.link, blog:props}}>
In your BlogPost component, access using following code
this.props.location.blog
PS. refreshing browser while you are on BlogPost component will require to re-fetch a blog again.

sending props via the react-router Link component

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
};

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

Passing data from one page to another while using router and state in ReactJS

I am new to ReactJS. I am trying to figure out how to pass data from one page to another while using router. When I click on Add User button, I want to add the user and navigate back to home page.
I've searched the web for this and found examples where data is transferred from parent to child using props. But here I am using router. How can I find a way to do this?
index.js
ReactDOM.render(
<Router history={browserHistory}>
<Route path="/" component={Home}>
<IndexRoute component={AllUsers}/>
</Route>
<Route path="/add" component={Add}/>
</Router>
, document.getElementById('root'));
Home.js:
class Home extends Component {
render() {
return (
<div>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/add">Add</Link></li>
</ul>
</nav>
<div>
{this.props.children}
</div>
</div>
);
}
}
AllUsers.js
class AllUsers extends Component {
constructor(props) {
super(props);
this.state = {
headingsData: ["Name"],
rowsData: []
}
}
componentDidMount() {
this.setState({
rowsData: [{name: "Alex"}, {name: "Jack"}]
})
}
render() {
return (
<div className="allUsersContainter">
<h2>All users</h2>
<table border="1" className="tableComp">
<Headings headings={this.state.headingsData}/>
<tbody>
<Rows rows={this.state.rowsData}/>
</tbody>
</table>
</div>
);
}
}
class Headings extends Component {
render() {
let headings = this.props.headings.map((heading, i) => {
return (<th>{heading}</th>);
});
return (<thead><tr>{headings}</tr></thead>);
}
}
class Rows extends Component {
render() {
let rows = this.props.rows.map((row, i) => {
return (<tr key={i}><td>{row.name}</td></tr>);
});
return (<tbody>{rows}</tbody>);
}
}
export default AllUsers;
Add.js
class Add extends Component {
render() {
return (
<form>
<label>First Name:</label><input placeholder="Name" ref="name" required/><br/>
<button type="submit">Add user</button>
</form>
);
}
}
export default Add;
This is a common problem to share a component state with another (not a child) component.
A good solution, in this case, is to use a state management library. Check out Redux or MobX as they are very common with React applications.
The main benefits of using those libraries with React application is Easy state sharing. This is the most important for you because you can share your state not only with child components as a props but almost for every component in your application.
For easy understanding, you can imagine state management libraries as a global state for your application, which you can modify almost from every component and subscribe to it changes.

Resources