I trying to add new button which would add new tabs
I'm using react-tabs
which build tabs like this
<Tabs>
<TabList>
<Tab>Title 1</Tab>
<Tab>Title 2</Tab>
</TabList>
<TabPanel>
<h2>Any content 1</h2>
</TabPanel>
<TabPanel>
<h2>Any content 2</h2>
</TabPanel>
</Tabs>
so I need two loop one for the tab and another one for tabpanel
like this
<Fragment>
<Tabs>
<TabList>
{stats.map(({ figure = "", instructions = "" }, i) => {
<Tab>
<RichText
tagName="h2"
placeholder={__("Write Recipe title…")}
value={figure}
onChange={value => updateStatProp(i, "figure", value[0])}
/>
</Tab>;
})}
</TabList>
{stats.map(({ figure = "", instructions = "" }, i) => {
<TabPanel>
<RichText
tagName="div"
multiline="p"
className="steps"
placeholder={__("Write the instructions…")}
value={instructions}
onChange={value => updateStatProp(i, "instructions", value[0])}
/>
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.splice(i, 1);
setAttributes({ stats: newStats });
}}
>
<Dashicon icon="no-alt" />
</Button>
</TabPanel>;
})}
</Tabs>
<div style={{ textAlign: "center", padding: "8px 0" }}>
{stats.length < 5 && (
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.push({ figure: "", instructions: "" });
setAttributes({ stats: newStats });
}}
>
Add new stat
</Button>
)}
</div>
</Fragment>
The state is stats.
Each item in the stats array looks something like this { figure: '100k', instructions:'humans'}
The button "add new stat" just appends a new stat object to this array and calls setAttributes.
The remove button just removes the item at that index.
It doesn't give any errors but there isn't any tab added when I click on add new stat button
You are not returning anything from the function given to map. Either return it or change the function body ({}) to parens (()) to make the return implicit:
<Fragment>
<Tabs>
<TabList>
{stats.map(({ figure = "", instructions = "" }, i) => { // return statement
return <Tab>
<RichText
tagName="h2"
placeholder={__("Write Recipe title…")}
value={figure}
onChange={value => updateStatProp(i, "figure", value[0])}
/>
</Tab>;
})}
</TabList>
{stats.map(({ figure = "", instructions = "" }, i) => ( // implicit return
<TabPanel>
<RichText
tagName="div"
multiline="p"
className="steps"
placeholder={__("Write the instructions…")}
value={instructions}
onChange={value => updateStatProp(i, "instructions", value[0])}
/>
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.splice(i, 1);
setAttributes({ stats: newStats });
}}
>
<Dashicon icon="no-alt" />
</Button>
</TabPanel>;
))}
</Tabs>
<div style={{ textAlign: "center", padding: "8px 0" }}>
{stats.length < 5 && (
<Button
isLarge={true}
onClick={() => {
const newStats = _cloneDeep(stats);
newStats.push({ figure: "", instructions: "" });
setAttributes({ stats: newStats });
}}
>
Add new stat
</Button>
)}
</div>
</Fragment>
Related
I have text fields that are being added dynamically on button click and mapping the items depending on state variable [inputList]. The problem is when I'm trying to delete a field using the splice or filter option it is always deleting the last item but in the console it is showing that it has deleted the specific item that I wanted but in UI it's removing the last field no matter which field I delete. For example, if there are 3 fields named 'A', 'B'& 'C'. If I try to delete B, it is deleting B but not affecting in the UI. In the UI it is showing only the last item is deleted that is C has been deleted but in console B has been deleted.
Here is my code:
<FormControl style={{ display: "flex", flex: 1 }}>
<FormGroup>
{inputList.map((x, i) => {
return (
<div key={i}>
<div className={styles.options}>
{!distributePoints ? (
<FormControlLabel
key={i}
value={x.isCorrect}
control={<Checkbox />}
checked={x.isCorrect}
onChange={(e) => handleIsCorrect(e, i)}
/>
) : (
""
)}
<div className="editor">
<BubbleEditor
handleAnswer={handleInputChange}
index={i}
theme="bubble"
/>
</div>
{distributePoints ? (
<TextField
variant="standard"
name="points"
InputProps={{
disableUnderline: true,
type: "number",
}}
value={x.points}
onChange={(e) => handlePointChange(e, i)}
className={styles.inputCounter}
/>
) : (
""
)}
{inputList.length !== 1 && (
<IconButton
onClick={() => handleRemoveClick(i)}
className={styles.icon}
>
<DeleteIcon
sx={{ fontSize: 24, color: "#3699FF" }}
/>
</IconButton>
)}
</div>
{inputList.length - 1 === i && (
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<button
onClick={handleAddClick}
type="button"
className={styles.addButton}
>
<img src={PlusSign} alt="" /> Add another answer
</button>
<div className={styles.label}>
<Checkbox
checked={shuffle}
onChange={handleShuffle}
style={{ color: "#00A3FF" }}
/>
Shuffle answer
</div>
</div>
)}
</div>
);
})}
</FormGroup>
</FormControl>
Remove function:
const handleRemoveClick = (index) => {
const list = [...inputList];
list.splice(index, 1);
setInputList(list);
};
Add function:
const handleAddClick = () => {
setInputList([...inputList, { answer: "", isCorrect: false, points: 0 }]);
};
State array of object that is being used:
const [inputList, setInputList] = useState([
{ answer: "", isCorrect: false, points: 0 },
]);
While updating a state property, using its previous value, the callback argument should be used.
This is due to the possible asynchronous nature of state updates
var handleRemoveClick = (index)=> {
setInputList((list)=> (
list.filter((o,i)=> (index != i))
);
};
var handleAddClick = ()=> {
setInputList((list)=> (
list.concat({answer:'', isCorrect: false, points: 0})
);
};
I'm coding a infinite inputs component to add bill items in a page, so the user can add how many items row as he wants. But now I have a crucial problem, when I click on "cancel" button for clean all the inputs and show just 1 row of items I show the previous state of this first row. How can I clear all the inputs and then show the placeholder in the first row of items? Thanks
PrincipalComponent.tsx:
type BillingManualProps = LinkStateProps & LinkDispatchProps;
const BillingManual: React.FC<BillingManualProps> = ({...}) => {
return (
<>
<section className="entity__main-container">
<div className="billing-manual-items-to-bill">
<h2 className="billing-title-row">Indique los ítems a facturar:</h2>
<BillingItems
selectedShipper={selectedShipper}
selectedShipperSettings={selectedShipperSettings}
billItems={billItems}
setOpenBillItem={setOpenBillItem}
setUpdateBillItem={setUpdateBillItem}
setNewBillItem={setNewBillItem}
setRemoveBillItem={setRemoveBillItem}
/>
</div>
<div className="billing-manual-shadow">
{billItems && (
<IonLabel
className="shipper_document_add_new_document button-border"
onClick={() => setNewBillItem()}
>
+ NEW ITEM
</IonLabel>
)}
</div>
<div className="billing-manual__buttonsGroup">
<GalaxyButton
color="medium"
className="GalaxyButton"
onClick={() => {
resetPropsFn();
}}
>
CANCEL
</GalaxyButton>
</div>
</section>
</>
);
};
resetProps action:
export const resetPropsFn = () => {
return async (dispatch: Dispatch) => {
dispatch(billingManualSlice.actions.resetPropsFn());
};
};
resetProps Slice:
resetPropsFn: (state) => {
return {
...state,
isFetching: false,
selectedShipper: null,
selectedShipperSettings: null,
billItems: [
{
item: null,
amount: null,
},
],
};
},
Billin child component:
<div className="billing-manual-scroll">
{!!billItems &&
billItems.map((document, id) => {
return (
<div key={id} className="billing-manual-items-container">
<IonRow className="input-delete-container" key={id}>
<IonCol className="billing-manual-column" size="6">
<IonItem style={{ marginTop: 4 }}>
<IonLabel color="primary" position={'stacked'}>
Ítem {id + 1}*
</IonLabel>
<TextField
name={'item'}
defaultValue={null}
id="billing-item-input"
variant="standard"
fullWidth={true}
disabled={
!(!!selectedShipper && !!selectedShipperSettings)
}
placeholder={'Indique el ítem a facturar'}
value={document.item ? document.item : null}
onChange={(event: any) => {
setUpdateBillItem(
id,
ValuesType.ITEM,
event.target.value
);
}}
/>
</IonItem>
</IonCol>
<IonCol className="billing-manual-column" size="6">
<IonItem style={{ marginTop: 4 }}>
<IonLabel color="primary" position={'stacked'}>
Monto*
</IonLabel>
<TextField
name={'amount'}
defaultValue={null}
style={{ backgroundColor: '#F5F5F5' }}
fullWidth={true}
type={'number'}
disabled={
!(!!selectedShipper && !!selectedShipperSettings)
}
placeholder={'0.00'}
value={
!!selectedShipper && !!selectedShipperSettings
? document.amount
: null
}
onChange={(event: any) => {
setUpdateBillItem(
id,
ValuesType.AMOUNT,
event.target.value
);
}}
InputProps={{
type: 'search',
startAdornment: (
<InputAdornment position="start">
{' '}
<AttachMoney fontSize={'small'} />
</InputAdornment>
),
}}
/>
</IonItem>
</IonCol>
</IonRow>
</div>
);
})}
</div>
You can set all the input states/hooks to empty string after completing the desired action
export const MobileFAQ = ({ faqs = [], defaultOpen = false }) => {
const { isOn: open, toggle } = useToggle(defaultOpen);
const ToggleIcon = open ? MinusIcon24Gray : PlusIcon24Gray;
return (
<div className="p16">
<h4 className="section-text-5 mb16">Frequently asked questions</h4>
{faqs.map((u, index) => (
<>
<div className="faq-item" onClick={toggle}>
<span className="c-black-3 text-medium">{u.question}</span>
<div className="faq-toggle-icon-mobile"></div>
</div>
<Collapse in={open}>
<div className="faq-answer c-black-3 text-4">{u.ans}</div>
</Collapse>
{index !== faqs.length - 1 && (
<div
style={{ height: 1, width: '100%', backgroundColor: '#e6e6e6' }}
className="mt12 mb16"
/>
)}
</>
))}
</div>
);
};
I have created faq array which is showing question and answer on toggle but it get open every index which should be coming as index wise
So I have 4 panels inside this Collapse from ant design and all 4 of them share the same states.
Here's the code for the Collapse:
const CustomCollapse = (props) => {
const [disabled, setDisabled] = useState(true);
const [followed, setFollowed] = useState(false);
const [opened, setOpen] = useState(false);
let [key, setKey] = useState([props.keys]);
useEffect(() => {
setFollowed(props.isFollowed)
}, [props.isFollowed])
const handlePanel= () => setOpen(prev => !prev);
const handlePanelClose = () => props.setShow(prev => !prev);
const combineFunc = () =>{
handlePanel();
handlePanelClose();
}
return (
<StyledCollapse accordian activeKey={props.show ? key : []} onChange={combineFunc}>
<AntCollapse.Panel
{...props}
header={props.header}
showArrow={false}
bordered={false}
key={props.keys}
extra={
<span>
<span style={{float: 'right'}}>
{followed ? <img src={tickIcon} alt="" style={{height:'1.2em', marginLRight:'10px', width:'auto', objectFit:'contain'}} /> : ""}
</span>
<div className={styles.extraContainer}>
{
!opened && !followed && <div id={styles.themeBox}><p>+10</p></div> // show this box
}
{
opened && !followed && <img src={arrowDownIcon} alt="" style={{height:'1.2em', marginLRight:'10px', width:'auto', objectFit:'contain'}} /> // show this icon
}
</div>
</span>
}
>
{props.children}
</AntCollapse.Panel>
</StyledCollapse>
);
};
Here's the code in father component which uses Custom Collapse which is imported as AntCollapse:
<AntCollapse isFollowed={followed} show={show} keys="1" setShow={setShow} id={styles.telegramHeader1} header="Follow XXX on Telegram">
<Row type='flex' align='middle' justify='center'>
<Button href={links} target="_blank" style={buttonStyle1} onClick={() => setClicked(false)}>
<Icon type="link" style={{ color: '#fff' }} theme="outlined" />
Subscribe
</Button>
</Row>
<span className={styles.greyLine}> </span>
<Row type='flex' align='middle' justify='center'>
<Button onClick={() => {handleFollow(); handleShow(); }} style={buttonStyle2} disabled={clicked}>Continue</Button>
</Row>
</AntCollapse>
I have 4 similar AntCollapse and I only show 1 of them here cause all 4 of them use followed and show. And whenever I expand the panel and click continue, all 4 of them are set to followed and show. How do I change the states seperately?
class CriteriaSetValue extends Component {
state = {
count: 0,
tableName: "",
fieldName:"",
tables: [],
fields: [],
addLine: [],
addField: []
};
onSubmit = e => {
e.preventDefault();
const addField = this.state.addField;
const size = addField.length + 1;
addField.push(size);
this.setState({
addField
});
addNewLine = event => {
event.preventDefault();
const addLine = this.state.addLine;
const size = addLine.length + 1;
const nextLine = this.state.count + 1;
addLine.push(size);
this.setState({
count: nextLine,
addLine
});
};
render() {
const {showForm, tableName, fieldName } = this.state;
const { props, state } = this;
return (
<React.Fragment>
<div className="form-wrapper">
<div className="row">
<div className="col-10 container">
<form onSubmit={this.submit} className="card">
<div className="card-header">
<h3 className="card-title">
Criteria Set
{/* <Locale value="std.formupload" /> */}
</h3>
</div>
<div className="card-body">
<div className="row">
<div className="col-md-7 col-lg-8">
<div className="add-line">
<Button
icon
labelPosition="left"
onClick={this.addNewLine}
>
Add Line
<Icon name="plus" />
</Button>
{this.state.addLine.map(index => {
return (
<div
className="field-line"
style={{ marginTop: "30px" }}
key={index}
id={index}
>
<h4 className="field-button">Line {index}</h4>
<Button
className="field-button"
icon
onClick={this.toggleForm}
>
<Icon name="box" />
</Button>
</div>
);
})
}
{
this.state.addField.map(index => {
return (
<React.Fragment>
<div
className="field-button"
style={{
marginTop: "20px",
paddingLeft: "20px"
}}
key={index}
id={index}
>
<h4
className="field-button"
onclick={this.addCriteriaValue}
>
<span>
table.field
</span>
</h4>
<Button
className="field-button"
icon
onClick={this.toggleDelete}
>
<Icon name="delete calendar" />
</Button>
</div>
<br></br>
</React.Fragment>
);
})
}
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</React.Fragment>
);
}
};
This is kind of related to what I am trying to achieve:
https://codesandbox.io/s/3vqyo8xlx5
But I want the child to appear under the preference of where the "Add child" button is clicked, not on the last one.
I am working on making multiple fields to appear under each line when a button beside line1, line2 and so on is clicked, but currently, the field jump to last lines when a button is clicked, it is not appearing on the appropriate line.
I have been able to show lines when the "Add Line" button is clicked and I have also been able to show the field when the button beside "Line #1" is clicked.
I want the fields for "Line #1" to show under the line1 when the field button is clicked, the field for "Line #2" to show under the line2 when the field button beside it is clicked and so on
You can try something like this:
const AddButton = ({ label, onClick }) => (
<button onClick={onClick}>
{label} [+]
</button>
)
const FieldData = ({ label, criterias, onAdd, onDelete }) => (
<React.Fragment>
<div
className='field-button'
style={{
marginTop: '20px',
paddingLeft: '20px'
}}
>
<h4 className='field-button'>
<AddButton
label='Add criteria'
onClick={onAdd}
/>
<span>
{label}
</span>
</h4>
{criterias.map((item, idx) => (
<p key={idx}>{item.id}</p>
))}
<button
className='field-button'
onClick={onDelete}
>
Del [-]
</button>
</div>
<br />
</React.Fragment>
)
class App extends React.PureComponent {
state = {
lines: []
}
handleSubmit = e => {
e.preventDefault()
console.log('DATA TO SAVE ' + JSON.stringify(this.state.lines))
}
handleAddLine = event => {
event.preventDefault()
this.setState(prevState => ({
...prevState,
lines: [
...prevState.lines,
{
id: (prevState.lines.length + 1),
fields: []
}
]
}))
}
handleAddField = lineId => e => {
e.preventDefault()
this.setState(prevState => {
const newLines = [ ...prevState.lines ]
const curLine = newLines[newLines.findIndex(({ id }) => id === lineId)]
curLine.fields.push({
id: curLine.fields.length + 1,
criterias: []
})
return {
...prevState,
lines: newLines
}
})
}
handleAddCriteria = (lineId, fieldId) => event => {
event.preventDefault()
this.setState(prevState => {
const newLines = [ ...prevState.lines ]
const curLine = newLines[newLines.findIndex(({ id }) => id === lineId)]
const curField = curLine.fields[curLine.fields.findIndex(({ id }) => id === fieldId)]
curField.criterias.push({
id: curField.criterias.length + 1
})
return {
...prevState,
lines: newLines
}
})
}
handleDeleteField = (lineId, fieldId) => event => {
event.preventDefault()
this.setState(prevState => {
const newLines = [ ...prevState.lines ]
const curLine = newLines[newLines.findIndex(({ id }) => id === lineId)]
curLine.fields = curLine.fields.filter(item => item.id !== fieldId)
return {
...prevState,
lines: newLines
}
})
}
render() {
const { lines } = this.state
return (
<React.Fragment>
<div className='form-wrapper'>
<div className='row'>
<div className='col-10 container'>
<form
onSubmit={this.handleSubmit}
className='card'
>
<div className='card-header'>
<h3 className='card-title'>
Criteria Set
</h3>
</div>
<div className='card-body'>
<div className='row'>
<div className='col-md-7 col-lg-8'>
<div className='add-line'>
<AddButton
label='Add Line'
onClick={this.handleAddLine}
/>
{lines.map((line, idx) => {
return (
<React.Fragment key={idx}>
<div
className='field-line'
style={{ marginTop: '30px' }}
>
<h4 className='field-button'>
Line {idx+1}
</h4>
<AddButton
label='Add field'
onClick={this.handleAddField(line.id)}
/>
</div>
{line.fields.map((lField, idx) => (
<FieldData
label={idx+1}
criterias={lField.criterias}
onAdd={this.handleAddCriteria(line.id, lField.id)}
onDelete={this.handleDeleteField(line.id, lField.id)}
/>
))}
</React.Fragment>
)
})}
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</React.Fragment>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
)
<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>
<div id="app"></div>
I defined a new data structure for the state, so It is better to think about nested data, and I refactored the code a little.