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.
Related
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>
)
I have two component: 1:StudentList 2: Major in react and antd.
StudentList Component rendered a list of students.
Major Component made a list of majors that you can pick them. After selecting major, the selected major title display on the top of the students list. and the list will be filtered according to the selected major.
This is StudentList component contain Major component:
class StudentList extends Component {
render(){
return(
<>
<Major/>
<h5>20 student found in <a>selected major</a></h5>
<List>
//this is the list of students and is not related to this question
</List>
</>);
}
}
This is Major Component with a filter button to open the popover:
class Major extends Component {
render() {
return (
<Popover
trigger="click"
content={content} //list of majors
>
<Button>
<FilterOutlined /> Select major to filter
</Button>
</Popover>
);
}
}
When I click on the Select major to filter button, the popover open to select majors. I want to change the code in order to open this popover from two place:
1- click on Select major to filter button in the Major component
2- click on selected major in the title in StudentList component.
Notice: I want to open the same popover in the same place (similar to when I click on Select major to filter button)
Maybe it could handle with state and handleVisibleChange function. but I don't know how to handle it from 2 components. I glad to hearing your solutions.
You can use the visible and onVisibleChange property from Antd's tooltip because they are used by the PopOver as well. You can find an easy example from Andt how to control a PopOver by visible in the docs.
To get the button click you can use onClick from antd's Button Api.
The desired example using React Components:
class Major extends Component {
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.value !== prevProps.value) {
this.setState({ visible: this.props.value });
}
}
state = {
visible: false
};
hide = () => {
this.setState({
visible: false
});
};
handleVisibleChange = visible => {
this.setState({ visible });
// this.props.onChange(visible); // add me to open popover on every click on studenlist
};
render() {
return (
<Popover
trigger="click"
content={<a onClick={this.hide}>Close</a>}
visible={this.state.visible}
onVisibleChange={this.handleVisibleChange}
>
<Button>Select major to filter</Button>
</Popover>
);
}
}
class StudentList extends Component {
state = {
visible: false
};
onClick = () => {
this.setState({ visible: !this.state.visible });
};
render() {
return (
<>
{/* <Major value={this.state.visible} onChange={setVisible} /> */}
<Major value={this.state.visible} />
<h5>
20 student found in <a>selected major</a>
</h5>
<Button onClick={this.onClick}>Select major from Studenlist</Button>
</>
);
}
}
Component example as a CodeSandBox.
Here is a simple example for your request using react hooks and simple buttons to open the PopOver:
function Major({ value, onChange }) {
const [visible, setVisible] = useState(value);
useEffect(() => {
value && setVisible(value);
}, [value]);
const hide = () => setVisible(false);
const handleVisibleChange = visible => {
setVisible(visible);
onChange(visible);
};
return (
<Popover
trigger="click"
content={<a onClick={hide}>Close</a>}
visible={visible}
onVisibleChange={handleVisibleChange}
>
<Button>Select major to filter</Button>
</Popover>
);
}
function StudentList() {
const [visible, setVisible] = useState(false);
const onClick = () => {
setVisible(true);
};
return (
<>
<Major value={visible} onChange={setVisible} />
<h5>
20 student found in <a>selected major</a>
</h5>
<Button onClick={onClick}>Select major from Studenlist</Button>
</>
);
}
The depended working CodeSandBox.
Edit1: Added React Component example.
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.
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
);
I can't find in the documentations how to use optionRenderer prop with react-select async (Select.Async)
here is a question that already been answered, but the renderOptions is not being called at all
here is my a snippet of my code:
renderOption = (option) => {
return (
<div>
// for example:
{option.label}: {option.value}
</div>
)
}
<Select.Async
placeholder={placeholder}
loadOptions={(inputValue) => this.asyncLoadOptions(inputValue)}
isClearable
onChange={(value, e) => {
this.onDropDownFilterChange(value, e)
}}
onMenuScrollToBottom={this.fetchDropDownNextPage}
defaultOptions={defaultOptions}
defaultValue={defaultValue}
styles={this.props.hasError ? customStyles : undefined}
optionRenderer={this.renderOption}
/>
Here I want to keep the functioality and styles for each dropDown item as is (e.g onMouseOver and so on) I just want to format how the items are shown in the list, so is this the right way to do it? or there no other option than using the components prop.
I was able to achieve the formatting right using the components prop, but I lost the styles, and all the mouse events.
so for those who are looking for the answer, this is what I ended up using and it does the job: (unrelated code has been removed to keep the snippet concise)
import AsyncSelect from 'react-select/async';
import { components } from 'react-select';
const Option = props => {
return (
<components.Option {...props} >
{props.data.label}<br/>
<small style={{color: 'gray'}}>
{props.data.manufacturerName || 'Unknown'} | {props.data.assetGroupDescription || 'Unknown'}
</small>
</components.Option>
)};
class FilterDropDownMenu extends Component {
render() {
return (
<>
<label htmlFor={id}>{translate(placeholder)}</label>
<AsyncSelect
{...this.props}
isClearable
onChange={(value, e) => {
this.onDropDownFilterChange(value, e)
}}
onMenuScrollToBottom={this.fetchDropDownNextPage}
defaultOptions={defaultOptions}
defaultValue={defaultValue}
styles={hasError ? customStyles : undefined}
components={{ Option }}
/>
</>
)
}
}
This way, I get the formatting that I want, And I don't loose the built in functionality of react-select (mouse events and styling and so on).