React use refs on dynamic tab panel - reactjs

I am using React, material-ui and material-table.
I have a closable tab panel. And when I change tabs I do not unmount the corresponding panel component. I just hide it with css. Which means that when I use my material-table component inside multiple tabs and I want to access the current table ref I just get the table ref which is inside of the recently opened tab panel.
How can I update the ref to correspond the current panels table when I cange the tab?
As I think I should update this ref on tab change event. But how can I do that? I use functional components and useRef.
I tried to use useCallback with no luck
This is my parent component:
const MainTabPanel = ({
tabs,
activeTab,
setTabs,
setActiveTab
}) => {
const tableRef = useRef(null)
const handleTabClose = (event, tabId) => {
event.stopPropagation()
const activeTabIndex = tabs.length > 1 ? tabs.length - 2 : 0
setActiveTab(activeTabIndex)
setTabs([
...tabs.filter(tab => tab.id !== tabId)
])
}
const handleTabChange = (event, newValue) => {
setActiveTab(newValue)
}
return (
<div>
<TabsToolbar
tabs={tabs}
open={open}
activeTab={activeTab}
onTabClose={handleTabClose}
onTabChange={handleTabChange}
/>
<TableRefProvider value={tableRef}>
{tabs.map(({ component: Component, id }, index) => (
<Panel value={activeTab} index={index} key={id} padding={2}>
<Component />
</Panel>
)
)}
</TableRefProvider>
</div>
)
}
export default MainTabPanel

You can try to unmount the table and set the ref to the new table by using the given selected tab as key to the component.
This will force a rerender of the component on key/tab change and will reset the tableRef.
On a side note, why do you not unmount, but hide it with css?

Related

Defining Components in React

I am creating a react website in which many pages have a specific button that should look the same for all. Should this button be its own component? If so, how would I specify the onClick events to be different for each button if it is a component?
Yes, it should be its own component.
Create it in a separate file so you can import them where you need to use.
The component should receive a onClick prop that you pass to the internal button tag.
See this example: https://reactjs.org/docs/components-and-props.html#composing-components
export const Button = ({ label, onClick, disabled }) => {
return (
<button
onClick={onClick}
disabled={disabled}
>
{label}
</button>
)
}
and then you can export this component inside any of the components you want, and pass in values like onClick and label to make it more dynamic
export const DemoFunction () => {
const onClickHandler = () => {}
return (
<Button label="Click" onClick={onClickHandler} />
)
}

`Popover` (as React component) with `OverlayTrigger`

I'm trying to create rich React component as popover content.
If I use example with simple const popover (https://react-bootstrap.netlify.app/components/overlays/#examples-1) everything works fine.
Problem
But custom react component fails to position itself. It appears on top left of the screen
const MyPopover = React.forwardRef((props, ref) => {
return <Popover ref={ref} placement={"bottom"}>
<Popover.Header as="h3">
<Form.Control></Form.Control>
</Popover.Header>
<Popover.Body>
<strong>Holy guacamole!</strong> Check this info.
</Popover.Body>
</Popover>
})
const PopoverChooser = ({children, container}) => {
const _refTarget = useRef(null)
return <>
<OverlayTrigger
trigger="click"
placement="bottom"
overlay={<MyPopover ref={_refTarget}/>}
target={_refTarget.current}
>
{children}
</OverlayTrigger>
</>
}
export default PopoverChooser;
As you can see, I'v tried to use ref's, but it's doesn't help.
Question
How can it link popover to target button (in image as dropdown button and in code as {children}).
Or should I position MyPopover manually (by checking button ID and assigning position.. etc.?)
Completely different approach to dig in..?
Your approach to forward the ref was right. What you actually forgot is to also inject props. According to the documentation:
The and components do not position themselves.
Instead the (or ) components, inject ref and
style props.
https://react-bootstrap.netlify.app/components/overlays/#overview
So what you need to do is to spread the props like this:
const MyPopover = React.forwardRef((props, ref) => {
return (
<Popover ref={ref} {...props}>
https://codesandbox.io/s/trusting-sid-0050g9

React scrollIntoView() not working with ref

I have a react functional component defined as
export const NavMenuHorizontal: React.FunctionComponent<NavMenuProps> = (
props
) => {
const selectedItemRef = React.createRef<HTMLDivElement>();
React.useEffect(() => {
selectedItemRef.current?.scrollIntoView({
behavior: "smooth",
inline: "center",
})
});
return (
<List>
{map(...) => (
<div ref={isActive ? selectedItemRef : undefined}>
some text
</div>
)}
</List>
);
};
//isActive is true for only one item in the map
I am trying to make the selected list item scroll into view using a ref but unable to get the expected result. However, if I try to add debug breakpoint at the hook, it's working as expected and scrolling the element in viewport.
I also tried wrapping my scrollIntoView inside a setTimeout() which works but it scrolls it more than once and leads to a jarring effect.

Call a material ui dialog

How to call a Material UI Dialog during onClick on delete icon ie onClick={deletePlayer(id)} ?
I have added the Dialog.js under modal/Dialog and imported to Home component.
I have added a demo here
Short answer: Forked CodeSandbox with working dialog
Long answer:
First of all, you need to move the display/dismiss logic out of the AlertDialog component and into the component that actually triggers the display of the modal (in your case, the Home component). This means that you'll receive the open state and onClose handler as props (along with the playerId which will hold the ID of the player being targeted for deletion). So the signature of your dialog component becomes:
export default function AlertDialog({ open, onClose, playerId }) {
return (
<Dialog open={open} onClose={onClose} ...> ... </Dialog>
);
}
In Home, we add the logic to track and set the state of both the dialog open/closed status, and the ID of the player targeted for deletion. We do this through useState:
const [deleteDialog, setDeleteDialog] = useState(false);
const [playerId, setPlayerId] = useState("");
While you could have as many AlertDialog components as you have players by adding <AlertDialog /> inside your player map loop, it is redundant as you'll only ever have one modal active (by definition). So all you have to do is place a single instance of <AlertDialog /> in your Home component. A good convention is to place it before the closing encompassing tag:
return (
<div className="App">
.
.
.
<AlertDialog
open={deleteDialog}
onClose={() => setDeleteDialog(false)}
playerId={playerId}
/>
</div>
);
Finally, we deal with the handler responsible for displaying the modal, in your case deletePlayer. We have two things to do there: set the player ID targeted for deletion through the playerId state variable, and display the modal through the deleteDialog state variable:
const deletePlayer = id => e => {
setPlayerId(id);
setDeleteDialog(true);
};
Create a state in the Home component to handle the Dialog visibility, set the state on click and render the AlertDialog conditionally:
const [openDialog, setOpenDialog] = useState(false);
...
const deletePlayer = id => e => {
setOpenDialog(true);
};
...
return(
...
{openDialog && (
<AlertDialog isOpen={openDialog} setIsOpen={setOpenDialog} />
)}
Then in the AlertDialog component:
export default function AlertDialog(props) {
const { isOpen, setIsOpen } = props;
const handleClose = () => {
setIsOpen(false);
};
return (
<div>
<Dialog
open={isOpen}
onClose={handleClose}
...
Working Example:

How to pass state from parent to child in react?

How do I pass a state attribute from parent to child? In the following implementation, the Dropdown component has a state "isActive" and I want to access it in the Button component to attach propper styling to it. The Dropdown has to generic as it is supposed to take different sorts of buttons.
<Dropdown items="...">
<Button active ="false" />
</Dropdown>
Dropdwon.js
...
constructor(props){
super(props)
this.state = {
isActive: true,
}
}
render (){
return (
<div className={styles.toggle} onClick={(event) => this.showMenu(event)}>
{this.props.children} /* want to set active prop for the child button here */
</div>
);
}
...
You have two possibilities:
Lift your Dropdown state and keep it in its parent component;
Use useContext hook;
The first approach would be better, but it may not be good for your application (I cannot know that). Let me make an example for both cases.
This is an example where I've lifted the isActive state to the parent component.
const ParentComponent = () => {
const [isActive, setIsActive] = useState(false);
handleIsActiveChange = (newValue) => {
setIsActive(newValue);
}
<Dropdown isActive={isActive} setIsActive={handleIsActiveChange}>
<Button isActive={isActive} />
</Dropdown>
}
const Dropdown = props => {
// You can use `props.isActive` to know whether the dropdown is active or not.
// You can use `props.handleIsActiveChange` to update the `isActive` state.
}
const Button = props => {
// You can use `props.isActive` to know whether the dropdown is active or not.
}
Instead, this exploits the useContext API:
const dropdownContext = React.createContext(null);
const Dropdown = props => {
const [isActive, setIsActive] = useState(false);
return (
<dropdownContext.Provider value={{ isActive }}>
{props.children}
</dropdownContext.Provider>
);
}
const Button = props => {
const dropdownCtx = React.useContext(dropdownContext);
// You can use `dropdownCtx.isActive` to know whether the dropdown is active or not.
}
Aside from the answer I linked, there might be another way of achieving this that I didn't see mentioned.
You can send a function as a children element of your dropdown which will take isActive as a variable :
<Dropdown items="...">
{isActive => <Button active={isActive} />}
</Dropdown>
Then, is the render function, simply call the function and send your state value as a parameter :
render(){
return (
<div className={styles.toggle} onClick={(event) => this.showMenu(event)}>
{this.props.children(this.state.isActive)}
</div>
);
}
<Dropdown >
<Button isActive={this.state.isActive} />
</Dropdown>
In your button get it with this.props.isActive

Resources