Toggle a spinner on button click inside an antd table row - javascript

I have a table like this with a column that contains a button like this:
const columns = [
...
{
title: "button",
dataIndex: "key",
render: (text, record) => {
return (
<Button
icon={<DeleteOutlined />}
onClick={() => pendingToggle()}
></Button>
);
}
}
];
When you click on this button it should swap the button with <Spin /> from ant design only on that row it is clicked. I have written a piece of code but it doesn't work you can find it here !
How can I do it?

You need to use React states so that the DOM gets re-rendered.
To do so, you need a react component, something like:
function ButtonCell() {
const [pending, setPending] = useState(false);
return pending ? (
<Spin />
) : (
<Button icon={<DeleteOutlined />} onClick={() => setPending(true)}>
Delete
</Button>
);
}
In your table you can type:
{
title: "button",
dataIndex: "key",
render: () => {
return <ButtonCell />;
} // or just `render: ButtonCell`
}
Working example:

Related

Toggle different components from multiple buttons (nextjs)

I'm trying to build an component that render different components based on which button the user clicks.
this is what the "pages" are:
`
const navigation = [
{ name: "EOQ", return: "<EOQgraph />" },
{ name: "ROP", href: "<ROPgraph />" },
{ name: "Table", href: "<Table />" },
{ name: "ROC+SS", href: "<ROCSSgraph />" },
{ name: "Table+SS", href: "<TableSS />" },
]
`
This is the function and the UseState that needs to receive the key (Not all of the components are in yet):
`
const [toggle, setToggle] = useState('')
const wantedGraph = (value) => {
switch (value){
case "EOQ":
return setToggle(<EOQgraph />);
case "ROP":
return setToggle(<ROPgraph />);
`
And the return that uses a .map to display all the buttons:
`
return(
<div>
{navigation.map((item) => (
<button type="submit"
className={'btn btn-outline-dark btn-md'}
key={item.name}
onClick = {() => setToggle(wantedGraph(item.name))}
>
{item.name}
</button>
))}
<div>
{ toggle }
</div>
</div>
)
`
Currently nothing is showing inside of the component.
The page works perfectly and the buttons are there.
UseState is also working, but as soon as you press on a button, the component doesn't render.
I should add that I'm also using Graph.js and each component is in a different page and i'm importing all of them.
This is happening because you are setting toggle's value to undefined in your button's onClick handler. It is getting set to undefined because wantedGraph returns the value gotten from calling setToggle, which does not return anything, i.e. undefined.
You should either change wantedGraph to return the component you want, e.g.:
const wantedGraph = (value) => {
switch (value) {
case "EOQ":
return <EOQgraph />
case "ROP":
return <ROPgraph />
}
}
Or change the onClick handler to not call setToggle:
return (
<div>
{navigation.map((item) => (
<button type="submit"
className={'btn btn-outline-dark btn-md'}
key={item.name}
onClick={() => wantedGraph(item.name)}
>
{item.name}
</button>
))}
<div style={{ color: 'white' }}>
{toggle}
</div>
</div>
)

Call a function in React from child component

Inside a component, there is a mapping for creating multiple elements. Each element can be deleted if a button is clicked:
const ParentComponent = () => {
...
const [fields, setFields] = useState([{value: 'a', editable: false},
[{value: 'b', editable: true},
[{value: 'c', editable: false}]);
const handleRemoveField = (id) => {
const values = [...fields];
values.splice(id, 1);
setFields(values);
}
...
return (
...
{fields.map((field, id) => {
return (
<div>
...
<button onClick={() => handleRemoveField(id)}>delete</button>
</div>
);
})}
...
);
The above code is working fine. The problem comes up when the delete part must be done from a modal component instead of directly clicking the button.
So I created a new Modal component:
const DeleteModal = ({ isDeleteModalOpen, closeDeleteModal }) => {
return (
<Modal isOpen={isDeleteModalOpen}>
<ModalTitle handleClose={closeDeleteModal} />
<button onClick={deleteElement}> OK </button>
<button onClick={closeDeleteModal}> cancel </button>
</Modal>
);
};
And inside ParentComponent, DeleteModal was imported. When handleRemoveField is called, instead of directly deleting the element it is opening the modal.
What I don't know how to do is to delete de element when OK button from modal is clicked (deleteElement function from modal should do that).
Modified ParentComponent:
const ParentComponent = () => {
...
const [fields, setFields] = useState([{value: 'a', editable: false},
[{value: 'b', editable: true},
[{value: 'c', editable: false}]);
const handleRemoveField = (id) => { // modified the method to call modal
openDeleteModal(id);
};
// added for open/close modal
const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);
const openDeleteModal = () => {
setDeleteModalOpen(true);
};
const closeDeleteModal = () => setDeleteModalOpen(false);
...
return (
...
{fields.map((field, id) => {
return (
<div>
...
<button onClick={() => handleRemoveField(id)}>delete</button>
// imported modal here:
<DeleteModal
isDeleteModalOpen={isDeleteModalOpen}
closeDeleteModal={closeDeleteModal} />
</div>
);
})}
...
);
The cancel button is working, it closes the modal. The problem is with OK button that must delete the element.
How can that be done?
You can add a new prop to your Modal components, maybe something along the lines of onDelete. Than you can pass a method which deletes your element to the Modal like so:
<Modal
isDeleteModalOpen={isDeleteModalOpen}
closeDeleteModal={closeDeleteModal}
onDelete={() => deleteElement(id)}
/>
Inside your Modal component you can call your onDelete prop to call deleteElement(id):
const DeleteModal = ({ isDeleteModalOpen, closeDeleteModal, onDelete }) => {
return (
<Modal isOpen={isDeleteModalOpen}>
<ModalTitle handleClose={closeDeleteModal} />
<button onClick={onDelete}> OK </button>
<button onClick={closeDeleteModal}> cancel </button>
</Modal>
);
};
So long story short: You can pass down a method with your child component to call it from there.
You could save the ID that is going to be deleted before open modal, and use it to decide if modal is visible or not, as well.
const [idToDelete, setIdToDelete] = useState(null);
const handleRemoveField = (id) => {
setIdToDelete(id);
};
...
<DeleteModal isDeleteModalOpen={idToDelete !== null} closeDeleteModal={closeDeleteModal} />
you use an variable to save which item you are deleting
const [itemDel,setItemDel] = React.useState(null);
then, when click edit to show modal, you set that item to itemDel ( in handleRemoveField function)
something like
return (
<>
{fields.map((field, id) => {
return (
<div>
...
<button onClick={() => handleRemoveField(id)}>delete</button>
</div>
);
})}
{
!!itemDel&& <DeleteModal
isDeleteModalOpen={isDeleteModalOpen}
closeDeleteModal={closeDeleteModal} />
}
</>
);
and, when you done, or want to hide modal, just call setItemDel(null)

How can I avoid updating all instances of my function component with the useState hook in React.js?

TL;DR I am making a reusable Button function component. My useState() hook for the button label is updating every Button instance. How can I prevent this?
I am very new to React and building a Book Finder app in order to learn. So far my app has a BookList and a ReadingList. Each BookDetail in either list has a Button to add/remove that book from the ReadingList. The add/remove function works (phew), but using useState to update the Button's label updates every instance of the Button component, and not just the one that was clicked.
Buttons on books in the BookList start with label 'Add to Reading List', but if I click any of them, all of them update to 'Remove from Reading List'.
I've tried moving the logic around into the Button component or either List component but I just end up breaking the function.
App.js
function App() {
const books = useState([])
const [booksToRead, setBooksToRead] = useState([])
const [addRemove, setAddRemove] = useState(true)
const [label, setLabel] = useState('Add to Reading List')
function handleAddBook(book) {
const newID = book.title_id
if( (typeof booksToRead.find(x => x.title_id === newID)) == 'undefined' ) {
setBooksToRead([...booksToRead, book])
}
}
function handleRemoveBook(book) {
console.log(book)
const array = booksToRead
const index = array.indexOf(book)
const newArray = [...array.slice(0, index), ...array.slice(index +1)]
setBooksToRead(newArray)
}
function addOrRemove(book) {
if( addRemove ) {
handleAddBook(book)
setLabel('Remove from Reading List')
setAddRemove(false)
} else {
handleRemoveBook(book)
setLabel('Add to Reading List')
setAddRemove(true)
}
}
return (
<main>
<BookList books={books} handleAddBook={handleAddBook} addOrRemove={addOrRemove} label={label} />
<ReadingList booksToRead={booksToRead} handleRemoveBook={handleRemoveBook} />
</main>
);
}
export default App;
BookList.js
function BookList ({ book, label, handleAddBook, addOrRemove }) {
return (
<div className="booklist">
{BookData.map((book, index) => {
const onAddBook = () => addOrRemove(book)
return (
<div key={index} className="card">
<BookDetail key={book.title_id} book={book} />
<Button key={index + 'btn'} label={label} onClick={onAddBook} />
</div>
)
})}
</div>
)
}
export default BookList
And finally, Button.js
export default function Button({ styleClass, label, onClick }) {
return (
<button className='btn' onClick={(event) => onClick(event)}>
{label}
</button>
)
}
Unstyled example in codesandbox: https://codesandbox.io/s/cool-rgb-fksrp
Can you make these changes and let me know if there any progress:
<Button label={label} onClick={() => addOrRemove(book)} />
<button className='btn' onClick={onClick}>
It looks like that in button you are passing event instead of book as function parameter
As it is right now, you are declaring a single label and using that same one on all your book entries. This is why they all display the same label. You would need to keep track of the label of each book, for instance by keeping the label as a field in the book object.
For example:
const books = useState([{ label: 'Add to reading list', addRemove: true }])
And then:
function addOrRemove(book) {
if( book.addRemove ) {
handleAddBook(book)
book.label = 'Remove from Reading List'
book.addOrRemove = false
} else {
handleRemoveBook(book)
book.label = 'Add to Reading List'
book.addOrRemove = true
}
}
This way, each book has it's own label.

How to create a button to mimic link popover feature on gutenberg richtext editor

I need to create a button, that will generate a tag with data-footnote attribute and it will be editable, just like the link popover that comes when you click it. That's what I tried so far, but the popover doesn't shows at all, I need that this button works exactly like the link button but instead, it changes my data-note attribute with a textbox instead of that input that shows when you click the link button.
This code below is what I've done so far. The button is there, it worsk to create my tag exactly as I need it, but I'm having a hard time finding how to make the popover appear so I can inline edit that data attribute.
import { Fragment } from '#wordpress/element';
import { RichTextToolbarButton, RichTextShortcut } from '#wordpress/block-editor';
import {
toggleFormat,
registerFormatType,
} from '#wordpress/rich-text';
const name = `custom/eos-footnote`;
export const footnote = {
name,
title: 'Footnote',
tagName: 'span',
className: 'eos-footnote',
attributes: {
//'data-note': 'data-note',
},
edit( { isActive, value, onChange } ) {
const onToggle = () => {
onChange(
toggleFormat( value, {
type: name,
attributes: {
'data-note': 'Please type your note here',
},
} )
);
};
return (
<Fragment>
<RichTextShortcut
type="primary"
character="n"
onUse={ onToggle }
/>
<RichTextToolbarButton
icon="admin-post"
title={ 'Footnote' }
onClick={ onToggle }
isActive={ isActive }
shortcutType="primary"
shortcutCharacter="n"
/>
</Fragment>
);
},
};
function registerFormats () {
[
footnote,
].forEach( ( { name, ...settings } ) => registerFormatType( name, settings ) );
};
registerFormats();
[google](https://developer.wordpress.org/block-editor/components/popover/#top)
this link i found it helpful check it out
Render a Popover within the parent to which it should anchor:
import { Button, Popover } from '#wordpress/components';
import { withState } from '#wordpress/compose';
const MyPopover = withState( {
isVisible: false,
} )( ( { isVisible, setState } ) => {
const toggleVisible = () => {
setState( ( state ) => ( { isVisible: ! state.isVisible } ) );
};
return (
<Button isSecondary onClick={ toggleVisible }>
Toggle Popover!
{ isVisible && (
<Popover>
Popover is toggled!
</Popover>
) }
</Button>
);
} );
If a Popover is returned by your component, it will be shown. To hide the popover, simply omit it from your component’s render value.
If you want Popover elements to render to a specific location on the page to allow style cascade to take effect, you must render a Popover.Slot further up the element tree:
import { render } from '#wordpress/element';
import { Popover } from '#wordpress/components';
import Content from './Content';
const app = document.getElementById( 'app' );
render(
<div>
<Content />
<Popover.Slot />
</div>,
app
);

Hypertexting by pasting a link using a toolbar

I have to say I started Javascript and React this week so I am not really familiar with it yet or with anything in the front end.
I have a link button in side a toolbar. I want to be able to click it, opening a text box where I can write a link, and then the text is hypertexted with it. Just want to say that any tip is appreciated.
Something like the following pictures.
I have coded the toolbar already and am using the slate-react module for the Editor (the text editor used). I am trying to follow what was done in a GitHub example, which is not exactly the same.
So, in essence, it is a link component inside a toolbar, which is inside a "Tooltip" component (that contains the horizontal toolbar plus another vertical bar), which is inside the editor.
My question is: How do I use react and slate editor to tie the Links together in the toolbar? Does the Link component need a state and onChange function? How can I include the Link component in the toolbar (button group), alongside the other buttons within "const Marks"?
I get that these questions might be basic but I am a beginner and would appreciate explanation.
My created Link component can wrap and unwrap link. When clicked,
onClickLink = event => {
event.preventDefault()
const { value } = this.state
const hasLinks = this.hasLinks()
const change = value.change()
if (hasLinks) {
change.call(this.unwrapLink)
}
else
{
const href = window.prompt('Enter the URL of the link:')
change.call(this.wrapLink, href)
}
this.onChange(change)
}
The wrap, unwrap and hasLinks boolean
class Links extends React.Component {
onChange = ({ value }) => {
this.setState({ value })
}
wrapLink(change, href) {
change.wrapInline({
type: 'link',
data: { href },
})
change.moveToEnd() }
unwrapLink(change) {
change.unwrapInline('link') }
hasLinks = () => {
const { value } = this.state
return value.inlines.some(inline => inline.type == 'link')
}
To render it in the editor.
const renderNode = ({ children, node, attributes }) => {
switch (node.type) {
case 'link': {
const { data } = node
const href = data.get('href')
return (
<a {...attributes} href={href}>
{children}
</a>
)
}
The "Tooltip" component, holding MarkSelect (the horizontal toolbar like the one in the picures) and another vertical bar called NodeSelector.
function Tooltip({ onChange, value }: Props) {
return (
<Fragment>
<SelectionPlacement
value={value}
render={({ placement: { left, top, isActive } }) => (
<div
id=...
{
isActive,
},
)}
style={{ left, top }}
>
<NodeSelector onChange={onChange} value={value} />
<MarkSelector onChange={onChange} value={value} />
</div>
)}
/>
The MarkSelector and other Marks (buttons) in the button group.
const MarkSelector = function MarkSelector({ onChange, value }: Props) {
return (
<ButtonGroup className=...>
{Marks.map(({ tooltip, text, type }) => {
const isActive = value.activeMarks.some(mark => mark.type === type);
return (
<Tooltip key={type} title={tooltip}>
<Button
className={classNames({ 'secondary-color': isActive })}
onMouseDown={event => {
event.preventDefault();
const change = value.change().toggleMark(type);
onChange(change);
}}
size=...
style=...
}}
>
{text}
</Button>
</Tooltip>
);
})}
</ButtonGroup>
);
};
const Marks = [
{
type: BOLD,
text: <strong>B</strong>,
tooltip: (
<strong>
Bold
<div className=...</div>
</strong>
),
},
{
type: ITALIC,
text:...
The editor with the tooltip.
render() {
const { onChangeHandler, onKeyDown, value, readOnly } = this.props;
return (
<div
className=...
id=...
style=..
>
{!readOnly && (
<EditorTooltip value={value} onChange={onChangeHandler} />
)}
<SlateEditor
ref=...
className=...
placeholder=...
value={value}
plugins={plugins}
onChange={onChangeHandler}
onKeyDown={onKeyDown}
renderNode={renderNode}
renderMark={renderMark}
readOnly={readOnly}
/>
{!readOnly && <ClickablePadding onClick={this.focusAtEnd} grow />}
</div>
);
}
Although it is not a recommended way to manipulate DOM directly in data-driven frontend frameworks, but you could always get the HTML element of the link, and set its innerHTML (which is the hypertext) according to internal states. This is hacky but it might works.

Categories