Render a map of div and their title - javascript

I would like to simplify "renderTitle" and "renderComments" in a unique function in a React component:
renderTitle(dish) {
return (
<h2>
Title array comment
</h2>
);
}
renderComments(dish) {
return (
dish.array.map(comment => {
return (
<div>
hello
</div>
);
})
);
}
render() {
return (
{this.renderTitle(this.props.dish)}
{this.renderComments(this.props.dish)}
);
}

Take a look at below code where I used Fragment (react 16.x). And see how I merged the functions in your question.
renderItems(dish) {
const comments = dish.array.map(comment => (<div>hello</div>))
return (
<React.Fragment>
<h2>
Title array comment
</h2>
{comments}
</React.Fragment>
);}

To use regular JavaScript expressions in the middle of JSX you have to wrap the JavaScript expression in {}.
Note that to return multiple elements on the same level you should either return an array or wrap them in a Fragment:
render() {
return (
<React.Fragment>
<h2>
Title array comment
</h2>
{
dish.array.map(comment => (
<div>
hello
</div>
));
}
</React.Fragment>
);
}

You can do this if this.props.dish is array.
renderTitle(dish) {
return dish.map(i => {
return <div>{i.title}</div>;/*this title may change*/
});
}
renderComments(dish) {
return dish.map(i => {
return <div>{i.comment}</div>;/*this comment may change*/
});
}
render() {
return (
<div>
{this.renderTitle(this.props.dish)}
{this.renderComments(this.props.dish)}
</div>
);
}

Related

How to map twice using Gatsby?

I want to create a table similar to how it's shown here ("Paslaugos" section), and allow the client to change table content using WordPress.
So I looped through, and displayed images and titles, without issue. However, I can't figure it out how to display table items. How do you map items twice in this context?
Update
{node.acf.table_items.header.map(({ c }) => (
<span>{c}</span>
))}
{node.acf.table_items.body[0].map(({ c }) => (
<span>{c}</span>
))}
So I kinda figure it out. This way would display header, but display only first item in the table. I need to loop body[0] in order to work, however I can't figure it out the exact syntax.
So thanks to ZeroToMastery forum the correct answer would be this:
{node.acf.table_items.body.map(mappingBody => {
return mappingBody.map(({ c, index }) => {
return (
<span key={index} className={classes.body}>
{c}
</span>
)
})
})}
Current Result
<StaticQuery
query={graphql`
{
allWordpressPost(
filter: {
categories: { elemMatch: { name: { eq: "favours" } } }
}
) {
edges {
node {
id
acf {
favours_title
table_items {
body {
c
}
header {
c
}
}
favours_images {
localFile {
childImageSharp {
fluid(maxWidth: 600) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
}
}
}
`}
render={data =>
data.allWordpressPost.edges.map(({ node }) => (
<div key={node.id} className={classes.item}>
<Img
className={classes.img}
fluid={
node.acf.favours_images.localFile.childImageSharp.fluid
}
/>
<h2 className={classes.title}>{node.acf.favours_title}</h2>
<div className={classes.grid}>
{node.acf.table_items.header.map(({ header }) => (
<span>{header}</span>
))}
{node.acf.table_items.body.map(({ body }) => (
<span>{body}</span>
))}
</div>
</div>
))
}
/>
I'm presuming that the data in body is a 2D nested array, e.g.
node.acf.table_items.body = [['row1-columnA', 'row1-columnB'], ['row2-columnA', 'row2-columnB']];
To map through each item in a 2D array, you could use map() twice:
{node.acf.table_items.body.map(c => c.map(el => (
<span>{el}</span>
)))}
Or, if you know there are a set number of items in each sub-array, you could use target the specific elements using square bracket notation:
{node.acf.table_items.body.map(c => (
<span>{c[0]}</span>
<span>{c[1]}</span>
))}
So thanks to Zero To Mastery forum, the correct answer would be this:
{node.acf.table_items.body.map(mappingBody => {
return mappingBody.map(({ c, index }) => {
return (
<span key={index} className={classes.body}>
{c}
</span>
)
})
})}

React: only map when props exists

I'm trying to do conditional rendering in React (only maps and renders when props exist).
render() {
if (this.props.res) {
return(
<div>{this.props.res.map(item => <li key={item.id}>{item.subreddit}</li>}</div>
)
} else {
return null
}
}
But I have this error:
Parsing error: Unexpected token, expected ","
Why is that, and how can I fix it? Or in another way, is there a better way to achieve my purpose?
There is syntax error, map(...) doesn't have closing parenthesis.
It should be:
<div>{this.props.res.map(item => <li key={item.id}>{item.subreddit}</li>)}</div>
Here are the issues I see:
map is missing the closing parenthesis
check the array length (an empty array will return true otherwise)
list items should be wrapped with an appropriate tag, such as <ul/>
render() {
const { res } = this.props;
return !res.length ? null : (
<ul>{res.map(item => <li key={item.id}>{item.subreddit}</li>)}</ul>
);
}
there was a brace missing in the end of the map.
render() {
return (
<div>
{
this.props.res && this.props.res.map(item => (
<li key={item.id}>{item.subreddit}</li>
))
}
</div>
);
}
render() {
if (this.props.res) {
return (
<div>
{this.props.res.map(item => (
<li key={item.id}>{item.subreddit}</li>
))}
</div>
);
} else {
return null;
}
}

React won't render within map function

I'm trying to render an array containing some objects using the JS function map().
However when I return the text nothing is shown:
console.log(this.props.project.projects); // (2) [{…}, {…}]
this.props.project.projects.map((item, index) => {
console.log(item.projectDescription); //"Testproject"
return (
<div key={index}>
{item.projectDescription}
</div>
)
})
I just don't get it, why there is no text shown, since the console.log(item.projectDescription) shows exactly what I want to display.
Update:
It works when I change it to this:
return this.props.project.projects.map((item, index) => (
<div key={index} style={{ color: '#fff' }}>
{item.projektBeschreibung}
</div>
))
I already thought about using the foreach-method but I think it should actually work using the map()-function.
Here you can see also the render method of my Component.
class ProjectRow extends Component {
renderProjects() {
console.log(this.props.project);
if (this.props.project.loading) {
return (
<div style={{color: '#fff'}}>
Loading
</div>
)
} else {
console.log(this.props.project.projects);
this.props.project.projects.map((item, index) => {
console.log(item);
console.log(item.projektBeschreibung);
console.log(index);
return (
<div key={index}>
{item.projektBeschreibung}
</div>
)
})
}
}
render() {
return (
<div>
{this.renderProjects()}
</div>
);
}
}
The renderProjects function is not returning anything when it hits your else case. Here is an example of use:
renderProjects() {
console.log(this.props.project);
if (this.props.project.loading) {
return (
<div style={{color: '#fff'}}>
Loading
</div>
)
} else {
console.log(this.props.project.projects);
// added return statement here
return this.props.project.projects.map((item, index) => {
console.log(item);
console.log(item.projektBeschreibung);
console.log(index);
return (
<div key={index}>
{item.projektBeschreibung}
</div>
)
})
}
}
why not use map like below ?
render(){
return(
<div>
{this.props.project.projects.map((item, index) => (
<div key={index}>
{item.projectDescription}
</div>
))}
</div>
)
}

You may have returned undefined, an array or some other invalid object rendering state data

Have been facing issue while iterating through a list and printing elements in React.
The React Code is:
import React from 'react';
import ReactDOM from 'react-dom';
class NewComponent extends React.Component {
constructor(props){
super(props);
this.state = {myData: []}
}
componentWillMount(){
let data = document.getElementById('demo').innerHTML;
data = JSON.parse(data);
this.setState({myData: data});
}
render() {
return this.state.myData.map((item) => {
return (
<div>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
);
});
}
}
ReactDOM.render(
<NewComponent />,
document.getElementById('demo')
)
And I'm getting an error of:
bundle.js:830 Uncaught Error: NewComponent.render(): A valid React element
(or null) must be returned. You may have returned undefined, an array or
some other invalid object.
I'm pretty sure there is some issue with returns in the render function of the
Not sure what is the issue though.
EDITS
I have made the following edits, the error is not there anymore but nothing is rendering.
renderList() {
console.log("Running");
return this.state.myData.map((item) => {
<div>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
});
}
render() {
console.log(this.state.myData);
if(this.state.myData.length)
return <div>{this.renderList()}</div>
else
return <div>Loading...</div>
}
In Chrome console I'm getting:
(2) [{…}, {…}]
0:{_id: {…}, description: "hello", title: "sankit"}
1:{_id: {…}, description: "lets add some thing new", title: "hi"}
length:2
_proto_:Array(0)
Running
what you can do is extract your js code from the render method in a separate method like so:
renderList() {
return this.state.myData.map((item) => {
<div>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
})
}
then in your render method:
render() {
if(this.state.myData.length){
return (
<div>{this.renderList()}</div>
);
}
else
{
return (
<div>Loading...</div>
);
}
}
You can wrap it with root element like div,
React ver 15 render functions supports only returning one element.
render() {
<div>{this.state.myData.map((item) =>
<div>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
)}</div>
}
}
Change like this, While you using map should use key attribute for index
makeUI() {
if(!this.state.myData.length)
return
return this.state.myData.map((item, index) => {
return (
<div key={index}>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
)
})
}
render() {
return (<div>
{ this.makeUI() }
</div>
)
}
I think you are missing the return in renderList -> .map
This should work.
renderList() {
return this.state.myData.map((item) => {
return (
<div>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
);
});
}
render() {
if(this.state.myData.length){
return (
<div>{this.renderList()}</div>
);
}
else {
return (
<div>Loading...</div>
);
}
}

Return multiple React elements in a method without a wrapper element

I'm trying to return multiple React elements from a helper method. I could solve it simply by moving around some code, but I'm wondering if there's a cleaner way to solve it. I have a method that returns part of the render method, and that functions needs to return both a React element and some text. It's clearer through an example:
class Foo extends React.Component {
_renderAuthor() {
if (!this.props.author) {
return null;
}
return [
' by ',
<a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
]; // Triggers warning: Each child in an array or iterator should have a unique "key" prop.
render() {
return (
<div>
{this.props.title}
{this._renderAuthor()}
</div>
);
}
}
I know the render method has to return exactly 1 React element. Using a helper method like this would trigger a warning, and fixing the warning (by adding keys) would make the code too convoluted. Is there a clean way to do this without triggering a warning?
Edit:
Another use case:
render() {
return (
<div>
{user
? <h2>{user.name}</h2>
<p>{user.info}</p>
: <p>User not found</p>}
</div>
);
}
Edit 2:
Turns out this isn't possible yet, I wrote about 2 workarounds here: https://www.wptutor.io/web/js/react-multiple-elements-without-wrapper
Support has been added using the Fragment component. This is a first-class component.
So you can now use:
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
For more information visit: https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html
The error message tells you exactly how to solve this:
Each child in an array or iterator should have a unique "key" prop.
Instead of this:
return [
' by ',
<a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];
Do this:
return [
<span key="by"> by </span>,
<a key="author" href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];
Yes, you need to wrap the text node ("by") in a span in order to give it a key. Such are the breaks. As you can see, I've just given each element a static key, since there's nothing dynamic about them. You could just as well use key="1" and key="2" if you wanted.
Alternatively, you could do this:
return <span> by <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a></span>;
...which obviates the need for keys.
Here's the former solution in a working snippet:
const getAuthorUrl = author => `/${author.toLowerCase()}`;
class Foo extends React.Component {
_renderAuthor() {
if (!this.props.author) {
return null;
}
return [
<span key="by"> by </span>,
<a key="author" href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];
}
render() {
return (
<div>
{this.props.datePosted}
{this._renderAuthor()}
</div>
);
}
}
ReactDOM.render(<Foo datePosted="Today" author="Me"/>, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
It's not currently possible to do this without some sort of workaround like wrapping everything in another component, since it ends up with the underlying React code trying to return multiple objects.
See this active Github issue where support for this is being considered for a future version though.
Edit: You can now do this with Fragments in React 16, see:
https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html
There is another way to solve this. I will suggest you create another component Author.js:
function Author(props) {
return (<span>
<span> by </span>
<a href={props.getAuthorUrl(props.author)}>{props.author}</a>
</span>)
}
class Foo extends React.Component {
render() {
return (
<div>
{this.props.title}
{this.props.author && <Author author={this.props.author} getAuthorUrl={this.getAuthorUrl} />}
</div>
);
}
}
I didn't test this code though. But it will look more cleaner I think. Hope it helps.
I like to have an If-component around for such things, and I have wrapped everything into a span, as it doesn't really break anything and makes the need for keys go away...
const getAuthorUrl = author => `/${author.toLowerCase()}`;
function If({condition,children}) {
return condition ? React.Children.only(children) : null;
}
class Foo extends React.Component {
render() {
return (
<div>
{this.props.datePosted}
<If condition={this.props.author}>
<span> by
<a key="author" href={getAuthorUrl(this.props.author)}>
{this.props.author}
</a>
</span>
</If>
</div>
);
}
}
ReactDOM.render(<Foo datePosted="Today" author="Me"/>, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
...skipping the array thing altogether?
This is a bit hacky but it doesn't have unnecessary jsx as you wished.
var author = 'Daniel';
var title = 'Hello';
var Hello = React.createClass({
_renderAutho0r: function() {
if (!author) {
return null;
}
return {author}
},
render: function() {
var by = author ? ' by ' : null;
return (
<div>
{title}
{by}
{this._renderAutho0r()}
</div>
);
}
});
React.render(<Hello name="World" />, document.body);
my JSFiddle
You can return fragments from sub-rendering functions but not from the main render function, at least before React 16. In order to do so, return an array of components. You don't need to set keys manually unless your fragment children will change (arrays are keyed with indices by default).
For creating fragments you may also use createFragment.
For inline usage, you may use an array or leverage immediately invoked arrow function.
See the example below:
const getAuthorUrl = author => `/${author.toLowerCase()}`;
class Foo extends React.Component {
constructor() {
super();
this._renderAuthor = this._renderAuthor.bind(this);
this._renderUser = this._renderUser.bind(this);
}
_renderAuthor() {
if (!this.props.author) {
return null;
}
return [
' by ',
<a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];
}
_renderUser() {
return [
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
]
}
render() {
return (
<div>
{this.props.datePosted}
{this._renderAuthor()}
<div>
{this.props.user
? this._renderUser()
: <p>User not found</p>}
</div>
<div>
{this.props.user
? [
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
]
: <p>User not found</p>}
</div>
<div>
{this.props.user
? (() => [
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
])()
: <p>User not found</p>}
</div>
</div>
);
}
}
ReactDOM.render(<Foo datePosted="Today" author="Me" user={{name: 'test', info: 'info'}} />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
In order to not get warnings each child must be assigned a key. In order to do so, instead of returning an array please use helper function fragment(...children) to assign index-based keys automatically. Please note that strings must be converted to spans or other nodes that can be assigned with a key:
const fragment = (...children) =>
children.map((child, index) =>
React.cloneElement(
typeof child === 'string'
? <span>{child}</span>
: child
, { key: index }
)
)
const getAuthorUrl = author => `/${author.toLowerCase()}`;
const fragment = (...children) =>
children.map((child, index) =>
React.cloneElement(
typeof child === 'string'
? <span>{child}</span>
: child
, { key: index }
)
)
class Foo extends React.Component {
constructor() {
super();
this._renderAuthor = this._renderAuthor.bind(this);
this._renderUser = this._renderUser.bind(this);
}
_renderAuthor() {
if (!this.props.author) {
return null;
}
return fragment(
' by ',
<a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>
);
}
_renderUser() {
return fragment(
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
)
}
render() {
return (
<div>
{this.props.datePosted}
{this._renderAuthor()}
<div>
{this.props.user
? this._renderUser()
: <p>User not found</p>}
</div>
<div>
{this.props.user
? fragment(
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
)
: <p>User not found</p>}
</div>
<div>
{this.props.user
? (() => fragment(
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
))()
: <p>User not found</p>}
</div>
</div>
);
}
}
ReactDOM.render(<Foo datePosted="Today" author="Me" user={{name: 'test', info: 'info'}} />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
Try this:
class Foo extends React.Component {
_renderAuthor() {
return <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>
}
render() {
return (
<div>
{this.props.title}
{this.props.author && " by "}
{this.props.author && this._renderAuthor()}
</div>
);
}
}
Perhaps a more simple way would be to rethink how you're architecting your application. However, in a more simple way.
You're triggering the warning because you're trying to render from an array and not react elements but directly html. In order to approach this, you would have to do
{this._renderAuthor().map(
(k,i) => (React.addons.createFragment({k}))
) }
React addons createFragment function basically does that, it reduces your html elements into react fragments that you can render.
React createFragment documentation
Alternatively, in a much better approach, you can create an AuthorLink stateless component like this..
function AuthorLink(props) {
return (
<div className="author-link">
<span> by </span>
<a href={props.authorUrl}> {props.author} </a>
</div>
});
}
and use this in your main component's render
render() {
const { author } = this.props;
return (
<div>
{this.props.datePosted}
<AuthorLink url={getAuthorUrl(author)} author={author} />
</div>
);
}
Try this approach on your array:
return [
<span key={'prefix-'+random_string_generator()}>' by '</span>,
<a key={'prefix-'+random_string_generator()} href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];

Categories