I have a render function like this one:
render() {
const statement = true
return (
<div>
{
statement &&
<div>
<p>
{this.buildStuff()}
</p>
<p>
{this.buildStuff()}
</p>
<p>
{this.buildStuff()}
</p>
</div>
}
</div>
);
}
To avoid calling buildStuff() three times, I would like to assign it to a variable. How can I declare a variable after the line with statement &&?
A quick solution would be to do
const statement = true
const stuff = statement ? buildStuff() : null;
but this solution use two branches instead of one.
You can try this code on StackBlitz.
This what it would look like in Razor.
You can try something like this as well:
You can create a function that deals with this UI representation.
In this function, you can call buildStuff and have it return 3 <p> tags.
Then in main render, you can check your condition and render accordingly. This will make your render clean and declarative.
getBuildJSX() {
const stuff = this.buildStuff();
return Array.from({ length: 3}, () => <p> { stuff }</p>);
}
render() {
const statement = true
return (
<div>
{
statement ? this.getBuilsJSX() : null
}
</div>
);
}
Try it online
First solution (edit: alternative)
render() {
const statement = true;
const stuff = this.buildStuff(statement, 3); // jsx result looped in divs
return (
<div>
{
statement &&
<div>
{ stuff }
</div>
}
</div>
);
}
Alternative, memoization (caching of functions) if this is your goal:
const memoize = require('fast-memoize');
const memoized = memoize(this.buildStuff);
...
render() {
const statement = true;
return (
<div>
{
statement &&
<div>
<p>
{memoized()}
</p>
<p>
{memoized()}
</p>
<p>
{memoized()}
</p>
</div>
}
</div>
);
}
The true power of memoization however is, if you cache based on the parameter you give to buildStuff (maybe you move statement into buildstuff?). In your case I would just clean up the component and parameters in favour of readability rather than optimising something. So last alternative:
// Stuff is a component now
const Stuff = ({statement, stuff}) => {
if(!statement)
return null;
const result = stuff();
return (
<div>
<p>
{result}
</p>
<p>
{result}
</p>
<p>
{result}
</p>
</div>
)
}
render() {
return (
<div>
<Stuff statement={true} stuff={this.buildStuff} />
</div>
);
}
The benefit, you can now choose to pass the result or the function itself through props, and in the downward component either call the function or simply have its results passed through.
Single answer to your question in the headline: you cant, JSX is not a templating engine like Razor.
Explanation:
// JSX
<div id="foo">Hello world</div>
// Translates into
React.createElement("div", {id: "foo"}, "Hello world");
// JSX
<div>{ statement && <div /> }</div>
// Translates to
React.createElement("div", null, statement && React.createElement("div"));
Either you declare a new variable with an attribute, or you simply cant, since javascript does not allow variable creation inside parameters of functions.
I think one of the main ideas of react is to use components to structure your code.
So one way to do it would be like this:
render() {
const statement = true;
const Stuff = ({statement}) => {
if (!statement) { return null; }
return this.buildStuff();
}
return (
<div>
<p>
<Stuff statement={statement} />
</p>
<p>
<Stuff statement={statement} />
</p>
<p>
<Stuff statement={statement} />
</p>
</div>
);
}
Updated StackBlitz.
This answer is an answer to the problem but not a solution to the question. If you cannot declare a variable inside brackets in react (as you could do in Razor for example). Calling twice a statement can still be your best bet.
render() {
const statement = true
const stuff = statement ? this.buildStuff() : null
return (
<div>
{
statement &&
<div>
<p>
{stuff}
</p>
<p>
{stuff}
</p>
<p>
{stuff}
</p>
</div>
}
</div>
);
}
At least, we call this.buildStuff() only if needed and if we do, we call it only once.
Related
I am new to React JS, I was testing out some functions in fiddler. I am not sure why I get an error pointing to the map function. I am not able to render the array i defined.
Relevant snippet:
{this.props.data.productSpecs.map(function(productSpec){
<b>Category Name:</b> {productSpec};
})}
Full code:
var productCategory = {
productName: 'SamamgaTV1',
productCategory: 'Television',
productSpecs: ['32inch','black','hd']
};
var ProductComponent = React.createClass({
render: function() {
return( <div>
<h2>Product</h2>
<b>Product Name:</b> {this.props.data.productName}
<h2>Category</h2>
<b>Category Name:</b> {this.props.data.productCategory}
<h2>Specs</h2>
{this.props.data.productSpecs.map(function(productSpec){
<b>Category Name:</b> {productSpec};
})}
</div>);
}
});
ReactDOM.render(
<ProductComponent data={productCategory} />,
document.getElementById('container')
);
First you missed to return, then you must return ONE element.
Here you return a <p> and a TextNode
Moreover you need to provide a unique key.
Try with this :
{this.props.data.productSpecs.map(function(productSpec, i){
return <span key={i}><b>Category Name:</b> {productSpec}</span>;
})}
You need to return value from map handler.
{this.props.data.productSpecs.map(function(productSpec){
return (<span><b>Category Name:</b> {productSpec}<span>);
})}
If you do not want to (or can't in some situations) wrap content in span, you can create & return fragment (very handy)
const createFragment = require('react-addons-create-fragment');
{this.props.data.productSpecs.map(function(productSpec){
return createFragment({
bold: <b>Category Name:</b>,
spec: productSpec,
});
})}
It might be that you are using a string so you have to use the split method and pass in an empty string such as myValue.split('').map();
I am trying to use classNames to replace the conditional below in one line.
My problem is that i am not sure what is the right way to write the code because of the div etc...
I have the codes below :
conditional
const { pageTitle, removeTitle = false } = props; # destructuring
let result;
if(removeTitle){
result = <div className="header-without-title">{pageTitle}</div>;
} else {
result = <div className="header-with-title">{pageTitle}</div>;
}
return (<div className="result-title">{result});
};
If the div did not exist, i could write something like this...
const result = classNames({"header-without-title": removeTitle, "header-title": !removeTitle});
But i am not sure now that I have the div , I would appreciate some help here...
A solution outside of JSX would be very helpful
return (
<div className="result-title">
<div className={`header-${removeTitle ? 'without-title' : 'with-title'}`}>{pageTitle}</div>
</div>
);
or with use https://github.com/JedWatson/classnames
return (
<div className="result-title">
<div className={classNames({ 'header-without-title': removeTitle, 'header-with-title': !removeTitle })}>
{pageTitle}
</div>
</div>
);
EDIT:
A solution outside of JSX
const result = (
<div className={classNames({ 'header-without-title': removeTitle, 'header-with-title': !removeTitle })}>
{pageTitle}
</div>
);
return (
<div className="result-title">
{result}
</div>
);
You can just inline classNames function
const { pageTitle, removeTitle = false } = props;
const result = classNames({"header-without-title": removeTitle, "header-title": !removeTitle});
return (
<div className="result-title">
<div className={result}>
{pageTitle}
</div>
</div>);
);
There are several answers to this. Depends of each case
Ternary between two classes in React:
<div className={`header-${removeTitle ? 'without-title' : 'with-title'}`}>
Ternary between class or null in React Javascript:
<div className={removeTitle ? 'without-title' : null}>
Ternary between render class or not in React Javascript and Typescript:
<div className={...(removeTitle ? { className: 'without-title' } : {})}>
I have seen similar questions here, but these haven't been helpful so far.
I have a component that has an array state:
eventData: []
Some logic watches for events and pushes the objects to the state array:
eventData.unshift(result.args);
this.setState({ eventData });;
unshift() here is used to push the new elements to the top of the array.
What I want to achieve is rendering the content of the state array. I have written a conditional that checks for a certain state, and based on that state decides what to output.
let allRecords;
if (this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
} else if (!this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
if (this.state.account === this.state.eventData[i].paramOne) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
}
}
Problems that I have with this piece of code:
The code always renders the very last value of eventData state object.
I would like to limit the rendered elements to always show not more than 20 objects (the last 20 records of the state array).
paramTwo is a bool, and according to its value I expect to see either Win or Loose, but the field is empty (I get the bool value via the console.log, so I know the value is there)
Is this even the most effective way of achieving the needed? I was also thinking of mapping through the elements, but decided to stick with a for loop instead.
I would appreciate your help with this.
A few things :
First, as the comments above already pointed out, changing state without using setState goes against the way React works, the simplest solution to fix this would be to do the following :
this.setState(prevState => ({
eventData: [...prevState.eventData, result.args]
}));
The problem with your code here. Is that the arrow function was never called :
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}
}
This function can be reduced to the following (after applying the deconstructing seen in the below code) :
<span>{paramTwo ? 'Win' : 'Lose'}</span>
Next up, removing repetitions in your function by mapping it. By setting conditions at the right place and using ternaries, you can reduce your code to the following and directly include it the the JSX part of your render function :
render(){
return(
<div> //Could also be a fragment or anything
{this.state.allRecords || this.state.account === this.state.eventData[i].paramOne &&
this.state.eventData.map(({ paramOne, paramTwo, paramThree, paramFour, paramFive, paramSix }, i) =>
<div className="event-result-table-container" key={i}> //Don't forget the key like I just did before editing
<div className="result-cell">
{paramOne}
</div>
<div className="result-cell">
<span>{paramTwo ? 'Win' : 'Lose'}</span>
</div>
<div className="result-cell">
{paramThree.c[0]}
</div>
<div className="result-cell">
{paramFour.c[0]}
</div>
<div className="result-cell">
{paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{paramSix.c[0]}
</div>
</div>
)
}
</div>
)
}
Finally, to only get the 20 first elements of your array, use slice :
this.state.eventData.slice(0, 20).map(/* CODE ABOVE */)
EDIT :
Sorry, I made a mistake when understanding the condition you used in your rendering, here is the fixed version of the beginning of the code :
{this.state.allRecords &&
this.state.eventData.filter(data => this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
Here, we are using filter to only use your array elements respecting a given condition.
EDIT 2 :
I just made another mistake, hopefully the last one. This should ahve the correct logic :
this.state.eventData.filter(data => this.state.allRecords || this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
If this.state.allRecords is defined, it takes everything, and if not, it checks your condition.
I cleaned up and refactored your code a bit. I wrote a common function for the repetitive logic and passing the looped object to the common function to render it.
Use Map instead of forloops. You really need to check this this.state.account === this.state.eventObj.paramOne statement. This could be the reason why you see only one item on screen.
Please share some dummy data and the logic behind unshift part(never do it directly on state object), we'll fix it.
getRecord = (eventObj) => (
<React.Fragment>
<div className="result-cell">
{eventObj.paramOne}
</div>
<div className="result-cell">
{eventObj.paramTwo ? <span>Win</span> : <span>Loose</span>}
</div>
<div className="result-cell">
{eventObj.paramThree.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFour.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{eventObj.paramSix.c[0]}
</div>
</React.Fragment>
)
render() {
let allRecords;
if (this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => this.getRecord(eventObj)}</div>;
} else if (!this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => {
if (this.state.account === this.state.eventObj.paramOne) {
return this.getRecord(eventObj);
}
return null;
})}</div>;
}
return (<div className="event-result-table-container">{allRecords}</div>);
}
LONG STORY SHORT: I would like for it to load the object in the nested array IF it is not equal to undefined but react throws typeError
I have this component that takes props from a parent component. Essentially I have an array that contains chat information and when I try to access it in this child component I get some very strange behaviour.
for example if I console log(props.conversations) I get my array which looks like this: conversations[{host, members[{ username }], log[{ author, content, timestamp }]}].
if I console log (props.conversations[0]) ill get the first object in that array. But if I console log (props.conversations[0].log) I get undefined. And thats fine because at the start the state will not be defined or contain anything, so I put a ternary operator as shown below in the code props.conversations[props.index].log[0] == null ?
but all i get is TypeError: Cannot read property 'log' of undefined at the ternary function.
Maybe I am not understanding this correctly or maybe it how react functions?
Again I would like for it to load the object in the nested array IF it is not equal to undefined.
Highly appreciate the help. The most important part is the friends component. I only show the other ones to show the state being passed down.
function Friends(props) {
console.log(props.conversations[props.index]);
return (
<div className="friend">
<img className="friendavatar" src={require("./static/bobby.jpg")}></img>
<div className="friendname">{props.username}</div>
<span className="iswatchingtitle"> is watching <strong>{props.watching}</strong></span>
<div className="friendchat" onClick={props.togglechat}>
{props.conversations[props.index].log[0] == null ?
<div>undefined</div>
:
<div>defined!</div>
}
</div>
</div>
)
}
social component
function Social(props) {
return (
<div>
<div className="userquickdash row">
<div className="usernamedash">{props.username}</div>
<div className="logout"><a href="/users/logout" onClick={props.fetchlogout}>logout</a></div>
</div>
<div>
<form className="search-form-flex" method="GET" action="/search">
<input className="user-search" id="search" type="search" placeholder=" Search users..." name="usersearch"></input>
</form>
</div>
<div className='friendchatcontainer' refs='friendchatcontainer'>
{/* Append friends from social bar state (props.friends). For each friend return appropriate object info to build Friends div using Friends(props) function above. */}
{props.friends.map(function(friend, index) {
// Shortens length of video title if length of string is over 48.
let friendWatching = function friendWatchingLengthSubstring() {
if (friend.watching.length > 57) {
let friendWatching = friend.watching.substring(0, 54) + '...';
return friendWatching;
} else {
friendWatching = friend.watching;
return friendWatching;
}
};
return (
<Friends username={friend.username}
watching={friendWatching()}
key={index}
index={index}
togglechat={props.togglechat}
conversations={props.conversations}
/>
)
})}
</div>
</div>
)
}
socialbar component
class Socialbar extends Component {
constructor(props) {
super(props);
this.state = { isLoggedIn: (cookies.get('loggedIn')),
sidebarximgSrc: sidebarcloseimg,
sidebarStatus: 'open',
username: cookies.get('loggedIn'),
friends: friends,
users: {},
conversations: [],
};
}
// function to run when mongodb gets information that state has changed.
// test if the current state is equal to new object array.
// then do something.
appendFriends() {
}
componentDidMount() {
if (this.state.sidebarStatus === 'open') {
document.getElementsByClassName('maindash')[0].classList.add('maindashwide');
this.openSideBar();
} else {
document.getElementsByClassName('maindash')[0].classList.remove('maindashwide');
this.closeSideBar();
}
// check for user logged in cookie, if true fetch users.
if (this.state.isLoggedIn) {
this.fetchUsers();
}
this.getFriendConversations();
};
getFriendConversations() {
// build loop function that updates state for conversations based on length of friends array in state.
var conversationsArray = this.state.conversations;
for (var i = 0; i < friends.length; i++) {
console.log(aconversationbetweenfriends[i]);
conversationsArray.push(aconversationbetweenfriends[i]);
}
this.setState({conversations: conversationsArray});
}
render() {
let sidebar;
const isLoggedIn = this.state.isLoggedIn;
if (!isLoggedIn) {
sidebar = <Login />
} else {
sidebar = <Social username={this.state.username} friends={this.state.friends} fetchlogout={this.fetchlogout} togglechat={this.togglechat} conversations={this.state.conversations} />
}
return (
<div>
<div className="sidebar sidebar-open" ref="sidebar">
<div className="sidebarcontainer">
{sidebar}
</div>
</div>
<div className="sidebarx sidebarxopen" ref="sidebarx" onClick={this.toggleSideBar}>
<img className="sidebaropenimg" src={this.state.sidebarximgSrc} ref='sidebarximg'></img>
</div>
</div>
);
}
};
It is not a good idea to access the element directly before validation.
Use something like this:
props.conversations[props.index] && props.conversations[props.index].log[0]
Tip: User object destructuring and default props.
You need to compare for undefined like this :
{props.conversations[props.index].log[0] === undefined ?
<div>undefined</div>
:
<div>defined!</div>
}
Also, You can go to below link for sandbox running example.
Sandbox link for example to show how you should check for undefined
Hi first of all check your {props.index} print this value. if it is proper then try this out.
{
props.conversations[props.index] ?
props.conversations[props.index].log[0] ? <div>defined!</div>:<div>Undefined</div>
:
<div>Undefined</div>
}
This will check if props.conversations[props.index] is defined then and then only try to process props.conversations[props.index].log[0]. So you will not get TypeError: Cannot read property 'log' of undefined at the ternary function.
I'm fairly new to ReactJS. I am looking to get the value inside a <div> when contentEditable is set to true.
const listResults = this.state.results['1'].map((result) =>
<div key={result.toString()} contentEditable="true">
{result}
</div>
);
return (
<h1> listResults </h1>
<div> {listResults} </div>
)
I am currently outputting a list into pre-filled text-boxes which allows the user to edit them. I am looking to add in a button which once clicked captures the data inside all of the text-boxes. Can anyone point me in a direction on how to capture the changed data.
It may also be worth noting I am using ReactJS on the client side through a CDN.
To get value of editable div:
class App extends React.Component {
constructor(){
super();
this.state = {
arr: [1,2,3,4,5]
}
this.change = this.change.bind(this);
}
change(e, index){
let tmpArr = this.state.arr;
tmpArr[index] = e.target.textContent;
this.setState({arr: tmpArr})
}
render(){
console.log(this.state);
return (
<tr>
{this.state.arr.map((el, index) => <td key={index} id="test" onBlur={(e) => this.change(e, index)} contentEditable="true">{el}</td>)}
</tr>
);
}
}
https://jsfiddle.net/69z2wepo/84647/
One note, you can't return two elements on the same level:
return (
<h1> listResults </h1>
<div> {listResults} </div>
)
It should be wrapped like this:
return (
<div>
<h1> listResults </h1>
<div> {listResults} </div>
</div>
)