In my HTML I have a lot of repeated elements with quite a few attributes each and most of them are the same. It's taking up a lot of space and I could clean up the code if I could just set the attributes as a variable and use that variable in all my elements. So is there a way to do this?
For example one of my elements look like this,
<button className='btn btn-secondary dropdown-toggle' style={{ margin: "5px" }} type='button' data-bs-toggle='dropdown' aria-expanded='false'>
Delete
</button>
And there are at least 10 other buttons with the exact same set of attributes and takes up almost 100 lines worth of space. Is there a way I can set the attributes in one place then apply it to every element to save space?
If you are reusing a collection of html attributes, here are two ways you could handle this:
Save the attributes into an object and spread the object onto each <button> that uses them.
Create a <CustomButton> component and encapsulate the shared attributes in the component. Because each attribute is exposed as a prop in the component interface, each prop/attribute has a default value but can be overwritten if necessary.
// #1
const defaultButtonProps = {
className: "btn btn-secondary dropdown-toggle",
style: { margin: "5px" },
type: "button",
"data-bs-toggle": "dropdown",
"aria-expanded": "false"
};
export default function App() {
return (
<div className="App">
<div>
{/* #1 */}
<button {...defaultButtonProps}>Delete</button>
<button {...defaultButtonProps}>Save</button>
</div>
<div>
{/* #2 */}
<CustomButton />
<CustomButton>Save</CustomButton>
</div>
</div>
);
}
// #2
const CustomButton = ({
className = "btn btn-secondary dropdown-toggle",
style = { margin: "5px" },
type = "button",
dataBsToggle = "dropdown",
ariaExpanded = "false",
children = "Delete"
}) => {
return (
<button
className={className}
style={style}
type={type}
data-bs-toggle={dataBsToggle}
aria-expanded={ariaExpanded}
>
{children}
</button>
);
};
Related
I had some help with a previous issue with my little project, but I have a new problem I can't seem to understand. My program takes an array of objects (call them cards), and displays an on-screen card for each element in the array. I have an edit button for each card, which should open the edit form for the chosen item, and pre-populate it with its current state - this all works.
I want to be able to edit the item, save it back in place into the array, and have that 'card' updated. This is the main component:
import React from "react";
import ReactFitText from "react-fittext";
import Editform from "./Editform";
function Displaycards({ lastid }) {
// dummy data hardcoded for now
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,
},
]);
const [showForm, setShowForm] = React.useState(false);
const [currentid, setCurrentid] = React.useState(0);
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);
setCurrentid(id);
}}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
</div>
</div>
);
})}
{showForm && (
<div>
<Editform cards={cards} setCards={setCards} id={currentid} />
</div>
)}
</div>
);
}
export default Displaycards;
and this is the Editform.js which it calls at the bottom. As far as I can tell I'm passing my array, setter function, and id of the card I want to edit:
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);
function saveChanges(cardtochange) {
setCards(
cards.map(
(
card // intention is map back over the original array, and if the id matches that
) =>
card.id === id // of the edited card, write the changed card back in at its ID
? {
id: id,
gamename: cardtochange.gamename,
maxplayers: cardtochange.maxplayers,
freespaces: cardtochange.freespaces,
tablenum: cardtochange.tablenum,
}
: card // ... or just write the original back in place.
)
);
}
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"
onClick={saveChanges(thisCard)} //remove to see edit form - leave in for perpetual loop.
>
Save changes
</button>
</div>
)}
</>
);
}
export default Editform;
If I comment out the onClick for the button, the page renders. If it's in there, the page gets stuck in an infinite loop that even React doesn't catch.
The way I'm trying to recreate my array is based on advice I've read here, when searching, which said to take the original array, rebuild it item-for-item unless the ID matched the one I want to change, and to then write the new version in.
I suspect there might be a way to do it with my setter function (setCards), and I know there's an onChange available in React, but I don't know a) how to get it to work, or b) how - or even if I need to - pass the changed array back to the calling component.
Your function is invoked directly upon components render:
{saveChanges(thisCard)}
Rename it to a callback style signature:
{() => saveChanges(thisCard)}
Also do add a jsfiddle/ runnable snippet for answerers to test ✌️.
Edit:
About the array of objects passing and updates, at your part the code is good where filter is used. We can apply idea of moving update logic to parent where data is located.
Now id + updated attributes could be passed to the update callback in child.
To give you hint, can use spread operator syntax to update items out of existing objects.
I have placed loader if the button is clicked the loader runs, but its mapped to get value from api so if i click one button the loader runs for all button, how to do it only for corresponding element
my loader is:
<button id="Addlist" onClick={() => this.onAddProvider(providerList.id)} className="btn info"> {this.state.loading ? (
<div className="search-loader">
<Loader
visible={this.state.loading}
type="Oval"
color="#092b50"
height={50}
width={50}
/>
</div>
) : "Add to List"}</button>
As the button is mapped in loop loader runs for all button if i click one button,
eg:
You are setting state.loading to true when a button is clicked, I suppose. But in your Loader component you are using that one state.loading variable for each of the buttons and whichever button is clicked, each of the loaders receive the true value for visible.
A solution would be to store the id of the provider in state.providerIdLoading if the provider is loading and for the condition of visible in the Loader you could place state.providerIdLoading === providerList.id. When the provider is finished loading you could set the value to false or whatever is not a providerId.
I think you should change your loading state to something like
const [loading, setLoading] = useState({providerID1: false, providerID2: false, ...})
and your Loader condition will change to
<button id="Addlist" onClick={() => this.onAddProvider(providerList.id)} className="btn info"> {this.state.loading[providerList.id] ? (
<div className="search-loader">
<Loader
visible={this.state.loading[providerList.id]}
type="Oval"
color="#092b50"
height={50}
width={50}
/>
</div>
) : "Add to List"}</button>
Here you go with a solution
this.state = {
loading: ""
};
onAddProvider = id => {
this.setState({ loading: id });
}
<button id="Addlist" onClick={() => this.onAddProvider(providerList.id)} className="btn info">
{
this.state.loading === providerList.id ? (
<div className="search-loader">
<Loader
visible={this.state.loading === providerList.id}
type="Oval"
color="#092b50"
height={50}
width={50}
/>
</div>
) : "Add to List"
}
</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Instead of having loading as boolean, please use loading as id and match with the current id.
Once loading is done then reset the value to empty string like
this.setState({ loading: "" });
I know this might have some similar questions but I don't seem to able to find the solution for my situation.
I have a form that will be submitted with the content of the child component, the child component is appended onClick and can be appended infinitely. How can I get the value from all the child component, and to post it.
This is index.js
class ListOfProducts extends React.Component {
constructor()
{
super();
this.appendChild = this.appendChild.bind(this);
this.state = {
children: [],
}
}
appendChild() {
this.setState({
children: [
...this.state.children, <NewComponent/>
]
});
}
render() {
return (
<form>
<div>
<pre><h2 className="h2"> Sales Order</h2></pre>
<div className="box" style={{height: '520px', width: '1300px', position: 'relative', overflow: 'auto', padding: '0'}}>
<div style={{height: '1000px', width: '1000px', padding: '10px'}}>
<div>
{this.state.children.map(child => child )}
</div>
</div>
</div>
<button className="addbut" onClick={() => this.appendChild()}>Add Items</button>
</div>
</form>
)
}
}
This is partial code of NewComponent.JS
<select
name="sel"
className="sel"
value={this.state.selecteditems}
onChange={(e) =>
this.setState({selecteditems: e.target.value})}
>
{this.state.data.map(item =>
<option key={item.productID} value={item.unitPrice}>
{item.itemName}
</option>
)}
</select>
{/*unit price*/}
<p>Unit Price: RM {this.state.selecteditems} </p>
{this.state.selecteditems.length ? (
<p>Quantity: </p>
) : null }
{/*button to add quantity*/}
{this.state.selecteditems.length ? (
<button onClick={this.addPro}> + </button>
) : null }
{/*textbox for quantity*/}
{this.state.selecteditems.length ? (
<input type="text" ref="quan" placeholder="Quantity"
value={this.state.quantity}
onChange={(e) =>
this.setState({quantity: e.target.value})}
>
</input>
) : null }
{/*button to decrease quantity}*/}
{this.state.selecteditems.length ? (
<button onClick={this.decPro}> - </button>
) : null }
{/*subtotal*/}
{this.state.selecteditems.length ? (
<p>Sub Total: RM {this.state.subtot} </p>
) : null }
Thanks in advance!
Quick and dumb: add callback like this
<NewComponent onChange={this.onNewComponentChange}/>
(and implement calling of this onChange callback at every change at NewComponent of course)
There are two ways I can think of getting the value from the child component -
Have a state management system (something like redux) which can actually store the data of all the child components. As and when the child component's data changes, it should be synced to the store and the same can be used by the parent to post the data on submit.
Assign ref to each of the child component when it gets appended. save those ref values in a array. Iterate through those references on submit of the form and call some specific getter function of child component to give you its data.
Preferred way is the first method.
I have this component form react-tooltip where I pass some props and it creates the tooltip. I want the place of the tooltip to be "top" on default, but when I pass the props to be in a different place, to change it.
class Tooltip extends PureComponent {
render() {
const { text, element, type, place, className } = this.props;
return (
<div data-tip={text} className="m0 p0">
{element}
<ReactTooltip
type={type}
place={place}
effect="solid"
className={className}
html
/>
</div>
);
}
}
Tooltip.defaultProps = {
type: 'info',
place: 'top',
className: 'tooltip-top',
};
Tooltip.propTypes = {
text: PropTypes.string.isRequired,
element: PropTypes.element.isRequired,
type: PropTypes.string,
place: PropTypes.sring,
className: PropTypes.sring,
};
export default Tooltip;
Then I have this other component where I pass some props to the Tooltip component and I just want this only component to be placed on the bottom.
<Tooltip
type="warning"
place="bottom"
className="tooltip-bottom"
text={
'Ingrese los datos de la información financiera histórica de su compañía en la plantilla'
}
element={
<div className={`center mt3 ${styles.optionButton}`}>
<NavLink
className="btn btn-primary"
to={`${path}/manual-upload`}
>Continuar</NavLink>
</div>
}
/>
The problem is that is rendering in the bottom but also on the top. How can I make this to only appear on the bottom (in this component and the rest of the tooltips on the top). Thanks ;)
If you use <ReactTooltip /> inside a loop then you will have to set a data-for and id for each one.
const Tooltip = ({ children, data }) => {
const [randomID, setRandomID] = useState(String(Math.random()))
return (
<>
<div data-tip={data} data-for={randomID}>{children}</div>
<ReactTooltip id={randomID} effect='solid' />
</>
)
}
export default Tooltip
I ran into this issue today as well, I was able to get it resolved, this might not be relevant to you anymore, but for people looking:
I had this issue with data-for and id as well, the solution for me
was to set a more unique identifier/a combination of a word and a
variable that I was getting from the parent component (i.e.
id=`tooltip-${parent.id}`).
Heres the same
issue.
Button component which renders a button based on prop dataSrc send by parent App.
export default class Button extends React.Component {
constructor(props) {
super(props);
}
render(){
return(
<div>
{this.props.dataSrc.map((element, index) => {
return <button
key={index}
className={`btn btn-default ${element.class} ${element.secondary ? "btn-secondary" : ""}`}
type="button"
disabled={element.disabled}>
{element.label}
</button>
})}
</div>
);
}
}
Parent component App sends dataSrc to Button.
class App extends React.Component {
constructor(props) {
super(props);
}
render(){
const btnSrc = [
{
"label":"Save",
"class":"user_information_save",
"disabled":false
},
{
"label":"Cancel",
"class":"userCancel",
"disabled":false
}
]
return <Button dataSrc={btnSrc} />
}
}
Now everything is fine. Here comes the scenario, the btnSrc in App(parent) component will be look alike:
const btnSrc = [
{
"label":"Save",
"class":"user_information_save",
"disabled":false
},
{
"label":"Cancel",
"class":"userCancel",
"disabled":false,
"data-id": "someID", // this will be dynamic
"name": "someName" // this will be dynamic
"onClick": this.someFunctionaName // this will be dynamic
}
]
Now the src is changed, but little confused in Button component to render those dynamic data added recently.
export default class Button extends React.Component {
constructor(props) {
super(props);
}
render(){
return(
<div>
{this.props.dataSrc.map((element, index) => {
return <button
key={index}
className={`btn btn-default ${element.class} ${element.secondary ? "btn-secondary" : ""}`}
type="button"
disabled={element.disabled}
//here i want to have those dynamic data "data-id": "someID", "name": "someName" here
// and i want to add onClick also here which will be loaded from btnSrc
>
{element.label}
</button>
})}
</div>
);
}
}
How can i add those dynamic custom object values to existing Button component and I do not know how to bind events to in .map too. Code Demo
Any help would be helpful.
I think, there are two solutions possible:
1- Use Spread syntax ..., and pass put all the passed values on button, by this was you don't need define each property separately. Like this:
return <button
key={index}
className={`btn btn-default ${element.class} ${element.secondary ? "btn-secondary" : ""}`}
type="button"
{...element}
>
{element.label}
</button>
Now if you pass onClick then only it will be assigned otherwise not.
2- Use destructuring and assign the default value if not passed from the parent.
Like this:
{this.props.dataSrc.map((element, index) => {
const {onClick=null, dataGrid=''} = element;
return <button
key={index}
className={`btn btn-default ${element.class} ${element.secondary ? "btn-secondary" : ""}`}
type="button"
disabled={element.disabled}
onClick={onClick}
data-grid={dataGrid}
>
{element.label}
</button>
})}
Working Example.