I'm using React-Bootstrap Popover and I was wondering if there is any builtin property that I can add either to Popover itself or to OverlayTrigger so only one popover will display at a time.
You can try rootClose props which will trigger onHide when the user clicks outside the overlay. Please note that in this case onHide is mandatory. e.g:
const Example = React.createClass({
getInitialState() {
return { show: true };
},
toggle() {
this.setState({ show: !this.state.show });
},
render() {
return (
<div style={{ height: 100, position: 'relative' }}>
<Button ref="target" onClick={this.toggle}>
I am an Overlay target
</Button>
<Overlay
show={this.state.show}
onHide={() => this.setState({ show: false })}
placement="right"
container={this}
target={() => ReactDOM.findDOMNode(this.refs.target)}
rootClose
>
<CustomPopover />
</Overlay>
</div>
);
},
});
I managed to do this in a somewhat unconventional manner. You can create a class which tracks the handlers of all of your tooltips:
export class ToolTipController {
showHandlers = [];
addShowHandler = (handler) => {
this.showHandlers.push(handler);
};
setShowHandlerTrue = (handler) => {
this.showHandlers.forEach((showHandler) => {
if (showHandler !== handler) {
showHandler(false);
}
});
handler(true);
};
}
Then in your tooltip component:
const CustomToolTip = ({
children,
controller,
}: CustomToolTipProps) => {
const [showTip, setShowTip] = useState(false);
useEffect(() => {
if (!controller) return;
controller.addShowHandler(setShowTip);
}, []);
return (
<OverlayTrigger
onToggle={(nextShow) => {
if (!nextShow) return setShowTip(false);
controller ? controller.setShowHandlerTrue(setShowTip) : setShowTip(true);
}}
show={showTip}
overlay={(props: any) => <Overlay {...props}/>}
>
<div className={containerClassName}>{children}</div>
</OverlayTrigger>
);
};
It's not really a 'Reacty' solution but it works quite nicely. Note that the controller is completely optional here so if you wanted you could not pass that in and it would then behave like a normal popover allowing multiple tooltips at once.
Basically to use it you can create another component and instantiate a controller which you pass into CustomToolTip. Then for any tooltips which are rendered using that component, only 1 will appear at a time.
STEP 1: we declare a currentPopover variable that contain current popover id, so we are sure that there is only one popover at a time.
const [currentPopover, setCurrentPopover] = useState(null);
STEP 2: the OverlayTrigger from react-bootstrap has properties to set popover state manually. If the currentPopover variable is equal to popover id then we show the popover.
show={currentPopover === `${i}`}
STEP 3: the OverlayTrigger from react-bootstrap has properties to handle popover click manually. On click we update the currentPopover variable with the new id, except if we clicked on the current.
onToggle={() => {
if( currentPopover === `${i}` )
setCurrentPopover(null)
else
setCurrentPopover(`${i}`)
}}
RESULT:
const [currentPopover, setCurrentPopover] = useState(null);
<OverlayTrigger
trigger="click"
show={currentPopover == `${i}`}
onToggle={() => {
if( currentPopover == `${i}` )
setCurrentPopover(null)
else
setCurrentPopover(`${i}`)
}}
>
(I use ${i} as id cause my OverlayTrigger is in a loop where i is the index)
Related
I have a piece of code like this.
Modal.confirm({
tilte: "title",
content: (
<>
<input onChange={
() => this.setState({ A: true });
}></input>
this.state.A && <div>text</div>
</>
)
})
The purpose is that after the input changes, <div>text</div> will be displayed, but it is found that state A cannot be obtained in time after the change, resulting in the content in div not being displayed. How can I modify it? ?
Question:
You are using imperative (Modal.confirm) to display the Modal, and the state change does not trigger the Modal to re-render.
solution:
Modal can be displayed declaratively, for example:
class Test extends React.Component {
state = {
A: false,
visible: false
};
handleClick = () => {
this.setState({ visible: true });
};
render() {
const { visible } = this.state;
return (
<>
<Button onClick={this.handleClick}>Show Modal</Button>
<Modal
visible={visible}
onCancel={() => this.setState({ visible: false })}
>
<input onChange={() => this.setState({ A: true })}></input>
{this.state.A && <div>text</div>}
</Modal>
</>
);
}
}
Extract the content displayed by Modal.confirm into a component
class ConfirmContent extends React.Component {
state = {
A: false
};
render() {
return (
<>
<input onChange={() => this.setState({ A: true })}></input>
{this.state.A && <div>text</div>}
</>
);
}
}
//
Modal.confirm({
tilte: "title",
content: <ConfirmContent />
});
//
Root cause: The update granularity of React is a fiber tree, such an imperative method often recreates a fiber tree (internally it will call createRoot, or ReactDom.render), your question That's when the update operation is triggered with a fiber on fiber tree1 (the fiber tree you created yourself in App.tsx), not with fiber on fiber2 Update (the fiber tree you created with confirm), a component corresponds to a fiber, they belong to that fiber tree after they are passed a createRoot or ReactDom.renders
I'm trying to set the state from the parent component using a callback. This callback gets passed down to the child component that renders a material ui datatable. The callback responds onClick and passes some values to the callback. The problem is that setting the state with the values from the callback arguments doesn't work.
My assumption is that when the user clicks the button from the child component, it should invoke the callback function and pass the values I needed to set the state.
Parent Component:
export default function ViewJobs() {
const [type, setType] = useState('');
const [params, setParams] = useState({});
const callback = ({ cellValues, componentType, path }) => {
setType(componentType);
setParams(cellValues) // Sets the params with an object.
console.log(cellValues) // Displays the data I need in the console
history.push(path);
};
console.log(params) // Displays undefine in the console.
return(
<React.Fragment>
<TabPanel value={value} index={0} dir={theme.direction} >
<DataTable
jobs={job}
title='All'
parentCallback={callback}
/>
</TabPanel>
</React.Fragment>
);
}
Child Component
import React, { useEffect } from 'react';
export default function DataTable(props) {
const { jobs, parentCallback } = props;
const rows = jobs.payload;
const handleDiaryClick = (event, cellValues) => {
const params = {
cellValues,
componentType: 'diary',
path: "/view/jobs/diary"
};
parentCallback(params);
};
const renderDiaryElement = params => {
return (
<Button
variant="contained"
color="primary"
style={{ backgroundColor: "#000000" }}
onClick={(event) => {
handleDiaryClick(event, params);
}}
>
<MenuBookIcon />
</Button>
);
}
return (
<div
className={classes.root}
style={{ height: 400, width: '100%' }}
>
<DataGrid
rows={rows}
columns={columns}
pageSize={5}
//checkboxSelection
disableSelectionOnClick
/>
</div>
);
}
Since the state has been lifted up to the parent component, I'm under the impression that the code above should be working.
I tried to reproduce the issue but I couldn't replicate it.
Any advice or inputs are appreciated. Thanks.
After further checking on my codebase, I found that the history.push(path) located in my callback is causing the issue. I had to remove this line of code for it to work.
I have create a function which scrolls a ScrollView to a set position when it is called (near top). I would like for the function to be called from the react-navigation tab bar. Calling the function is easy, but I am struggling to get it to communicate with the scrollRef from the screen component.
Here's my snack: https://snack.expo.dev/#dazzerr/scroll-to-top-function
You'll find this function in App.js which is called when the tab bar is pressed:
const onTabPress = () => {
scrollRef.current?.scrollTo({ // how do I get ref={scrollRef} from component.js ScrollView?
y: 0,
animated: true,
});
};
and in component.js is the ScrollView in question:
<ScrollView
ref={scrollRef}
style={styles.container}
scrollEventThrottle={16}
>
{ScreenContent()}
</ScrollView>
How can I get the scrollRef from component.js called from inside the onTabPress function from app.js? 🤔
You'll need to pass the function through react-navigation setParams like this:
This goes inside your createBottomTabNavigator:
tabBarOnPress: (scene, jumpToIndex) => {
scene.navigation.state.params.scrollToTop(); // Here's the magic!
},
And this inside your component:
useEffect(() => {
props.navigation.setParams({
scrollToTop: () => {
onTabPress();
},
});
}, []);
const onTabPress = () => {
scrollRef.current?.scrollTo({
y: 600, // whatever number you want here
animated: true,
});
};
Of course don't forget to add the scrollRef to your ScrollView, and make sure that your ScrollView is from react-native and not react-navigation. You might also want to add conditionals inside the tabBarOnPress such as:
if (
isFocused &&
typeof route.routes[0]?.params?.onTabBarPress !== "function"
)
But that's your doing 👍
Here's a working snack: https://snack.expo.dev/#dazzerr/ontabpress-scrollto
The issue is keydown/keyup aren't working when mention list popup has scroll , i can scroll using mouse but keyup/keydown aren't making the scroll move to the right position
This can be achieved by custom entry Component ->
const entryComponent = (props:any) => {
const { mention, isFocused, searchValue, ...parentProps } = props;
const entryRef = React.useRef<HTMLDivElement>(null);
useEffect(() => {
if (isFocused) {
if (entryRef.current && entryRef.current.parentElement) {
entryRef.current.scrollIntoView({
block: 'nearest',
inline: 'center',
behavior: 'auto'
});
}}
}, [isFocused]);
return (
<>
<div
ref={entryRef}
role='option'
aria-selected={(isFocused ? 'true' : 'false')}
{...parentProps}>
<div className={'mentionStyle'}>
{mention.name}
</div>
</div>
</> );
};
I made a inline widget similar a placeholder (ckeditor4), but now I want to render a dropdown when the widget is selected to show options values to replace the placeholder. I trying use BalloonPanelView but no success until now, someone have a idea about how to make it?
this.editor.editing.view.document.on('click', (evt, data) => {
evt.stop();
const element = data.target;
if (element && element.hasClass('placeholder')) {
if (!element.getAttribute('data-is-fixed')) {
const balloonPanelView = new BalloonPanelView();
balloonPanelView.render();
['option1', 'option2', 'option3'].forEach((value) => {
const view = new View();
view.set({
label: value,
withText: true
});
balloonPanelView.content.add(view);
});
balloonPanelView.pin({
target: element
});
}
}
});
I found the solution using ContextualBalloon class:
import ContextualBalloon from "#ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon";
// Define ballon
const balloon = editor.plugins.get(ContextualBalloon);
const placeholderOptions = // Here I defined list with buttons '<li><button></li>'
// Finnaly render ballon
balloon.add({
view: placeholderOptions,
singleViewMode: true,
position: {
target: data.domTarget
}
});