How to pass props through a Javascript eval of a component? - javascript

I've got the following code:
Layout = React.createClass({
render() {
return (
<div>
<header>
<div className="container">
<button className="btn btn-sm pull-right"
onClick={this.handleLogout}>LogOut
</button>
</div>
</header>
<div className="container">
{this.props.content}
</div>
</div>
);
}
});
The Layout component serves as my shell. I pass it other "pages" which will get rendered with it (don't worry so much about this code, it's specific to Meteor):
ReactLayout.render(Layout, {content: <Home />});
My problem is, I want Layout to be able to pass props down to its children. In pseudo-code:
<div className="container">
{this.props.content someProp=this.props.someProp}
</div>
How do I go about doing this?

There a couple ways to go about passing the props to the children, but a simple way that we have a utility method for doing so is with React.cloneElement.
renderChildren() {
const someProps = { content : this.props.conent }// Filter props to pass
return React.Children.map(this.props.children, function(child) {
return React.cloneElement(child, someProps)
});
}
render() {
//...
<div className="container">
{this.renderChildren()}
</div>
//...
}
https://facebook.github.io/react/blog/2015/03/03/react-v0.13-rc2.html#react.cloneelement
As an example fiddle on how this looks and so you can test yourself: https://jsfiddle.net/jhuwhjp1/
~~ Note:
The previous way of doing this was React.addons.cloneWithProps which has been deprecated

Figured it out. Points to cdbitesky for pointing me in the right direction.
This render function in Layout will take whatever content I pass it (a React component) and pass it any of Layout's props.
Layout = React.createClass({
getDefaultProps() {
/*
* This will get passed down to whatever React component was passed to
* Layout via the prop "content"
*/
return {awesomeness: true}
},
render() {
return (
<div>
<header>
<div className="container">
<button className="btn btn-sm pull-right"
onClick={this.handleLogout}>LogOut
</button>
</div>
</header>
<div className="container">
{React.cloneElement(this.props.content, this.props)}
</div>
</div>
);
}
});

Related

How to update upvote counter for individual elements instead of all of them at once with React

Newbie dev learning React.
I'm trying to create an upvote functionality to a blog post in React but when I click on the upvote button I'm upvoting all of the blog post cards at once instead of the individual card.
How can I fix this? I believe the issue may be in the way I'm setting setState? But I may be wrong and looking for help.
Thanks in advance!
====
class Posts extends Component {
state= {
points: 0
}
componentDidMount() {
this.props.fetchPosts()
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.newPost) {
this.props.posts.unshift(nextProps.newPost);
}
}
handleClick = () => {
this.setState({points: this.state.points + 1})
}
render() {
const postItems = this.props.posts.map((post, index) => (
<div key={index} className="ui three stackable cards">
<div className="ui card">
<div className="content">
<div className="header">{post.title}</div>
<div className="meta"> {post.author}</div>
<div className="description">
<p>{post.body}</p>
</div>
</div>
<div className="extra content">
<i className="check icon"></i>
{this.state.points} Votes
</div>
<button className="ui button"
type="submit"
onClick={this.handleClick}>Add Point</button>
</div>
</div>
))
return (
<div>
<br />
<h2 className="ui header">
<i className="pencil alternate icon"></i>
<div className="content">
Blog Feed
<div className="sub header">Create New Post!</div>
</div>
</h2>
{postItems}
</div>
)
}
}
You have a single component storing the "points" state for all your posts. To achieve the functionality you described, each post should be it's own component with it's own state.
class Post extends Component {
state = {
points: 0
}
handleClick = () => {
this.setState({points: this.state.points + 1})
}
render = () =>
<div key={index} className="ui three stackable cards">
<div className="ui card">
<div className="content">
<div className="header">{this.props.title}</div>
<div className="meta"> {this.props.author}</div>
<div className="description">
<p>{this.props.body}</p>
</div>
</div>
<div className="extra content">
<i className="check icon"></i>
{this.state.points} Votes
</div>
<button className="ui button"
type="submit"
onClick={this.handleClick}>Add Point</button>
</div>
</div>
}
}
You are upvoting every card because you have only one counter. A separate counter should be defined for every card.
state = {}; // dictionary-a-like state structure
handleClick = (id) => () => {
this.setState((prevState) => ({
[id]: prevState[id] ? prevState[id] + 1 : 1, // check and increment counter
}));
}
onClick={this.handleClick(post.id)} // call function with post.id as argument
{this.state[post.id] || 0} Votes // display votes for every card
Note: I assumed that every card has it's own unique id, if not - index may come handy too.
You will need one counter for each post. Currently you only have a single counter for all posts, which means that they all display that same value.
The best way to achieve this would probably be to separate your post into its own component, and have that keep track of the counter.
The following solution uses a post ID (if you have it) to create a key in a stateful points object. Then, on click, you can add to the correct points key.
state = {
points: {}
}
handleClick = postId => {
this.setState({
points: {
...this.state.points,
[postId]: (this.state.points[postId] || 0) + 1
}
})
}
const postItems = this.props.posts.map((post, index) => (
...
<div className="extra content">
<i className="check icon"></i>
{this.state.points[post.id] || 0} Votes
</div>
<button
className="ui button"
type="submit"
onClick={() => this.handleClick(post.id)}
>
Add Point
</button>
...
)

Update component with data from parent in React

In my react app I have this child component that inherits data from its parent. However, it does not update the page with new data from the child component when a relevant anchor link is clicked.
Here's my build - https://suite-search-lk.surge.sh/result/369523
From the link above if you click on a suggested card h1 element it just updates the URL with the id but does not update the page with the relevant card data from that id.
Any idea how I can fix this? Do I have to force the component to re-update?
Parent component (Card Wrapper)
class CardWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
stories: []
};
}
componentDidMount() {
axios
.get(API)
// .then(response => console.log(response))
// get our stories array, check it and then change state to contain our stories
.then(data => {
let stories;
if (data.data.stories && data.data.stories) {
if (Array.isArray(data.data.stories)) {
stories = data.data.stories;
} else {
stories = [data.data.stories];
}
} else {
stories = [];
}
this.setState({
stories: stories
});
});
}
render() {
return (
<CardWrapperDiv>
<div className="headingWrapper">
<div className="heading"> Suggested for you</div>
</div>
<Cards>
{this.state.stories.map(story => {
return (
<Card
title={story.content.title}
img={story.content.img}
description={story.content.description}
deadline={story.content.deadline_date}
tags={story.content.tags}
key={story.id}
id={story.id}
/>
);
})}
</Cards>
</CardWrapperDiv>
);
}
}
export default CardWrapper;
Child component
class Card extends React.Component {
render() {
return (
<CardDiv>
<div className="cardbox">
<div className="cardDetails">
<div className="headlineText">
<Link to={`/result/${this.props.id}`}> {this.props.title} </Link>
</div>
<div className="headlineSub">Colombo, Sri Lanka</div>
<div className="headlineDes">{this.props.description}</div>
<div className="textRemain">
{" "}
Deadline date: {this.props.deadline}
</div>
<div className="buttonRow">
<button className="downloadBtn">Download</button>
<button className="viewBtn">View</button>
</div>
</div>
<div className="cardimgwrapper">
<div className="cardimg">
<img src={this.props.img} alt="some title" />
</div>
</div>
</div>
</CardDiv>
);
}
}
export default Card;
Sorry it seems I have figured this out using the following post - Can you force a React component to rerender without calling setState?
Although I'm not exactly sure if it's the best way to go about it.
Essentially I used an OnClick listener to run a function and forces a re-render of the entire component.
Hope this can help someone else :)
class Card extends React.Component {
handleButtonClick() {
this.forceUpdate();
}
render() {
return (
<CardDiv>
<div className="cardbox">
<div className="cardDetails">
<div className="headlineText">
<Link to={`/result/${this.props.id}`} onClick={this.handleButtonClick}> {this.props.title} </Link>
</div>
<div className="headlineSub">Colombo, Sri Lanka</div>
<div className="headlineDes">{this.props.description}</div>
<div className="textRemain">
{" "}
Deadline date: {this.props.deadline}
</div>
<div className="buttonRow">
<button className="downloadBtn">Download</button>
<button className="viewBtn">View</button>
</div>
</div>
<div className="cardimgwrapper">
<div className="cardimg">
<img src={this.props.img} alt="some title" />
</div>
</div>
</div>
</CardDiv>
);
}
}
export default Card;
U have to use ur child component as a pure component. PureComponent Update when ur props change.
class Card extends React.PureComponent {
handleButtonClick() {
this.forceUpdate();
}
render() {
return (
<CardDiv>
.....
.....
</CardDiv>
);
}
}
export default Card;

Maximum update depth exceeded- React component error

I am trying to implement lazy loading in my react component. But whenever I update my state in reducer after calling the (loadMore) function it gives me this error.
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Here is my article rendering file where i am implementing lazy loading. I am not setState anywhere in the render. I am not able to understand where the error is.
This component is called in article container.
Below is the table.js component code-
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
loadMore = () => {
if (this.props.articleReducer.article_data.length > 0) {
this.props.dispatch(loadArticleDataApi(2, 'lazyLoad'));
}
}
render() {
return (
<div>
<InfiniteScroll pageStart={1} loadMore={this.loadMore} initialLoad={true}
hasMore={true} loader={< div className = "loader" key = {0}> Loading ...</div>}>
<div className="table">
<div className="container ">
<div className="show-grid row">
<div className="col-xs-4 head1">Title</div>
<div className="col-xs-2 head2">Pub All</div>
<div className="col-xs-2 head3">Publisher</div>
<div className="col-xs-2 head4">Rss</div>
<div className="col-xs-1 head5">AMP</div>
<div className="col-xs-1 head7">Publish</div>
</div>
{this.props.articleReducer.article_data.map((ele, index) => {
let hreff = `https://so.city/amp/delhi/${ele._id}.html`;
return (
<div className="show-grid row rowData" key={index}>
<div className="col-xs-4">{ele.title}</div>
<div className="col-xs-2">
<SwitchButtonPubAll checkedProp={ele.allFeedPublished}/>
</div>
<div className="col-xs-2 publisher">{ele.createdBy}</div>
<div className="col-xs-2">
<SwitchButton checkedProp={ele.rssCreated}/>
</div>
<div className="col-xs-1 amp">
<i className="fa fa-refresh" aria-hidden="true"></i>
<a href={hreff}>
<i className="fa fa-link" aria-hidden="true"></i>
</a>
</div>
<div className="col-xs-1">
<i
className={ele.published === 1
? "fa fa-eye eyee"
: "fa fa-eye-slash"}
aria-hidden="true"></i>
</div>
</div>
)
})}
</div>
</div>
</InfiniteScroll>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {dispatch};
}
export default connect(state => state, mapDispatchToProps)(Table);
I suspect that you are using React Infinite Scroller and this answer is based on that.
The problem is that you are setting hasMore={true}. When you scroll to the end of page and hasMore is true, the component request more data by call loadMore, the new data is the same as the previous one (so the page is not scrolled up) but hasMore is still true (it must be false to tells the component that there is no new data) so it calls loadMore again, and again,... and crash.
Solution: Provide a mechanic to check if there is new data available and pass it to hasMore

How do I properly pass function props on another component with React.Component?

I am introducing my self in es6+, I have a hard time trying to pass a function props to another component.
This is my code:
class ProductList extends React.Component {
constructor(props) {
super(props);
this.onVote = this.handleProductUpVote.bind(this);
}
handleProductUpVote(productId) {
console.log(productId +" was upvoted.")
}
render() {
const products = Data.map((product) => {
return (
<Product
key={'product-'+product.id}
id={product.id}
title={product.title}
description={product.description}
url={product.url}
votes={product.votes}
submitter_avatar_url={product.submitter_avatar_url}
product_image_url={product.product_image_url}
onVote={this.handleProductUpVote}
/>
);
});
return (
<div className="ui items">
{products}
</div>
);
}
}
I want to pass the function onVote in this component(Product)
class Product extends React.Component {
handleUpVote() {
this.props.onVote(this.props.id).bind(this) /* the error is here, I am trying
to pass the id props, and invoke the onVote prop here */
}
render() {
return (
<div className="item">
<div className="image">
<img src={this.props.product_image_url} />
</div>
<div className="middle aligned content">
<div className="description">
<a onClick={this.handleUpVote}>
<i className="large caret up icon"/>
</a>
{this.props.votes}
</div>
<div className="description">
<a href={this.props.url}>
{this.props.title}
</a>
</div>
<div className="extra">
<span> Submitted by: </span>
<img
className="ui avatar image"
src={this.props.submitter_avatar_url}
/>
</div>
</div>
</div>
);
}
}
I have no problem with other props here. I am trying to invoke the function on handleUpVote, I used bind with it, but I can't make it work. Help?
You have to use bounded handleProductUpVote method when you pass it to Product component.
As you can see in constructor, you already bound it and assigned to this.onVote property.
There are 2 solutions:
You should use onVote={this.onVote} in render method.
Change the name of property onVote in constructor to this.handleProductUpVote. And you end up with this.handleProductUpVote = this.handleProductUpVote.bind(this) and leave assignment in render method (i.e. onVote={this.handleProductUpVote})
More info at http://reactkungfu.com/2015/07/why-and-how-to-bind-methods-in-your-react-component-classes/
Update:
And update your Product class:
class Product extends React.Component {
constructor(props) {
super(props);
this.handleUpVote = this.handleUpVote.bind(this);
}
handleUpVote() {
this.props.onVote(this.props.id)
}
// the render method
}
Remove the bind in handleUpVote() in your Product component and just invoke it like this.props.onVote(this.props.id);

React onClick handler in map function

I've been searching the web for answers to my question, but without success. I am trying to add a simple react click handler to my button, but I can't seem to make it work. It is probably something really simple, I just can't wrap my head around it.
Here is my code:
export default class ReviewBox extends Component {
deleteReview() {
console.log("hey");
}
render() {
const {reviews, date, lectureId} = this.props;
const that = this;
return (
<div className="container content-sm">
<div className="headline"><h2>Reviews</h2> <span>{date}</span></div>
<div className="row margin-bottom-20">
{reviews.map(review => {
return(
<div className="col-md-3 col-sm-6">
<div className="thumbnails thumbnail-style thumbnail-kenburn">
<div className="caption">
<h3>{review.comment}</h3> <br />
<button className="btn btn-danger" onClick={this.deleteReview()}>Delete</button>
</div>
</div>
</div>
)
})}
</div>
<hr />
<AddReview lectureId={lectureId} />
</div>
)
}
}
It refuses to fire the function when I click a button. I've tried with .bind(this) and onClick={() => this.deleteReview} etc.
All help appreciated!
I think you are missing braces () in arrow function
<button className="btn btn-danger" onClick={() => this.deleteReview()}>Delete</button>
i think this will help you.....
export default class ReviewBox extends Component {
deleteReview() {
console.log("hey");
},
render() {
const {reviews, date, lectureId} = this.props;
const that = this;
return (
<div className="container content-sm">
<div className="headline"><h2>Reviews</h2> <span>{date}</span></div>
<div className="row margin-bottom-20">
{reviews.map(review => {
return(
<div className="col-md-3 col-sm-6">
<div className="thumbnails thumbnail-style thumbnail-kenburn">
<div className="caption">
<h3>{review.comment}</h3> <br />
<button className="btn btn-danger" onClick={this.deleteReview}>Delete</button>
</div>
</div>
</div>
)
})}
</div>
<hr />
<AddReview lectureId={lectureId} />
</div>
)
}
}
Removing () from the onClick function call and using { this.deleteReview } will indeed fire up the method, but if you need to bind this as well inside that method, go with #duwalanise answer.
Ah, now I understand.
It is because I am rendering the react on serverside, that's why the click handler doesn't work.
I will have to render the JS on the client, in order for it to work :)

Categories