React formatting HTML element properties from multiple sources - javascript

I am iterating over state that holds the data for cards like
cards: [
{ id: 1, name: "p1", value: "Prize 1", imageSrc: "/path/to/image", bgColor: { background: "#FF6384", border: "4px solid #FF6384" }, isVisible: "" },
in my iteration, I am using a ternary to determine one of the classes like
<div className={`prize-card ${this.state.flipped === card ? "flipped" : ""} `} onClick={() => this.clickHandler(card)}>
I see how I can use the ternary to determine a second class on top of standard "prize-card", but how would I add even a third class based on the bgColor state? In a standard HTML element, I can just:
<div className={card.bgColor}>
But I can't figure out the syntax on how to add the {card.bgColor} to the rest of the className below... the bgColor below is wrapped in asterisks to show what I cannot add without errors.
<div className={ **{card.bgColor}**`prize-card ${this.state.flipped === card ? "flipped" : ""} `} onClick={() => this.clickHandler(card)}>
Any help is appreciated. Thank you in advance.

Related

How can a component be rendered conditionally dependent upon the value in a defaultprops array?

I have a ReactJS static defaultProps array, which is used to render a number of buttons. I'm looking to conditionally generate a text-component dependent upon which specific button is clicked.
Here is an example depicting a Car component:
...
static defaultProps = {
cars: [
{ type: 'hatchback', color: 'red', info: '...' },
{ type: 'sedan', color: 'blue', info: '...' },
{ type: '4x4', color: 'green', info: '...' },
]
}
...
Let us say that each 'type' field generates a button labelled with type. Let's imagine that I want to create a swatch div that depicts the color onClick. So if I click the 'hatchback' button, then the swatch div will turn 'green' or 'red' or 'blue', dependent upon the car type selected.
What is the easiest way to do this?
Here is an example of the button mapping function:
{this.props.cars.map((car) => (
<button>
{car.type}
</button>
And here would be an example of the colorSwatch div:
<div className={styles.colorSwatch}>{car.info}</div>
To be as specific as possible, the problem is to do with the text content and not conditional CSS issues.
I want to know how to conditionally set info.
You can store the clicked button object in state and use it to render your colorSwatch div
{this.props.cars.map((car) => (
<button onClick={() => this.setState({clickedBtn: car})}>
{car.type}
</button>
const { clickedBtn } = this.state;
...
{clickedBtn && <div className={styles.colorSwatch}>{clickedBtn.info}</div>}

Propagate style props to React children and apply them to a CSS file

I am rendering component Dropdown which acts like html dropdown component, but is a collection of divs ordered and unordered lists. I am trying to pass styles to the className elements , which have dropdown.css file rendering the styles, but I am not sure how to target those specific elements all the way from the parent component.
How to target
div className="select-box-current"
with style={{ border: "1px solid #D4D4D4" }}
and
div className="select-box-list"
with
style={{
opacity: 1,
display: "inherit",
animationName: "none",
cursor: "pointer"
}}
The CodeSandblox is here -> https://codesandbox.io/s/weathered-wood-cf8u8?file=/src/App.js:481-614
So with React if you pass props that are the same name, it only select the one that was passed last. So with your two style props, it only would pass the last one. However, it probably isn't the best idea to use the name style anyway, since it isn't descriptive and also mirrors the actual HTML style attribute.
Give the two different styles unique names, in the App.js file:
<div className="App">
<Dropdown
inputName="viola"
list={[
{ yard_id: "1", yard_name: "Yard 1" },
{ yard_id: "2", yard_name: "Yard 2" }
]}
// this style is for className="select-box-current"
currentStyles={{ border: "1px solid #D4D4D4" }}
// this style is for className="select-box-list"
listStyles={{
opacity: 1,
display: "inherit",
animationName: "none",
cursor: "pointer"
}}
/>
</div>
Now we need to pass those two props through your component tree, the next file is the Dropdown.js file.
Before I get to passing the props, I want to comment about something that is wrong, that isn't entirely related.
export const Dropdown = ({ list, ...others }) => {
const copiedProps = {};
Object.keys(others).forEach((key) => {
// these are the props that need to get thru:
if ("style" === key || "className" === key) {
copiedProps[key] = others[key];
}
});
...
The copying of props, isn't necessary and the way that it is done is actually detrimental. If you want to specifically target a value in the incoming props, you can access it directly (ex props.myProp or in this case other.style) or destructuring assignment.
Since you are wanting to only pass the style (now listStyles and currentStyles) and the className I chose to assign them to a variable using the destructuring assignment.
export const Dropdown = ({
list,
currentStyles,
listStyles,
className,
...others
}) => { ... }
Now that we have those props, we want to pass it into your DropdownView which contains the actual elements you're wanting to target.
<DropdownView
dropdownItems={dropdownItems}
activeItem={activeItem}
hover={hover}
setActiveItem={(activeItem) => {
setActiveItem(activeItem);
}}
onMouseOver={(hover) => onMouseOver(hover)}
toggleList={() => toggleList(!hideList)}
hideList={hideList}
className={className}
currentStyles={currentStyles}
listStyles={listStyles}
/>
Okay, now we have the styles and classname where we want them. All you have to do is get the props like we did above, and then set them on the elements.
<div
className="select-box-current"
tabIndex="0"
autoFocus={true}
onClick={() => toggleList()}
style={currentStyles}
>...</div>
I forked the sandbox to include a working example. But node that I didn't set the use the className prop in the DropdowView since it wasn't clear what element would have that.
https://codesandbox.io/s/hardcore-sun-yyx0g?file=/src/DropdownView.jsx
I think instead of using them from the parent div you can directly use those styles to those elements like this. https://codesandbox.io/s/eloquent-lamport-3spzr?file=/src/App.js
But if you want to use those styles from the parent. Then you can pass them using specific name. Like this
<Dropdown
inputName="viola"
list={[
{ yard_id: "1", yard_name: "Yard 1" },
{ yard_id: "2", yard_name: "Yard 2" }
]}
// this style is for className="select-box-current"
selectBoxCurrentStyle={{ border: "1px solid #D4D4D4" }}
// this style is for className="select-box-list"
selectBoxListStyle={{
opacity: 1,
display: "inherit",
animationName: "none",
cursor: "pointer"
}}
/>
Here is the link https://codesandbox.io/s/damp-rain-cyhxb?file=/src/App.js:133-643

My state open all of my submenu but i just want one submenu open

I have a left-menu and when you click on a element, the sub-menu of the element appear.
But with my actual code, when a click on a element, all of my submenu appear.
I know my method is not right, but i don't know how to do :(
My example code :
import { useState } from 'react'
export default function Menu(){
const [visibleSubCategorie, setVisibleSubCategorie] = useState(false)
const Menu = [{
name: 'Homme', link : '/homme-fr', subCategory: false
}, {
name: 'Femme', link : '/femme-fr', subCategory: [{
name: 'haut', link : '/femme-haut-fr'
},{
name: 'bas', link : '/femme-bas-fr'
}]
},{
name: 'Enfant', link : '/enfant-fr', subCategory: [{
name: 'haut', link : '/enfant-haut-fr'
},{
name: 'bas', link : '/enfant-bas-fr'
}]
}]
console.log("Menu -> Menu", Menu)
return(
<>
{Menu.map(item=>
<div>
{item.subCategory ?
<>
<button type="button" onClick={() => setVisibleSubCategorie(!visibleSubCategorie)}>{item.name}</button>
{visibleSubCategorie && item.subCategory.map(subCategorys=>
<>
<p>{subCategorys.name}</p>
</>
)}
</>
:
<a href={item.link}>{item.name}</a>
}
</div>
)}
</>
)
}``
example : when i click at the button "Femme" to see the sub-category of femme, it's like i click too on the button "Enfant".
I can create a composant and make the usestate "const [visibleSubCategorie, setVisibleSubCategorie] = useState(false)" inside and this composant inside the map but i know another method exist.
You are using the same piece of state to control all of your subCategories. A possible solution would be to useState as an array of string values for each subcategory.
const [visibleSubCategorie, setVisibleSubCategorie] = useState([])
setVisibleSubCategorie([...visibleSubCategorie, subCategorys.name])
Then check to see if that name exists in the array to know if you should show the subcategory.
{visibleSubCategorie.includes(subCategorys.name) && item.subCategory.map(subCategorys=>
You will then have to remove that item from the array when closing.
You could solve this using a method similar to what #Kyler suggested.
I suggest using a HOC, like this:
const setOpen = (setOpen, opened) => () => setOpen(!opened);
And then in your JSX:
onClick={setOpen(setVisibleSubCategorie, visibleSubCategorie)}
Note that in order for this to work, you'd have to have state for each of your sections.

Can I specify a Divider or Header in Semantic UI React's options array for the dropdown component?

I am working with ReactJS and using SemanticUI for ReactJS to style the front end,
Is it possible to specify a header or divider from within the options array of objects for a dropdown component?
I get the impression from the documentation that this is not supported yet.
I solved this by changing to object in the options array to have more properties (which allow you to customise the content):
{
text: "YouGov Filters",
value: "yougov-header",
content: <Header content="YouGov Filters" color="teal" size="small" />,
disabled: true
},
It's probably not the ideal way to achieve what I want because I have to set disabled to true (I don't want it to be a selectable option) which means it adopts the greyed out 'disabled' style. I tried to counter this by specifying a color for the header which resulted in the disabled style being applied over the teal colour, not perfect but it will do for now.
Another workaround is to do it by map array:
const options = [
{
text: "note",
icon: 'sticky note outline',
description: 'test',
},
{
divider: true
},
{
text: "task",
icon: 'calendar check outline',
description: 'test',
},
];
return (
<Dropdown className='multicontent__button' text='add new' button>
<Dropdown.Menu>
<Dropdown.Header icon='tags' content='Tag Label' />
{options.map((option, i) => {
if (option.divider === true) return (<Dropdown.Divider key={i}/>);
return (
<Dropdown.Item
key={i}
text={option.text}
icon={option.icon}
description={option.description}
action={option.action}
onClick={this.handleOption}
/>
);
})}
</Dropdown.Menu>
</Dropdown>
);
Mr B's solution is genius. And it can be cleaner with a little modification of his:
function FragmentWithoutWarning({key, children}) {
// to get rid of the warning:
// "React.Fragment can only have `key` and `children` props."
return <React.Fragment key={key}>{children}</React.Fragment>;
}
// then just:
{
as: FragmentWithoutWarning,
content: <Header content="YouGov Filters" color="teal" size="small" />
}
Since <React.Fragment /> is not able to capture any event, you even don't have to disable the item.

React - changing the background of a single span class not working

I am new to React so my apologies if the question, or the thing I am trying to achieve is just weird (and please do tell if there is a better / more logic way to do this).
I am using the List Fabric React component in my React application, which is based on the ListGridExample component which is found here:
https://developer.microsoft.com/en-us/fabric#/components/list
I have set it up but I can't seem to accomplish the following:
When a span class (which is actually an item) in the List component is clicked, I want to change it's background color, to do this I have followed the instructions in the following post:
https://forum.freecodecamp.org/t/react-js-i-need-a-button-color-to-change-onclick-but-cannot-determine-how-to-properly-set-and-change-state-for-that-component/45168
This is a fairly simple example but this changes all my grid cells / span classes to the color blue instead of only the clicked one. Is there a way I can make just the clicked span class change it's background?
The Initial state:
The state after clicking one span class (which is wrong):
Implementation code (ommitted some unecesary code):
class UrenBoekenGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
bgColor: 'red'
}
}
render() {
return (
<FocusZone>
<List
items={[
{
key: '#test1',
name: 'test1',
},
{
name: 'test2',
key: '#test2',
},
{
name: 'test3',
key: '#test3',
},
{
name: 'test4',
key: '#test4',
},
..... up to 32 items
]}
onRenderCell={this._onRenderCell}
/>
</FocusZone>
);
}
changeColor(item){
this.setState({bgColor: 'blue'});
console.log('clicked item == ' + item.name)
}
_onRenderCell = (item, index) => {
return (
<div
className="ms-ListGridExample-tile"
data-is-focusable={true}
style={{
width: 100 / this._columnCount + '%',
height: this._rowHeight * 1.5,
float: 'left'
}}
>
<div className="ms-ListGridExample-sizer">
<div className="msListGridExample-padder">
{/* The span class with the click event: */}
<span className="ms-ListGridExample-label" onClick={this.changeColor.bind(this, item)} style={{backgroundColor:this.state.bgColor}}>{`item ${index}`}</span>
<span className="urenboeken-bottom"></span>
</div>
</div>
</div>
);
};
}
I now have attached the click event to the span class itself but I would think it is way more logic to have the click event on the item(s) (array) itself, however I could not find a way to achieve this either.
----UPDATE----
#peetya answer seems the way to go since #Mario Santini answer just updates a single cell, if another cell is clicked then the previous one returns back to normal and loses it's color.
So what I did is adding the items array to the state and adding the bgColor property to them:
this.state = {
items: [
{
key: '#test1',
name: 'test1',
bgColor: 'blue',
},
{
name: 'test2',
key: '#test2',
bgColor: 'blue',
},
{
name: 'test3',
key: '#test3',
bgColor: 'blue',
},
{
name: 'test4',
key: '#test4',
bgColor: 'blue',
},
],
}
Now in my List rendering I have set the items to the state items array and added the onClick event in the _onRenderCell function:
render() {
return (
<FocusZone>
<List
items={this.state.items}
getItemCountForPage={this._getItemCountForPage}
getPageHeight={this._getPageHeight}
renderedWindowsAhead={4}
onRenderCell={this._onRenderCell}
/>
</FocusZone>
);
}
_onRenderCell = (item, index) => {
return (
<div
className="ms-ListGridExample-tile"
data-is-focusable={true}
style={{
width: 100 / this._columnCount + '%',
height: this._rowHeight * 1.5,
float: 'left'
}}
>
<div className="ms-ListGridExample-sizer">
<div className="msListGridExample-padder">
<span className="ms-ListGridExample-label"
onClick={this.onClick(item.name)}
style={{backgroundColor: item.bgColor}}
>
{`item ${index}`}
</span>
<span className="urenboeken-bottom"></span>
</div>
</div>
</div>
);
};
The problem is that I can't add the onClick event in the _onRenderCell function as this will give the following error:
I want to keep the Fabric List component as it also has functions for rendering / adjusting to screen size, removing the list component entirely and just replacing it with what #peetya suggested works:
render() {
<div>
{this.state.items.map(item => (
<div onClick={() => this.onClick(item.name)} style={{backgroundColor: item.bgColor}}>
{item.name}
</div>
))}
</div>
}
But this will also remove the List component functionality with it's responsive functions.
So my last idea was to just replace the items of the List with the entire onClick div and removing the _onRenderCell function itself, but this makes the page blank (can't see the cells at all anymore..):
render() {
return (
<FocusZone>
<List
items={this.state.items.map(item => (
<div onClick={() => this.onClick(item.name)} style={{backgroundColor: item.bgColor}}>
{item.name}
</div>
))}
getItemCountForPage={this._getItemCountForPage}
getPageHeight={this._getPageHeight}
renderedWindowsAhead={4}
// onRenderCell={this._onRenderCell}
/>
</FocusZone>
);
}
I thought that perhaps the css ms-classes / div's should be in there as well because these have the height/width properties but adding them (exactly as in the _onRenderCell function) does not make any difference, the page is still blank.
The problem is that you are storing the background color in the state of the Grid and assign this state to every element of the grid, so if you update the state, it will affect every element. The best would be if you create a separate component for the Grid elements and store their own state inside there or if you want to use only one state then store the items array inside the state and add a new bgColor attribute for them so if you want to change the background color only for one item, you need to call the setEstate for the specific object of the items array.
Here is a small example (I did not tested it):
class UrenBoekenGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{
key: '#test1',
name: 'test1',
bgColor: 'blue',
},
],
};
}
onClick(name) {
this.setState(prevState => ({
items: prevState.items.map(item => {
if (item.name === name) {
item.bgColor = 'red';
}
return item;
})
}))
}
render() {
<div>
{this.state.items.map(item => (
<div onClick={() => this.onClick(item.name)} style={{backgroundColor: item.bgColor}}>
{item.name}
</div>
))}
</div>
}
}
Actually you are changing the color of all the span elements, as you set for each span the style to the state variable bgColor.
Insteas, you should save the clicked item, and decide the color based on that:
this.state = {
bgColor: 'red',
clickedColor: 'blue
}
In the constructor.
Then in the click handler:
changeColor(item){
this.setState({selected: item.name});
console.log('clicked item == ' + item.name)
}
So in the renderer (I just put the relevant part):
<span ... style={{backgroundColor: (item.name === this.state.selected ? this.state.clickedColor : this.state.bgColor)}}>{`item ${index}`}</span>

Categories

Resources