Conditional rendering class with map for React - reactjs

I am not even sure if what I am trying is possible but here goes. For starters, I have an array of objects where each object has two key:value pairs. One key is "name", and the second key is a "genre".
Each of these objects is mapped into a div, using its genre in the class, which is then placed into a container div. What I am trying to do is control the class of each individual div by its class name which I control via the component's state. An idea of what I have is as follows:
constructor(props) {
super(props);
this.state = {
fineArt: false,
game: false,
literature: false,
movie: false,
music: false,
tv: false
}
<div className="inspiration-container">
{inspirations.map((item) => (`${this.state}.item.genre`) ? <div className={`inspiration ${item.genre}__selected`} key={item.name}>{item.name}</div> :
<div className={`inspiration ${item.genre}`} key={item.name}>{item.name}</div>)}
</div>
An example using a "game" would be for it to map the div out, and to check the value for "this.state.game". If that value is true it renders the div with the class of "inspiration game__selected" and if it is false it would just render "inspiration game".
I know this sort of thing is possible because I have already used it as:
{this.state.game ?
<a className="inspiration-button" onClick={this.toggleGame}><div className="game-button__selected"></div>Game</a> :
<a className="inspiration-button" onClick={this.toggleGame}><div className="game-button__unselected"></div>Game</a>}
My two trains of thought are that either 1) I am doing something wrong or 2) I am unable to access the state in this manner (because of how the mapping works). If I am doing something wrong, I assume it would be somewhere in the beginning of my ternary operator. The current result I am getting is the ternary operation is always true.
Any help is appreciated and thank your for reading this and any help you can provide.

You are accessing your state incorrectly
(`${this.state}.item.genre`)
This outputs to [Object object].item.genre since you're trying to convert an object to a string.
What you want to do is access it by this.state[item.genre] which accesses the property in the state
So your code should be
{ inspirations.map((item) =>
this.state[item.genre] ?
<div className={`inspiration ${item.genre}__selected`} key={item.name}>
{item.name}
</div> :
<div className={`inspiration ${item.genre}`} key={item.name}>
{item.name}
</div>
)
}
Even better would be to use the package classnames mentioned in comments by #Bryan Downing
https://github.com/JedWatson/classnames
https://www.npmjs.com/package/classnames
This would be the ideal way to use classnames (Be careful of conflicting classnames though if you use common classnames)
import classnames from 'classnames';
// render function
{ inspirations.map((item) =>
<div
className={
classnames('inspiration', item.genre, {
[`__selected`]: item.genre
})
}
key={item.name}
>
{item.name}
</div>
)
}
// CSS
.inspiration {
... // Your css
}
// If css not the same as .selected then you can do
// .game:not(.selected)
.game {
// Your css
}
.game.selected {
// Your css
}
.movie {
// Your css
}
.movie.selected {
// Your css
}

Instead of conditionally returning two separate elements, you can conditionally create just the className instead:
{ inspirations.map( item => {
const className = `inspiration ${item.genre}${this.state[item.genre] ? '__selected' : ''}`;
return <div className={className} key={item.name}>
{item.name}
</div>
}}

Related

Show / Hide element by data attribute (react=

How can i show/hide elements in a list using react? I have buttons with data-attribute
and i want when click show elements with this classname and hide the others.
example here:
class ModelosItems extends React.Component {
handleCheck(e) {
console.log(e.currentTarget.dataset.gama );
}
render() {
return (
<section className="section">
<div className="container">
<h2 className="title is-size-4 has-text-centered is-uppercase has-text-weight-bold">Gama kia</h2>
<div className="tabs-container">
<div className="fade"></div>
<div className="tabs">
<ul>
<li className="is-active">Gama Completa</li>
<li>Cidatidos e Familiares</li>
<li>Suv e Crossover</li>
</ul>
</div>
</div>
<ModelsList />
</div>
</section>
);
}
};
export default ModelosItems;
Thank you!
For hide the other datas and just show the special data, you have to filter your list. After, you can for example, add a CSS class whose this class hide the other datas, and you'll have just your special data showed.
A tutorial : https://www.youtube.com/watch?v=HqQ-kOchqHM
Utilize local state for this. Initialize it with the first value (probably todos) and update it when you select another type correspondingly.
Next, pass this state data attribute to ModelsList component as a prop and simply filter the items within the list.
Introduce state to ModelosItems
constructor(props) {
super(props);
this.state = {currentGama: null};
}
on click update state
handleCheck(e) {
this.setState({
currentGama: e.currentTarget.dataset.gama
});
}
Add new prop to ModelosItems gama and feed currentGama state value to it
<ModelsList gama={this.state.gama} />
In your ModelList component, I'm guessing here
cars.filter(car => car.gama === this.props.gama).map(...
this will result in rendering only cars that have right gama. This does not use classnames to hide cars. But I think this is what you need.

styled component computing style but not applying it

Dear genius StackOverflowians,
I am trying to write an app where users can configure questions and answers, along with defining help text for each question. I'm writing this in typescript React - which is handy when you want to define types of answers for questions.
I want to have a button next to the question that shows/hides a styled document. The button looks and works great, but the document that is hidden/shown doesn't get the generated style class that ought to be associated with it.
Here is the functional component to display the help document:
let HelpTextBody = function(props: { helpDocument: DocumentationStore }) {
return (
<div>
{props.helpDocument.toReallySimple().map(tok => {
return React.createElement(tok.tag, null, tok.content);
})}
</div>
);
};
tok comes from a custom class DocumentationStore that is pretty much a wrapper around markdown-it, a handy js library for working with md files, which I would like my users to write their helptext in (and store it that way).
So I do this (in a different module for DocumentationStore class):
toReallySimple(): MdJson[] {
let bigParsed = this.md_.parse(this.Text, null).filter(
t => return t.type == "inline" || t.type.indexOf("open") > 0
});
Later on, I style HelpTextBody with:
const StyledHelpDocument = styled(HelpTextBody)`
background-color: lightslategray;
`;
Keeping it simple now so I can just see if it's working...
I then include it in a component with the button that I export:
class HelpText extends React.Component<helpProps, helpState> {
constructor(props: helpProps) {
super(props);
this.state = {
hidden: true
};
}
swapHidden() {
this.setState({
hidden: !this.state.hidden
});
}
render() {
if (this.state.hidden) {
return (
<span>
<StyledButton
itemScope={this.state.hidden}
onClick={() => this.swapHidden()}
>
Need Help?
</StyledButton>
</span>
);
} else {
return (
<span>
<StyledButton onClick={() => this.swapHidden()}>
Hide Help
</StyledButton>
<StyledHelpDocument helpDocument={this.props.helpDocument} />
</span>
);
}
}
So I webpack it all and get stuff into the browser, and what I get back is this style tag (after clicking the button), which looks right:
<style data-styled-components="">
/* sc-component-id: sc-bdVaJa */
.sc-bdVaJa {} .gscXTZ{background:red;color:white;font-size:1em;margin:1em;padding:0.25em 1em;border:2px solid red;border-radius:3px;}.iwtdKP{background:white;color:red;font-size:1em;margin:1em;padding:0.25em 1em;border:2px solid red;border-radius:3px;}
/* sc-component-id: sc-bwzfXH */
.sc-bwzfXH {} .hAvMqj{background-color:lightslategray;}</style>
But my html for the document is missing the reference to the class (.hAvMqj I guess?)
<span>
<button class="sc-bdVaJa iwtdKP">Hide Help</button>
<div><p>Here the text is grey</p></div>
<!-- ^This^ is the StyledHelpDocument... no class!-->
</span>
So where am I going wrong? I don't understand why it generates the style, and the component's HTML renders... but the class isn't applied to the component! What do you think?
Your styled-components class isn't being applied because you're styling a custom component, but you haven't included className as a prop. Add className as an optional prop in the component you're styling, and also be sure to apply className somewhere in the render method for that component. For your case, it should be added like so:
let HelpTextBody = function(props: { helpDocument: DocumentationStore, className: string }) {
return (
<div className={props.className}>
{props.helpDocument.toReallySimple().map(tok => {
return React.createElement(tok.tag, null, tok.content);
})}
</div>
);
};

In React, Is it good practice to search for certain element in DOM?

Is it good to just specify className for element so i could find it later in the DOM through getElementsByClassName for manipulations?
Adding a class to find the DOM element? Sure you can do that, but refs are probably the better solution.
Manipulating the DOM element? That's an absolute no-go. The part of the DOM that is managed by React should not be manipulated my anything else but React itself.
If you come from jQuery background, or something similar, you will have the tendency to manipulate element directly as such:
<div class="notification">You have an error</div>
.notification {
display: none;
color: red;
}
.show {
display: block;
}
handleButtonClick(e) {
$('.notification').addClass('show');
}
In React, you achieve this by declaring what your elements (components) should do in different states of the app.
const Notification = ({ error }) => {
return error
? <div className="notification">You have an error</div>
: null;
}
class Parent extends React.Component {
state = { error: false };
render() {
return (
<div>
<Notification error={this.state.error} />
<button onClick={() => this.setState({ error: true })}>
Click Me
</button>
}
}
The code above isn't tested, but should give you the general idea.
By default, the state of error in Parent is false. In that state, Notification will not render anything. If the button is clicked, error will be true. In that state, Notification will render the div.
Try to think declaratively instead of imperatively.
Hope that helps.
When using React, you should think about how you can use state to control how components render. this.setState performs a rerender, which means you can control how elements are rendered by changing this.state. Here's a small example. I use this.state.show as a boolean to change the opacity of the HTML element.
constructor(props) {
super(props)
this.state = {
show: true
}
}
handleClick() {
this.setState({show: false})
}
render() {
const visibility = this.state.show ? 1 : 0
return (
<button style={{opacity: visibility} onClick={() => this.handleClick()}>
Click to make this button invisible
</button>
)
}

Unknown props warning when passing props to children

I'm trying to create a reusable dropdown menu wrapper component using this pattern:
class DropdownMenu extends React.Component {
render() {
return (
<span>
{React.cloneElement(
this.props.children,
{
menuOpen: this.props.menuOpen,
toggleMenu: this.props.toggleMenu
}
)}
</span>
);
}
}
const HeaderUserDropdown = ({menuOpen, toggleMenu }) => (
<DropdownMenu>
<div className={menuOpen ? 'visible' : ''}>
<button onClick={toggleMenu} />
</div>
</DropdownMenu>
)
But I get an error along the lines of Warning: Unknown props menuOpen, toggleMenu on <div> tag. Remove these props from the element. I know that I can use data- to get this working correctly, but that seems sort of hacky. What's the correct way to pass these props down to the children?
React distinguishes between HTML elements which are written in lower case (e.g. <div>) and React components which start with a capital letter.
In your code, you're trying to clone an HTML div element and add the properties menuOpen and toggleMenu, but these attributes are not supported by <div>, hence the warning. You need to set custom attributes on an HTML element, you'll need to use the data- prefix convention.

How does the ReactBootstrap OverlayTrigger container property work?

I have a popover inside OverlayTrigger.
I define it as
const myOverlayTrigger = <ReactBootstrap.OverlayTrigger
placement='bottom' overlay={<ReactBootstrap.Tooltip>...</ReactBootstrap.Tooltip>}>
{myContent}
</ReactBootstrap.OverlayTrigger>;
Then I render it inside one of my elements like that:
<li>{myOverlayTrigger}</li>
I want to render OverlayTrigger itself inside <li> but it renders inside body, as defined in documentation. I'm trying to use container attribute to render it inside parent <li>.
First, I tried to assign ID to <li> and pass this ID as a string to container=... (which isn't a best way).
Second, I tried to create additional element <span></span> and render it inside along with {myOverlayTrigger}. Also I pass it (assigned to variable) to container attribute
const c = <span></span>;
... container={c} ...
<li>{c} {myOverlayTrigger}</li>
Both approaches consistently gives an error not a dom element or react component.
Obviously assigning <li>...</li> itself as a container doesn't work either as it being defined after myOverlayTrigger is defined.
Question: how to use it right?
ReactBootstrap.Overlay is recommended for the reason listed in the document.
The OverlayTrigger component is great for most use cases, but as a
higher level abstraction it can lack the flexibility needed to build
more nuanced or custom behaviors into your Overlay components. For
these cases it can be helpful to forgo the trigger and use the Overlay
component directly.
For your case, the code below renders the ReactBootstrap.Overlay component into a list item with React ref attribute.
getInitialState() {
return (
show: false
);
},
render() {
return (
<ul>
<li ref="dest" onClick={ () => {
this.setState( { show: !this.state.show } );
}}>my contents</li>
<ReactBootstrap.Overlay placement="bottom"
show={ this.state.show } container={ this.refs.dest }>
<ReactBootstrap.Tooltip>tooltip</ReactBootstrap.Tooltip>
</ReactBootstrap.Overlay>
</ul>
);
}
When the tooltip is displayed by clicking, the resulting HTML would be
<ul data-reactid=".0.1.0.0.0.0.0.1.1.1.1:$3.1.1">
<li data-reactid=".0.1.0.0.0.0.0.1.1.1.1:$3.1.1.0">
contents
<div>
<div role="tooltip" class="fade in tooltip right" data-reactid=".3">
<div class="tooltip-arrow" data-reactid=".3.0"></div>
<div class="tooltip-inner" data-reactid=".3.1">My tooltip</div>
</div>
</div>
</li>
<span data-reactid=".0.1.0.0.0.0.0.1.1.1.1:$3.1.1.1">,</span>
<noscript data-reactid=".0.1.0.0.0.0.0.1.1.1.1:$3.1.1.2"></noscript>
</ul>

Resources