My react component is rendering 1 too many child components - reactjs

I'm new to react and struggling with working on why this is happening
I have a component, which takes an array, maps it and then for each element it renders a new component
class CallingPattern extends Component {
constructor(props) {
super(props);
const { serviceId } = this.props.match.params
this.state = {
serviceId: serviceId,
}
}
render() {
return (
<div>
<h1>CallingPattern page</h1>
<h4>Current Journey ServiceID: {this.state.serviceId}</h4>
<CallingStation serviceId={this.state.serviceId}/>
<div className="calling-stations">
// journeyData is a local JSON file
{journeyData.service.stops.map((stop, i) => {
{console.log("mapping")} // logs 8 times (length of array)
return (
<CallingStation
key={`station-${i}`}
stopInfo={stop}
serviceId={this.state.serviceId}
/>
);
})}
</div>
</div>
);
}
}
export default CallingPattern;
I'm expecting 8 CallingStations to be rendered (one for each in the array, which has a .length of 8). Here's CallingStation code:
class CallingStation extends Component {
render() {
console.log(`Rendered!`)
return (
<div>
<div>
Calling Station stop: { this.props.stopInfo ? this.props.stopInfo.location.crs : "" }
</div>
</div>
)
}
}
export default CallingStation;
On the page, there are 9 CallingStations (the first one doesn't have a 'this.props.stopInfo' but DOES have 'this.props.serviceId'.
Can anyone help me understand or point me in the direction of resources that are related?

The problem is here. There is an extra CallingStation in your render method :)
<h4>Current Journey ServiceID: {this.state.serviceId}</h4>
<CallingStation serviceId={this.state.serviceId}/>

In the JSON data the journeyData.service.stops.length will be 17. You can log it to the console using console.log(journeyData.service.stops.length) in your render method and remove this line <CallingStation serviceId={this.state.serviceId}/> it doesn't make sense before mapping. To work with large JSON data you better use a good json viewer i suggest this chrome extension it's fantastic JSON viewer Awesome.
render() {
console.log(journeyData.service.stops.length)
return (
<div>
<h1>CallingPattern page</h1>
<h4>Current Journey ServiceID: {this.state.serviceId}</h4>
<div className="calling-stations">
// journeyData is a local JSON file
{journeyData.service.stops.map((stop, i) => {
{console.log("mapping")} // logs 8 times (length of array)
return (
<CallingStation
key={`station-${i}`}
stopInfo={stop}
serviceId={this.state.serviceId}
/>
);
})}
</div>
</div>
);
}

Related

ReactJS: How to map items in conditional statements

I have a react component that allows a user to click a button in the header and add different input types.
export default class ElementContainer extends Component {
render() {
return (
<div className="append-header">
<Headline buttonCheck={this.props.buttonCheck} />
<SubHeadline buttonCheck={this.props.buttonCheck} />
<URLButton={this.props.buttonCheck} />
</div>
)
}
}
I'm trying to implement react-beautiful-dnd into the project so that those inputs can be repositioned like a list in Trello.
Looking at that library (and other similar libraries), they use the data as lists in order to perform the drag and drop function.
The items in my app are added to the view by conditional rendering:
export default class InputShow extends Component {
render() {
const { node } = this.props;
return (
<div className="editor-div" >
{
(node.type === 'buttonA') ?
<textarea
//omitted code
id={node.id}
onChange={this.props.inputContentHandler} />
:
(node.type === 'buttonB')
?
<URLButton
url={this.url}
id={node.id}
title={this.title}
/>
:
""
}
</div >
)
}
}
I've tried to map the items by creating a state for items (additions)
export default class InputShow extends Component {
constructor(props) {
super(props)
this.state = {
additions: []
}
}
render() {
const { node } = this.props;
return (
<div className="editor-div" >
{this.state.additions.map(addition => (
<div key={addition.id}>
{
(node.type === 'buttonA') ?
<textarea
//omitted code
id={node.id}
onChange={this.props.inputContentHandler} />
:
(node.type === 'buttonB')
?
<URLButton
url={this.url}
id={node.id}
title={this.title}
/>
:
""
}
</div>
))}
</div >
)
}
}
I didn't get any errors, however now when I click on the buttons, no data is being displayed in the view. I've done simple maps in the past with API and local data but I've never tried it with ternary statements. Thanks for any feedback on a solution to this problem.
What you can do is separate the view logic from the code and create a functional component. Pass the values from the main as below:
{this.state.additions.map(addition => (
return <CustomTemplate id={addition.id}
nodeId={node.id} changeEvent={this.props.inputContentHandler}
nodeType={node.Type} url={this.url} title={this.title}/>))}
Create CustomTemplate something like this
const CustomeTemplate =(props)=>(
use props to get the values in the templates
)

Is there any obvious reason this won't render?

I'm pulling in an array of objects and mapping them to another component to be rendered.
renderRatings(){
if(this.props.ratings.length > 0){
return this.props.ratings.map(rating => {
<Rating
id={rating.id}
title={rating.title}
value={rating.value}
/>
});
}
}
This is where I render the rendering function.
render() {
return (
<div>
{this.renderRatings()}
</div>
);
}
}
This is the component I'm trying to populate and have rendered.
class Rating extends Component{
componentDidMount(){
console.log("props equal:", this.props)
}
render() {
return (
<div className="card darken-1" key={this.props._id}>
<div className="card-content">
<span className="card-title">{this.props.title}</span>
<p>{this.props.value}</p>
<button>Edit</button>
<button onClick={() => this.deleteRating(this.props._id)}>Delete</button>
</div>
</div>
);
}
}
export default connect({ deleteRating })(Rating);
No errors are being thrown, but when the page loads, the surrounding menu comes up, and the fetch request returns an array and supposedly maps it to the 'Rating' component, but no mapped Rating cards appear.
in your map, you're not returning the Rating etc... because you used { to define a code block, you have to type return. And since it's multi-line, use parens to mark the start and end of the Rating component.
return this.props.ratings.map(rating => {
<Rating
id={rating.id}
title={rating.title}
value={rating.value}
/>
needs to be
return this.props.ratings.map(rating => {
return (<Rating
id={rating.id}
title={rating.title}
value={rating.value}
/>)

React content rendering based on map output not working

I have a component which is going to rely on the results of an array.map to determine it's output. In this case the array contains an element called "name", and one of the names is 'Welcome.' What needs to happen is the component spits out a particular div for the 'Welcome' instance and different content (an Accordion component) for every other instance. I've used a ternary operator in the render which I'm then calling in the return, but it's outputting the same text for every instance of the component (the text specified for only the 'Welcome' instance.) I can't for the life of me figure out what I'm doing wrong. Here's the code:
export default class Back extends React.Component {
constructor(props) {
super(props)
}
render() {
const CheckDomains = this.props.totaldomains.map((domain, index) => {
<div>
{
domain.name === 'Welcome' ?
<div>This is welcome text.</div>
:
<div>This is accordion text.</div>
}
</div>
)
});
return(
<div className='back'>
{CheckDomains}
</div>
);
}
}
You need to return something in your map callback function.
Like so:
const CheckDomains = this.props.totaldomains.map((domain, index) => {
return (
<div>
{
domain.name === 'Welcome' ?
<div>This is welcome text.</div>
:
<div>This is accordion text.</div>
}
</div>
)
});

User react router inside of map

I would like to use react-router (must use 2.8.1) for rendering content inside a list (using map).
However, if I display {this.props.children} outside the .map, it renders one at time.
I need it to display inline/under the list entry.
How can I achieve this?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: x.movies
};
}
render() {
return (
<div>
<h2>Repos</h2>
{x.movies.map(movie =>
<span key={movie.id}>
{movie.name}
<NavLink to={"/repos/" + movie.id + "/next"}>React Router</NavLink>
</span>
)}
{this.props.children}
</div>
);
}
}
You are rendering the children inside the loop, which i believe is causing the extra Next Id:4 to be displayed between each entry.
Try the below, by rendering the children outside the loop.
{
x.movies.map(movie => (
<span>
<Result key={movie.id} result= {movie}/>
</span>
))
}
<span>{this.props.children}</span>

Force stop container subscribtion (Meteor+React+CreateContainer)

How can I manually stop the subscription and clean the collection on the client when you switch route via react-router?
The fact is that if I go to the page where there is a component subscribed to, such as "top 10 news" page, where published all the news, I see, as a container of news component performing a normal search the collection first finds objects with subscription last page.
export default createContainer((props)=>{
let {limitCount, userId} = props;
let query = (userId)?{'userId':userId}:{};
var handle = Meteor.subscribe('news_preview', query,limitCount);
var posts = Post.find(query, {sort:{createdAt:-1}}).fetch(); //Here, the container finds documents to which another component has been signed.
var needWait = !!handle.ready()&&!!posts;
return {
needWait:!needWait,
posts:posts
}
}, PostList)
After a moment, the container will complete its membership and will give us the relevant objects ...
How can I check that when linking the previous container has stopped its subscription and deleted objects?
In details. With the logic description
PostListContainer
This is a sample of NewsList component, with infinity scroll and subscribtion container. Is just detect scroll, and pass limitCount to child component.
export default class PostListContainer extends Component{
constructor(props){
super(props)
this.state={
limitCount:10,
}
this.handleScroll = this.handleScroll.bind(this);
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll, false);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll, false);
}
handleScroll(event) {
let documentHeight = $(document).height(); //jquery is bad idea, i know
let windowHeight = $(window).height();
let scrollPosition = $(window).scrollTop();
let isLoading = this.refs.PostList.data.needWait;
if(scrollPosition+windowHeight>=documentHeight && !isLoading){
this.uppendSkipCount()
}
}
uppendSkipCount(){
let limitCount = this.state.limitCount;
this.setState({
limitCount:limitCount+5
});
}
render(){
let userId = (this.props.userId)?this.props.userId:undefined;
return(
<div>
<PostList ref="PostList" limitCount={this.state.limitCount} userId={userId} />
</div>
)
}
}
PostList
This component get properties, subscribe and render child components.
export class PostList extends Component {
constructor(props){
super(props);
}
postList(){
return(
<Masonry>
{this.props.posts.map((post)=>(
<div className="col-xs-12 col-sm-6 col-md-6 col-lg-4" key={post._id}>
<PostPreview post={post}/>
</div>
))}
</Masonry>
)
}
render() {
let content = (this.props.posts)?this.postList():undefined;
let loading = (this.props.needWait)?<Loading />:undefined;
return(
<div>
{content}
{loading}
</div>
)
}
}
export default createContainer((props)=>{
let {limitCount, userId} = props;
let query = (userId)?{'userId':userId}:{};
var handle = Meteor.subscribe('news_preview', query,limitCount);
var posts = Post.find(query, {sort:{createdAt:-1}}).fetch();
var needWait = !!handle.ready()&&!!posts;
return {
needWait:!needWait,
posts:posts
}
}, PostList)
And it work goood. But if i will come on the page from page that contain for example BestNews component i will get Posts object from that in first iteration:(
export class BestNews extends Component {
constructor(props){
super(props);
}
componentWillUnmount(){
this.props.handle.stop(); / looks like it help
}
goToPage(post){
browserHistory.push(`/news/item/${post._id}`);
}
bestPosts(){
return bestPosts = this.props.posts.map((post)=>{
return(
<div key={post._id}>
<ListItem
onTouchTap={()=>this.goToPage(post)}
leftAvatar={<Avatar src={post.author().getAvatar().link()} />}
rightIcon ={<ActionChromeReaderMode />}
primaryText={post.title}
secondaryText={post.description}
/>
<Divider />
</div>
)
});
}
render() {
let content = (this.props.needWait)?<Loading />:this.bestPosts();
return (
<div className="box-margin">
<Paper zDepth={1}>
<div className="row middle-xs center-xs">
<div className="col-xs-12">
<h2 style={{fontWeight:'300'}}>Интересные новости</h2>
</div>
</div>
<div className="row start-xs">
<div className="col-xs-12">
<List>
<Divider />
{content}
</List>
</div>
</div>
</Paper>
</div>
)
}
}
export default createContainer((props)=>{
var handle = Meteor.subscribe('best_news');
var posts = Post.find({}, {sort:{likes:-1}, limit:5}).fetch();
var needWait = !handle.ready() && !posts;
return {
handle:handle,
needWait:needWait,
posts:posts
}
}, BestNews)
I found a solution.
It was obvious. But somehow it seems to me that there is a better one.
In this case, we need to use a child component of the container. With method componentWillUnmount() we can manually stop the subscription.
What is negative? The fact that we need to transfer the subscription object in a child component, and then manually stop the subscription. And to do it in each component, which can cause a similar problem.
The answer to the question - why can not we just wait for the completion of the subscription of the container, and then - to display the content. The answer is - it is possible, but as long as you have not implemented infinite scrolling logic. Otherwise, each time the user scrolls down the content will be lost. As long as the subscription is completed.
It's funny, but I always thought that the container so stops the subscription. I was wrong?
Solution with manualy stop subscribtion is not good. You understand why.
Take a look at the pretty good solution.
We need to protect themselves from the fact that when you visit a component we can get irrelevant data with other components. Yes type have the same data, but - with the other components. It can play a cruel joke with s. I think you understand why.
At the same time we can not put a cap "loading" every time a user scrolls down and loaded data. Because at the time of subscription, all data will disappear. Option with named subscriptions you too are not satisfied. For example, if you are using Astronomy ORM by Jagi.
So. We need simply to intercept getting values ​​from the container and once finished to record the fact of the first subscription in the state of the component.
export class PostList extends Component {
constructor(props){
super(props);
this.state={
firstSubscribtion:false
}
}
componentWillReceiveProps(nextProps){
if(!this.state.firstSubscribtion && nextProps.handleReady){
this.setState({
firstSubscribtion:true
});
}
}
postList(){
return(
<Masonry>
{this.props.posts.map((post)=>(
<div className="col-xs-12 col-sm-6 col-md-6 col-lg-4" key={post._id}>
<PostPreview post={post}/>
</div>
))}
</Masonry>
)
}
render() {
let content = (this.props.posts && this.state.firstSubscribtion)?this.postList():undefined;
let loading = (this.props.needWait)?<Loading />:undefined;
return(
<div>
{content}
{loading}
</div>
)
}
}
export default createContainer((props)=>{
let {limitCount, userId} = props;
let query = (userId)?{'userId':userId}:{};
var handle = Meteor.subscribe('news_preview', query,limitCount);
var posts = Post.find(query, {sort:{createdAt:-1}}).fetch();
var needWait = !!handle.ready()&&!!posts;
return {
handleReady:!!handle.ready(),
needWait:!needWait,
posts:posts
}
}, PostList)

Resources