conditional rendering order of the elements based on props? - javascript

I created a search input component and this input has an icon which I need to change the icon's align (on the left side or right side) based on different situations (it's a bootstrap input-group) so all I need to do is change the order of elements inside my div and I need a clean way to do this. I know I can use a ternary operation to conditional render my elements but this would cause to repeat the code. I'm looking for much cleaner way so any suggestions?
const SearchInput = ({onClose, onChangeValue, ...restProps}) => {
return (
<div className='input-group'>
<button className='btn' type='button'> //right now its on left side...
<i className='bi bi-search'></i>
</button>
<Input placeholder='Search here...' onChangeValue={onChangeValue} {...restProps} />
{onClose && <i className='bi bi-x' onClick={onClose}></i>}
</div>
);
};

My proposal
import clsx from 'clsx';
const SearchInput = ({onClose, onChangeValue, conditionProp, ...restProps}) => {
return (
<div className={clsx('input-group', conditionProp && 'my-order)}>
<button className='btn' type='button'> //right now its on left side...
<i className='bi bi-search'></i>
</button>
<Input placeholder='Search here...' onChangeValue={onChangeValue} {...restProps} />
{onClose && <i className='bi bi-x' onClick={onClose}></i>}
</div>
);
};
In CSS
.my-order {
flex-direction: row-reverse;
}
I like cslsx for conditional classnames, but of course I can do without it - conditionProp ? 'input-group my-order' : 'input-group'

Related

How do I make React onClick calls only affect a single item, instead of all?

I have an array of objects, and for each one I .map it into a component called Card.js. Each card has an 'edit' button, and I have an edit form which I want to appear ONLY for the card on which I clicked the button.
At the moment, whatever I try to do to pass an id into the Editform.js component, it still makes the form appear for all of the card components.
Here's the current component I call which is meant to render just form for the clicked button. I pass in all of the cards in the 'cards' array, and what I believe is the id of the current .map object from the calling function:
function Editform({ cards, setCards, id }) {
const thisCard = cards.filter((card) => card.id === id)[0];
const editThisCard = thisCard.id === id; // trying to match id of passed card to correct card in 'cards' array.
console.log(editThisCard);
return (
<>
{editThisCard && ( // should only render if editThisCard is true.
<div className="form">
<p>Name of game:</p>
<input type="text" value={thisCard.gamename}></input>
<p>Max players: </p>
<input type="text" value={thisCard.maxplayers}></input>
<p>Free spaces: </p>
<input type="text" value={thisCard.freespaces}></input>
<p>Table #: </p>
<input type="text" value={thisCard.tablenum}></input>
<p></p>
<button type="button" className="playbutton">
Save changes
</button>
</div>
)}
</>
);
}
export default Editform;
edit: apologies, I forgot to paste in the other code. Here it is. Note that I'm just hardcoding in a couple of cards for now:
import React from "react";
import ReactFitText from "react-fittext";
import Editform from "./Editform";
function Displaycards({ lastid }) {
const [cards, setCards] = React.useState([
{
id: 1,
gamename: "El Dorado",
maxplayers: 4,
freespaces: 1,
tablenum: 5,
},
{
id: 2,
gamename: "Ticket to Ride",
maxplayers: 4,
freespaces: 2,
tablenum: 3,
},
]); // using the React state for the cards array
const [showForm, setShowForm] = React.useState((false);
return (
<div className="cardwrapper">
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
<div>
<div className="card">
<ReactFitText compressor={0.8}>
<div className="gamename">{gamename}</div>
</ReactFitText>
<div className="details">
<p>Setup for: </p>
<p className="bignumbers">{maxplayers}</p>
</div>
<div className="details">
<p>Spaces free:</p>
<p className="bignumbers">{freespaces}</p>
</div>
<div className="details">
<p>Table #</p>
<p className="bignumbers">{tablenum}</p>
</div>
<button type="button" className="playbutton">
I want to play
</button>
<br />
</div>
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => setShowForm(!showForm)}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
{showForm && (
<div>
<Editform
cards={cards}
setCards={setCards}
id={id}
/>
</div>
)}
</div>
</div>
);
})}
</div>
);
}
export default Displaycards;
I feel like I'm missing something obvious, but I can't get my head around what it is. The current iteration of it is here - https://github.com/TSDAdam/lfp/tree/usestate-trial - and it was created with create-react-app .
It sounds like you have one state controlling all of the Cards. You haven't shown the Card component yet however. Have every Card control its own state, so when the edit button bound to the card is clicked, it only applies to that one card. If you show us more code we can narrow it down, but this is most likely the gist of your problem.
The problem is that the EditForm is inside the map function, so for every item in your cards array, a separate EditForm is rendered with the corresponding values, and all these EditForms get shown/hidden based on the same boolean in your state.
The solution is to move the EditForm outside the map function, and create a new state object that tracks an "active" card, from where the single EditForm could take its values.
This of course won't work if you want to render the EditForm in a position relative to the "active" card.
[Edit]
Okay, I ended my answer with a caveat, but I should add a solution for that as well, since it isn't very complicated.
If you want to render an EditForm below the selected card, for example, the approach would be to keep it inside the map function as it is now, and change the boolean state variable showForm into one that accepts a string/number (depending on what you use as the identifier for each card). And then use this state variable to determine which form shows at any given time.
const [showForm, setShowForm] = React.useState("");
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
// Rest of the JSX
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => setShowForm(id)}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
{showForm == id && (
<div>
<Editform
cards={cards}
setCards={setCards}
id={id}
/>
</div>
)}
</div>
</div>
);
})}

Map function renders first react element differently while using tailwind's utility classes

I am using Tailwind for CSS in my project and I map over a React button element like this:
<div className="flex flex-wrap justify-center items-center space-x-1 space-y-1">
{domains.map((domain, index) => {
return (
<Button
key={index}
bgColor="transparent"
hoverEffect="blue-500"
textColor="gray-700"
textHover="white"
boarder="blue-500"
boarderHover="blue-500"
otherClasses="text-xs block rounded-full px-4"
selcted="blue-500"
buttonTitle={domain}
/>
);
})}
</div>
Edit 1: Here is the button component
export const Button = ({
bgColor,
hoverEffect,
textColor,
textHover,
boarder,
boarderHover,
otherClasses,
buttonTitle,
selcted,
}) => {
return (
<>
<div className="block">
<button
className={
`bg-${bgColor} ${hoverEffect && `hover:bg-${hoverEffect}`}
text-${textColor} ${textHover && `hover:text-${textHover}`}
border border-${boarder} hover:border-${boarderHover} ${otherClasses}` +
` lg:focus:bg-${selcted ?? selcted} lg:focus:text-${textHover}`
}
>
{buttonTitle}
</button>
</div>
</>
);
};
If I render this code it returns something like this:
(https://drive.google.com/file/d/1XqxRQ6bUTSQqZTJVqbv-c9Rs4hXy01J2/view?usp=sharing)
The first item in the map gets a different orientation when I use flex or grid. I don't understand what I am doing wrong here. If I manually render several times it renders properly.
Guys Sorry for the late update. but I found out this is some issue with the vertical space option in tailwind CSS.
class= "space-y-6"
something similar to this. if you are using this try removing this. or find a workaround, it should work.

How do I make a label for a slider that slides along with the current value?

I want to make a label on top of the slider that moves along with it and updates it's value as it moves.
I can get the label to show but I don't know how to move it with the slider.
Also, I want to make buttons on the sides but if I place them on top of the bar, the slider doesn't work anymore. (code commented out)
function slideBar({ min, max, onChange, value, style, onCLick }) {
return (
<div className="slider-whole">
<div className="labels" style={style}>
<p className="min">{min}</p>
<p className="currentValue" style={style}>{value}</p>
<p className="max">{max}+</p>
</div>
<input
type="range"
name="range"
min={min}
max={max}git
value={value}
className={`slider slider${style.color}`}
id="myRange"
onChange={onChange}
style={style}
/>
{/* <div className="arrow-buttons" style={style}>
<i className="fas fa-chevron-left"></i>
<i className="fas fa-chevron-right"></i>
</div> */}
{/* <output htmlFor="range" onforminput="value = range.valueAsNumber;"></output> */}
</div>
)
}
Set your class slider-name with position:relative and class currentValue with position:absolute; and set the value for left dynamically as the current value changes.

How to use both custom scss and boostrap css in the className Ract JS

import styles from '../styles/filterableTable.scss';
Then view
return (
<div >
<p className={'text-success' + styles.ff}>Uthaya</p>
<input
value={filter}
ref={node => {input = node;}}
onChange={() => onFilter(input.value)} />
<ProductTable filter={filter} />
</div>
);
};
<p className={'text-success' + styles.ff}>Uthaya</p>
I tried this way but not working
"text-success" is boostrap class. if only this class is working combine both is not working
Can any help me fast.
you just need to put a space after text-success
<p className={'text-success ' + styles.ff}>Uthaya</p>
otherwise it will append the first thing from styles.ff to text-success and be one class that doesn't exist!

React Bootstrap Custom Component Overflowing Column

I have a custom toggle dropdown:
import React from 'react';
import 'react-datepicker/dist/react-datepicker.css';
const DateRange = props => (
<div className="dropdown artesianDropdownContanier">
<div className="btn-group width100">
<button type="button" className="btn dropdown-width">{props.selected}</button>
<button type="button" className="btn dropdown-toggle dropdown-toggle-split artesianDropdownToggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span className="sr-only">Toggle Dropdown</span>
</button>
<div className="dropdown-menu artesianDropdown">
{props.dropDownValues.map(val => (
<span
key={val}
value={val}
className={`dropdown-item ${props.dropdownItemClass}`}
onClick={() => props.onClick(val)}
aria-hidden="true"
>
{val}
</span>
))
}
</div>
</div>
</div>
);
export default DateRange;
Wich looks like this on the page:
When I go to resize the webpage the arrow portion of the component spills out of its bootstrap container:
If I set display on the arrow to "none" you can see that the dropdown button itself with the text is resizing perfectly. It seems to be the dropdown arrow portion which is giving me trouble.
Any ideas on how to keep this guy firmly in his parent container?
Thanks!
Try adding this style to the component containing {props.selected} :
<button
type="button"
className="btn dropdown-width"
style={{
textOverflow: 'ellipsis',
overflow: 'hidden'
}}
>
{props.selected}
</button>
This should replace the end of the {props.selected} text by 3 dots and let the place to the dropdown button. (some adjustments may be needed depending on your dropdown-width class)

Categories