I have to do something like:
email ? do_this : icon ? do_that : do_something_else
This can be done very simple using nested ternary but this ESLint rule doesn't make this possible.
On their documentation they recommend to use if-else but I don't know how to implement this in my case
The code works fine with one ternary.
return (
<td>
{email ? (
<span>...</span>
) : (
<span>...</span>
)}
</td>
adding nested ternary would return that ESLint error and using if-else says that if is an unexpected token:
return (
<td>
{if(email) return ( <span>...</span>);
else if(icon) return ( <span>...</span>);
else return ( <span>...</span>);
}
</td>
Is it possible to solve this problem?
You can store the cell content in a variable:
let content;
if(email) {
content = <span>...</span>;
} else if(icon) {
content = <span>...</span>;
} else {
content = <span>...</span>;
}
return <td>{content}</td>;
I find it useful to extract a complex functionality for readability:
import React from 'react';
// extracted functionality
const emailAction = (email, icon) => {
if (email) {
return <span>Do This</span>;
} else {
if (icon) {
return <span>Do That</span>;
} else {
return <span>Do Something Else</span>;
}
}
};
// your Component
export const TableData = (props) => {
return <td>{emailAction(props.email, props.icon)}</td>;
};
Another option is to use something like an enum to render:
if (email) {
content = 'email';
else if (icon) {
content = 'icon';
} else {
content = 'other';
}
return (
<td>
{{
email: <span>...</span>,
icon: <span>...</span>,
other: <span>...</span>,
}[content]}
</td>);
This is explained in more detail here: https://reactpatterns.js.org/docs/conditional-rendering-with-enum/
Related
I'm trying to do a little react app pulling some uniswap crypto data for my own UI just for fun, I've grabbed some data with a graphql query and I'm trying to render it out on the condition that its loaded which I get from a ternary operator in my functional component.
when I try this in multiple combinations, I just get the error that allTokenData.map is not a function
I have included my component below and I have notated where my mapping function is trying to pull data from the array I get back from graphql, since I'm getting data I'm sure I'm just mixing something up with the mapping syntax :/
here is a snippet of the data I'm grabbing for reference logged in the console, any help is appreciated
function CoinData(props) {
//fetch whichever coin we want to add
const NEWCOIN_QUERY = gql`
query tokens($tokenAddress: Bytes!) {
tokens(where: { id: $tokenAddress }) {
derivedETH
totalLiquidity
}
}
`;
const { loading: ethLoading, data: ethPriceData } = useQuery(ETH_PRICE_QUERY);
const { loading: allLoading, data: allTokenData } = useQuery(QUERY_ALL_TOKENS);
const { loading: coinLoading, data: coindata } = useQuery(NEWCOIN_QUERY, {
variables: {
tokenAddress: props.parentState.newcoins!== '' ? props.parentState.newcoins.toString() : '0x6b175474e89094c44da98b954eedeac495271d0f',
},
});
const coinPriceInEth = coindata && coindata.tokens[0].derivedETH;
const coinTotalLiquidity = coindata && coindata.tokens[0].totalLiquidity;
const ethPriceInUSD = ethPriceData && ethPriceData.bundles[0].ethPrice;
console.log(props.parentState.newcoins)
return (
<div>
<div>
coin price:{" "}
{ethLoading || coinLoading
? "Loading token data..."
: "$" +
// parse responses as floats and fix to 2 decimals
(parseFloat(coinPriceInEth) * parseFloat(ethPriceInUSD)).toFixed(2)}
</div>
<div>
Coin total liquidity:{" "}
{coinLoading ? "Loading token data...": parseFloat(coinTotalLiquidity).toFixed(0)}
</div>
<div>
</div>
<div>
//////////////////////////////////////////----map function////////////////////////////
{allLoading ? "Loading token data...":
<div>
{allTokenData.map((token, index) => (
<p key={index}> {token.id} SYN: {token.symbol}</p>
))}
</div>
}
//////////////////////////////////////////----map function////////////////////////////
</div>
</div>
);
}
maybe allTokenData.tokens.map.
It is because allTokenData is an object.
const {tokens} = allTokenData
{tokens.map((token, index) => (
<p key={index}> {token.id} SYN: {token.symbol}</p>
))}
The function is getting the value of a button click as props. Data is mapped through to compare that button value to a key in the Data JSON called 'classes'. I am getting all the data correctly. All my console.logs are returning correct values. But for some reason, I cannot render anything.
I've tried to add two return statements. It is not even rendering the p tag with the word 'TEST'. Am I missing something? I have included a Code Sandbox: https://codesandbox.io/s/react-example-8xxih
When I click on the Math button, for example, I want to show the two teachers who teach Math as two bubbles below the buttons.
All the data is loading. Just having an issue with rendering it.
function ShowBubbles(props){
console.log('VALUE', props.target.value)
return (
<div id='bubbles-container'>
<p>TEST</p>
{Data.map((item,index) =>{
if(props.target.value == (Data[index].classes)){
return (
<Bubble key={index} nodeName={Data[index].name}>{Data[index].name}
</Bubble>
)
}
})}
</div>
)
}
Sandbox Link: https://codesandbox.io/embed/react-example-m1880
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
const circleStyle = {
width: 100,
height: 100,
borderRadius: 50,
fontSize: 30,
color: "blue"
};
const Data = [
{
classes: ["Math"],
name: "Mr.Rockow",
id: "135"
},
{
classes: ["English"],
name: "Mrs.Nicastro",
id: "358"
},
{
classes: ["Chemistry"],
name: "Mr.Bloomberg",
id: "405"
},
{
classes: ["Math"],
name: "Mr.Jennings",
id: "293"
}
];
const Bubble = item => {
let {name} = item.children.singleItem;
return (
<div style={circleStyle} onClick={()=>{console.log(name)}}>
<p>{item.children.singleItem.name}</p>
</div>
);
};
function ShowBubbles(props) {
var final = [];
Data.map((item, index) => {
if (props.target.value == Data[index].classes) {
final.push(Data[index])
}
})
return final;
}
function DisplayBubbles(singleItem) {
return <Bubble>{singleItem}</Bubble>
}
class Sidebar extends Component {
constructor(props) {
super(props);
this.state = {
json: [],
classesArray: [],
displayBubble: true
};
this.showNode = this.showNode.bind(this);
}
componentDidMount() {
const newArray = [];
Data.map((item, index) => {
let classPlaceholder = Data[index].classes.toString();
if (newArray.indexOf(classPlaceholder) == -1) {
newArray.push(classPlaceholder);
}
// console.log('newArray', newArray)
});
this.setState({
json: Data,
classesArray: newArray
});
}
showNode(props) {
this.setState({
displayBubble: true
});
if (this.state.displayBubble === true) {
var output = ShowBubbles(props);
this.setState({output})
}
}
render() {
return (
<div>
{/* {this.state.displayBubble ? <ShowBubbles/> : ''} */}
<div id="sidebar-container">
<h1 className="sidebar-title">Classes At School</h1>
<h3>Classes To Search</h3>
{this.state.classesArray.map((item, index) => {
return (
<button
onClick={this.showNode}
className="btn-sidebar"
key={index}
value={this.state.classesArray[index]}
>
{this.state.classesArray[index]}
</button>
);
})}
</div>
{this.state.output && this.state.output.map(item=><DisplayBubbles singleItem={item}/>)}
</div>
);
}
}
ReactDOM.render(<Sidebar />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The issue here is ShowBubbles is not being rendered into the DOM, instead (according the sandbox), ShowBubbles (a React component) is being directly called in onClick button handlers. While you can technically do this, calling a component from a function will result in JSX, essentially, and you would need to manually insert this into the DOM.
Taking this approach is not very React-y, and there is usually a simpler way to approach this. One such approach would be to call the ShowBubbles directly from another React component, e.g. after your buttons using something like:
<ShowBubbles property1={prop1Value} <etc...> />
There are some other issues with the code (at least from the sandbox) that you will need to work out, but this will at least help get you moving in the right direction.
I have a function which I need to return a string with line breaks, or divs that will render properly
export const showPeople = (peopleArray = []) => {
let peopleString = peopleArray ? peopleArray.map((people) => {
<div>
`${people.name && people.name},
${people.last_name && people.last_name}`
</div>
}).join('') : ''
return peopleString
}
I tried with divs, without divs, with return without return, and without join and i get [Object] for each person, in this particular case an empty string
I have seen similar questions but was not able to solve my problem
If the goal is to reduce a list of items to a single string that is rendered as a multi-line string, then you can use the white-space:pre-wrap; CSS rule to achieve this:
body {
white-space: pre-wrap;
}
Once you've done that you should be able to render a multiple line string with line breaks on \n characters, as follows:
export const showPeople = (peopleArray = []) => {
let peopleString = peopleArray ? peopleArray.map((people) => (
`${people.name ? people.name : ''} ${people.last_name ? people.last_name : ''}`
})
.join('\n') : ''
return peopleString
}
I've posted a working version here for you to see also
Hope this helps!
You dont want to return a string you want to return an array of elements:
let peopleString = peopleArray.map((people) => (
<div>
{people.name || ""},
{people.last_name || ""}
</div>
));
return peopleString
export const showPeople = (peopleArray = []) => {
let peopleString = peopleArray ? peopleArray.map(people =>
`<div>
${people.name}, ${people.last_name}
</div>`
).join('') : ''
return peopleString
}
var peoples = [{name: 'Jhon', last_name: 'Doe'},{name: 'Mario', last_name: 'Peach'}]
var peopleString = peoples.map(people =>
`<div>
${people.name}, ${people.last_name}
</div>`
).join('')
console.log(peopleString)
document.getElementById('container').innerHTML = peopleString
<div id="container"></div>
Since you're not doing string concatenation with variables. You need to remove Template Strings inside your divs.
It appears to me that you are not returning anything in that map function:
(people) => { ...string here... }
I would suggest, simply:
(people) => ( ...string here... )
or:
(people) => { return ...string here... }
Another issue might be that what you seem to intend to return in the map function is NOT a string, and probably(?) should be; it's hard to say without seeing the function in context.
So if you specifically want to use the HTML Line Break element to separate the strings you can use this.
import { Fragment, ReactNode } from "react";
const separateWithLineBreaks = (strings: string[]): ReactNode => {
return (
<Fragment>
{strings.map((str, index) => (
<Fragment key={index}>
{str}
{index < strings.length - 1 && <br />}
</Fragment>
))}
</Fragment>
);
};
I'm trying to convert strings starting with '#' and '#' to a clickable link and for that I'm doing a regex to replace that word with html tag and adding onclick function inside it to perform the operation by calling the search function. I'm using 'html-react-parser' to parse that in the render part. That is converting the text to button but it is not calling the search() function when I click it on. I can't understand how can I fix that? Is there anyway to fix that or any better approach?
The data I'm trying to convert is in JSON format which has three different field.
Data sample:
{date:"2014-06-01 04:27:08", text:"Bob Newhart, Master of the One-Sided Conversation t.co/xmdtl25WyD via #nytnow", user_id:"nytimes"}
My code:
import React, { Component } from 'react';
import client from './Credentials';
import '../App.css';
import Linkify from 'react-linkify';
import Parser from 'html-react-parser';
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {results: []};
}
// const Autolinker = require( 'autolinker' );
search(string){
client.search({
index: 'tweet',
type: 'tweet',
size: 1000,
body: {
query: {
"bool": {
"should": [
{"match": {"text": string}},
{"match": {"user_id": string}},
{"match": {"date": string}}
]
}
},
}
}, (error, response, status) => {
if (error) {
console.log("search error: " + error)
}
else {
console.log("--- Response ---");
console.log(response);
console.log("--- Hits ---");
response.hits.hits.forEach(function (hit) {
let re1 = new RegExp("((#|#)([a-z0-9]+))", "g");
hit._source.text = hit._source.text.replace(re1, ("<button onclick={this.search($&)}>$&</button>"));
this.setState({results: this.state.results.concat([hit._source])})
}.bind(this)
);
}
// console.log(this.state.results);
})
}
handleKeyPress = (event) => {
if(event.key === 'Enter'){
event.preventDefault();
const search_query = event.target.value;
this.state.results=[];
this.search(search_query);
}
};
render() {
return(
<Linkify>
<div>
<input className={"search-bar"} type="text" onKeyPress={this.handleKeyPress.bind(this)}>
</input>
<div>{this.state.results.map((results, index) => (
<p key={index} className={"result"}><span className={"date"}>{results.date}</span> <span> </span>
<span className={"user_id"}>{results.user_id}</span> <span> </span>
<span>{Parser(results.text)}</span></p>
))}</div>
</div>
</Linkify>
);
}
}
From what I see, you are trying to parse string as javascript code. This isn't normally possible, and is considered dangerous. If you want to do so, this issue has some insights
change the button to
hit._source.text = hit._source.text.replace(re1, ("" + $& + ""));
Parser(results, { transform: () => this.htmlParserTransformer })
and
import {convertNodeToElement} from "react-html-parser"
htmlParserTransform = (node, index) => {
if (node.type == "tag" && node.name == "button") { // button tag
const {id} = node.attribs // extract the actual url
return (
<button
onClick={() => this.search(id)} // click
>
{convertNodeToElement (node, index, this.htmlParserTransform)}
</span>
</a>
)
}
PS: this code isn't tested, but I hope I'm pointing you in the right direction.
I am working on a component where I need to display and hide a modal.
this is what I have in the render method in React
<div style={{visibility : this.state.displayModal}}>
<p>Pop up: Bet Behind Settings</p>
</div>
<button onClick={this._openModal}>CLICK</button>
and here is the function
_openModal = () => {
if (this.state.displayModal === 'hidden') {
this.setState({
displayModal : 'visible',
})
} else {
this.setState({
displayModal : 'hidden',
})
}
}
the main concern I have, is, how to set the state in a more elegant way, or this should be the way to do it ?
here the full code
constructor (props) {
super(props);
this.state = {
displayModal : 'hidden',
}
}
render () {
return (
<div style={{visibility : this.state.displayModal}}>
<p>Pop up: Bet Behind Settings</p>
</div>
<button onClick={this._openModal}>CLICK</button>
)
}
_openModal = () => {
if (this.state.displayModal === 'hidden') {
this.setState({
displayModal : 'visible',
})
} else {
this.setState({
displayModal : 'hidden',
})
}
}
so, what should be the way to this pop up in a React way.
I think it's a good way to do it. But it will be more concise if you make displayModel a boolean:
_toggleModal = () => this.setState({displayModal: !this.state.displayModal})
On a complex page using hidden will be a performance issue. Try something like this instead;
render() {
var returnIt;
if (this.state.hide) {
returnIt = null;
} else {
returnIt = (
<div style={{visibility : this.state.displayModal}}>
<p>Pop up: Bet Behind Settings</p>
</div>
<button onClick={this._openModal}>CLICK</button>
)
}
return (returnIt);
}
This is just a personal opinion, but I think a better UX would be that the button should only be used to open the modal; and the modal should be closed by either clicking the X in the modal (if there is) or when you click anywhere outside the modal.
That said if you definitely need the button to toggle between the 2 states, how about something like this?
constructor (props) {
super(props);
this.state = {
displayModal : false
}
}
render () {
return (
<div style={{visibility : this.state.displayModal === true ? 'visible' : 'hidden'}}>
<p>Pop up: Bet Behind Settings</p>
</div>
<button onClick={this._toggleModal}>CLICK</button>
)
}
_toggleModal = () => {
const current = this.state.displayModal;
this.setState({
displayModal : !current
});
}
Using https://github.com/fckt/react-layer-stack you can do like so:
import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
return (
<Cell {...props}>
// the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
<Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
hideMe, // alias for `hide(modalId)`
index } // useful to know to set zIndex, for example
, e) => // access to the arguments (click event data in this example)
<Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
<ConfirmationDialog
title={ 'Delete' }
message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
confirmButton={ <Button type="primary">DELETE</Button> }
onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
close={ hideMe } />
</Modal> }
</Layer>
// this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
<LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
<div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
<Icon type="trash" />
</div> }
</LayerContext>
</Cell>)
// ...