I was wondering what would be best way to open a react-bootstrap modal from sidebar that is in another nested component?
To clarify a bit I have a modal an it is rendered in the component which lists items and it is used for edit them when you click one of the items. What i would like to do is when user clicks on the button in the sidebar that it opens that modal to add a item to the list (change from edit to add).
At first i was thinking to move the modal to parent of the sidebar but the problem is i would have to pass props 4 times to get to the list, and that could be confusing to someone who will later edit this code.
Next i taught about the context and sure that could work but to make a global state just for that it loses its purpose.
I taught about using refs but i am not sure on how to implement them.
And last thing what i taught about was just render the modal as a new component inside the sidebar but it wont change much since i want to update the list once user has added the item and i would be in the same spot.
And i would like to avoid directly accessing DOM with id (if possible), because i would like to find a "react way" of doing this.
For example (just to visualize not the actual code)
<Root>
<Component1>
<Component2>
<SideBar>
<Button onClick={setShowModal(true)}> <!-- click here -->
</SideBar>
</Component2>
<Component3>
<Component4>
<Modal show={showModal}/> <!-- open modal here -->
</Component4>
</Component3>
</Component1>
</Root>
I would just like a hint on how to approach this and what would be the best way to do this.
The most "React" way would be to store the state on the component that is the first common ancestor of the button and the modal. The downside to this approach, as you mentioned, is that you will have to pass this state down. I think this would still be a clean approach.
Another thing you could consider for this case is an event emitter. You have the Modal listen to an event to change its "open" state:
import EventEmitter from 'EventEmitter';
const eventEmitter = new EventEmitter();
export default function Modal() {
const [open, setOpen] = React.useState(false);
const handleToggleModal = () => {
setOpen(!open);
}
React.useEffect(() => {
eventEmitter.addListener('toggle-modal', handleToggleModal)
return () => {
eventEmitter.removeListener('toggle-modal', handleToggleModal);
}
}, [])
// ...
}
In the component that opens/closes the modal, you will then have to emit the corresponding event:
eventEmitter.emit('toggle-modal');
Related
I have modal screen (using react-bootstrap), on modal screen i have multiple overlays (popup menus) linked to items. These overlays has inputs, and when i click on input it immediately loses focus. I cant figure out whats wrong, because another one popup menu, that i have on normal screen, not modal, works fine. Tried to set autofocus, but it immediately loses too.
I wrote example, https://codesandbox.io/s/rkemy
I think it is somehow connected with popper, because bootstrap overlay uses it, dont know where to dig
The fix provided in the other response is a workaround that doesn't fix the real cause of the issue.
The issue is caused by an internal logic of the Modal component of react-overlay library that is a dependency library of react-bootstrap.
Specifically, the issue is caused by code listed below
const handleEnforceFocus = useEventCallback(() => {
if (!enforceFocus || !isMounted() || !modal.isTopModal()) {
return;
}
const currentActiveElement = activeElement();
if (
modal.dialog &&
currentActiveElement &&
!contains(modal.dialog, currentActiveElement)
) {
modal.dialog.focus();
}
});
that enforce the focus on the first modal open as soon as that modal lose the focus, like when you move the focus on the input.
In order to solve the issue, you have to pass the enforceFocus={false} to your Modal component.
The documentation of the API can be found here: https://react-bootstrap.github.io/react-overlays/api/Modal#enforceFocus
As the docs says:
Generally this should never be set to false as it makes the Modal less accessible to assistive technologies, like screen readers. but in your scenario this is a need to work properly.
Solution is to wrap Overlay in container:
import React from "react";
import { Overlay } from "react-bootstrap";
import { X } from "react-bootstrap-icons";
export const PopupMenuWrapper = (props) => {
const { target, title, show, onClose, children } = props;
const ref = React.useRef(null);
return (
<div ref={ref}>
<Overlay
container = {ref.current}
target={target.current}
show={show}
placement="bottom-start"
rootClose={true}
onHide={onClose}
>
...
</div>
...
I am creating a react project where I have a button in my home page and when I click it, it should open up a new page that contains a form to fill. Because it is a form and it should only appear after clicking the button on the home page, I don't want to use react router because I don't want the user to just type 'mywebsite.com/form'. I also want the functionality that, when the user submits the form, the page then goes back to the homepage and the data from the form should be available in the home page. For example, if the form had text fields, lists, date pickers, etc. I want all that data in the homepage after the user submits the form.
P.S. I am using material-ui components for my whole app so the text fields, datepickers, etc. are all mui components, so the data fetching from these components has to be according to that.
In your Home component do a conditional render based on if the button was clicked. If it was, render your Form component. Ideally your Home component should be your smart component and the Form should be a dumb component. Meaning that Home should manage all the state and Form is purely just for visuals(UI).
import React, {useState, useEffect} from 'react';
import myForm from './myForm';
const Home = () => {
const [isBtnClicked, setIsBtnClicked] = useState(false);
const [formData, setFormData] = useState(undefined);
useEffect(() => {
if(formData){
setIsBtnClicked(false); // or you can pass this setter to form component and set to false when form is submitted.
}
},[formData])
return (
<>
{isBtnClicked
? <myForm setFormData={setFormData} />
: (<h2>Home With Button</h2>
<Button variant="contained" onClick={() => setIsBtnClicked(true)})
}
};
import React from 'react';
const myForm = ({setFormData}) => {
//assuming you have refs to all inputs
const handleFormSubmit = (e) => {
e.preventDefault();
// construct form data and call setFormData()
}
return (
<>
<form onSubmit={handleFormSubmit()}>
...
</form>
</>
};
Since you are using material-ui, I would recommend you use one of their Dialog components. In case you want to make it look like a completely different page opens up to fill the form, you could use their full-screen dialog component. Build your form within this component and change the props to your liking. Conditionally render the form when the user clicks a button on your home page.
Use the fullscreen prop from the Dialog API and add others that you need.
If i can give my opinion, i think you can use NextJS with server side rendering for this. NextJS provides a structured folder called "pages". You can add your page to this. The redirecting can also be done using router in next package. An easy way without any need for react router. I used to use react router until i found out about this.
I need to show/hide a modal based on user interaction (ie - a button press) in a component which is neither a parent or child of the modal itself. I'm currently trying to do so by passing the modal as a prop to the modal controller, but the following errors are thrown depending on which method I call:
TypeError: modal.setNativeProps is not a function
TypeError: modal.setState is not a function
Is there a way to show the modal given how this is structured?
import Modal from 'react-native-modal'
const modalRef = React.createRef();
const modal = <Modal ref={modalRef} isVisible={false}>
<ModalController modal={modalRef} />
export const ModalController = ({modal}) => {
function onButtonPress(){
modal.setState({isVisible:true})
modal.setNativeProps({isVisible:true})
}
return (
<Button title='Show Modal' onPress={onButtonPress()} />
)
}
Ciao, in case there is no parent/child relation between components, the only way I found to pass/set data from one component to another is use a global state manager like react-redux. With redux you can easly define a global state and set it from component that fire modal open/close. Modal component reads this state and open/close itself.
So I think you've gotten a little confused.
Firstly, remember that everything inside ModalController is going to execute on every render. Your function onButtonPress will be created every render (this is fine), but you are actually calling that function when you pass it to onPress render . This means you're executing onButtonPress on every render, which is probably not what you want.
This is an easy fix - you just remove the () so it's just onPress={onButtonPress}. Now it'll only trigger when the button is pressed.
More fundamentally, the solution to your problem is much simpler than what you've done in your code. Generally 'refs' are only used in special cases where you really want to tell your components what to do (like telling a ScrollView to scroll to a particular position, or telling an input to focus so the keyboard shows). If you're using a ref it should be very intentional.
So a simple solution to have a component with a button that shows a modal could look like:
import React, {useState} from 'react';
import {View, Button, Text} from 'react-native';
import Modal from 'react-native-modal';
export const ModalController = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
function onButtonPress() {
setIsModalVisible(true);
}
return (
<View>
<Button title='Show Modal' onPress={onButtonPress} />
{isModalVisible && <MyModal />}
</View>
);
};
const MyModal = () => (
<Modal>
<Text>Hey I am a modal</Text>
</Modal>
);
Notice the use of useState. This is how you hold 'state' in a functional component like this (as opposed to a class component where you would use setState.
I hope this helps. Let me know if you have questions!
Hi kind of new to coding and React (~3 weeks in) so if I asked this question in a weird way please let me know and I will try to clarify as best as I can.
I am working on these two components and I wanted to pass data that is generated dynamically from COMPONENT1 to COMPONENT2 (see code below).
In COMPONENT1 I am using map to dynamically display an array of buttons. Then using JSX to dynamically add the text in based on the data of an array of objects that was defined. This works fine but what I wanted to do is to take some of the data that was dynamically rendered and pass it on to COMPONENT2 i.e. so each button can send there particular data down to COMPONENT2.
In particular, I want to be able to send obj.text dynamically to COMPONENT2 based on which button I press.
COMPONENT 1
export default function Button(props){
const arrObj = [
{ id: '0', var: 'varString1', text: 'textString1'},
{ id: '1', var: 'varString2', text: 'textString2'}
]
return (
<div>
{arrObj.map( (item) =>
<div key={obj.id}>
<Icon> {`${obj.var}`}<span> {`${obj.text`}</span></Icon>
<Button>{'add something'}</Button>
</div>
}
COMPONENT 2
const Style = (props) => {
return(
<div>
<h1>{`Please ${props.ojb.text}`}</h1>
<Button> {`Please ${props.ojb.text}`} </Button>
</div>
)
Quick example of what I am expecting:
COMPONET1:
<Icon>{refresh}<span> Refresh the page </span>
<Button onClick=(send "Refresh the page to COMPONENT2")></Button>
COMPONET2:
<h1> Please Refresh the Page </h1>
<Button> Please Refresh the Page </Button>
NOTE: I am using Material-UI to style theses components if that helps.
Again please let me know if there is anything that is confusing about this and if there is anything I can do to clarify. Also, in terms of state management I would prefer not to use redux but I am willing to take feedback on any of this.
If The COMPONENT2 is a child component of COMPONENT1:
Save the clicked button value obj.text to the state of COMPONENT1
and pass it to COMPONENT2, you can add a function to change that state
object on the button's onClick event.
If COMPONENT1 and COMPONENT2 are siblings:
Make a function in their parent component to fetch the value from COMPONENT1 using props and pass it to COMPONENT2 from the parent.
Use Context API or a store. (That would be an overkill if your scenario is simple)
And most importantly Welcome to React and Happy coding! :)
I am adding the icon button in form of the start from and I want to fill it on click with solid color.
import StarIcon from '#material-ui/icons/StarBorder'
...
<IconButton><StarIcon className={classes.favoricon}/></IconButton>
I had a look at icon implementation, could not find a class that would control it (check code here). I could create a separate svg, but maybe there is a better option
I am not sure if there is a better way.
Create a stateon the component and modify the state whenever the Component is clicked.
Use onClick function to toggle the state which changes the Component from StarIcon to Star and vice-versa.
handleChange = () => {
const value = this.state.value;
this.setState((prev) => ({value: !prev.value}));
};