For some reason the modal is rendered more than one time, sometimes 2 or 3. Then, after a few seconds the aditionals modals are removed automatically.
The modal is opened by a route so I'm doing somethig like this:
const ModalWrapper = (props) => {
return (
<Modal
show={props.showModal}
onHide={props.hide}
>
...
</Modal>
);
};
class ComponentPage extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: true,
};
}
#autobind
closeModal() {
this.props.history.goBack();
this.setState({showModal: false});
}
render() {
return (
<ModalWrapper
{...this.state}
hide={this.closeModal}
/>
);
}
}
Usually this happens when the Modal is inside a wrapper, for example:
const ChildrenWrapper = ({children}) => {
return <div>{children}</div>
}
And the modal is a child in the wrapper:
const ModalWrapped= ({}) => {
return <ChildrenWrapper>
<Modal show={true}>some content</Modal>
</ChildrenWrapper>
}
Than the App :
const App = () => {
return <ModalWrapped/>
}
The result is that the instance of the Modal is rendered 2 times in the virtual dom.
I got the same issue and i opened an issue on GitHub:
https://github.com/react-bootstrap/react-bootstrap/issues/2730
Related
I am trying to toggle a modal from separate components. the first most common component is my app.tsx so i set the state in that file.
type TokenUpdateType = {
sessionToken: string | undefined | null,
createActive: boolean
}
export default class App extends Component<{}, TokenUpdateType> {
constructor(props: TokenUpdateType) {
super(props)
this.state = {
sessionToken: undefined,
createActive: false
}
...
toggleModal = () => {
this.setState({createActive: !this.state.createActive})
}
return
<Home isOpen={this.state.createActive} toggleModal={this.toggleModal} />
my home component takes these props and passes again to another component
type AuthProps = {
isOpen: boolean
toggleModal: () => void
...
}
const Home = (props: AuthProps) => {
return(
<>
<Sidebar sessionToken={props.sessionToken} toggleModal={props.toggleModal}
<ChannelEntryModalDisplay sessionToken={props.sessionToken} isOpen={props.isOpen} toggleModal={props.toggleModal}/>
</>
)
}
isOpen gets passes to my modal component and is used in this component
type AuthProps = {
isOpen: boolean
toggleModal: () => void
...
}
const ChannelEntryModalDisplay = (props: AuthProps) => {
return(
<div>
<Modal show={props.isOpen}>
<ChannelEntry sessionToken={props.sessionToken}/>
<Button className='button' type='button' outline onClick={props.toggleModal}>close</Button>
</Modal>
</div>
)
}
my modal is not showing even when i set createactive to true. i believe i may be passing props incorrectly but im not sure what i am doing incorrectly. i appreciate any feedback.
try to create a new state from the props:
const [createActive, setCreateActive] = useState<boolean>()
constructor(props: TokenUpdateType)
{
super(props)
setCreateActive(props.createActive)
}
useEffect(() => {
setCreateActive(props.createActive) // update the state when props changes
}, [props])
...
toggleModal = () => {
this.setCreateActive(!createActive)
}
<Home isOpen={createActive} toggleModal={this.toggleModal} />
I have a registration view where in my table i have command to show modal with confirmation:
(...)
render: (rowData) => (
<button
onClick={() => RenderModals(rowData, 'DELETE_USER_MODAL')}
>
Remove
</button>
),
(...)
My RenderModals function looks like this:
type RenderModalProps = {
data: any;
modalCommand: string;
};
export const RenderModals = (data, modalCommand) => {
console.log(data);
switch (modalCommand) {
case 'DELETE_USER_MODAL':
return <DeleteUserModal data={data} />;
case 'SOME_MODAL':
return console.log('some modal');
default:
undefined;
}
};
and I can see console.log(data) in the example above. But... I cant see any console.log from DeleteUserModal component.
DeleteUserModal:
type DeleteUserModalProps = {
data: any;
};
export const DeleteUserModal = ({ data }: DeleteUserModalProps) => {
console.log(`show data ${data}`);
return <div>some text...</div>;
};
I can't figure out what I'm doing wrong ?
Why console.log from DeleteUserModal doesn't trigger?
The way you currently have things set up, this would work:
class RegistrationExampleOne extends React.Component {
constructor(props) {
super(props);
this.state = {component: null};
}
render() {
return (
<div>
<button onClick={() => this.setState({component: RenderModals(rowData, 'DELETE_USER_MODAL')})}>Remove</button>
{this.state.component}
</div>
);
}
}
Option one is not necessarily the better way of doing things, but it is more dynamic.
Option two (as mentioned in the comments by #Brian Thompson) would be similar to this:
import DeleteModal from './path';
class RegistrationExampleTwo extends React.Component {
constructor(props) {
super(props);
this.state = {showDeleteModal: null};
}
render() {
return (
<div>
<button onClick={() => this.setState({showDeleteModal: true})}>Remove</button>
{this.state.showDeleteModal && <DeleteModal data={rowData} />}
</div>
);
}
}
It seems just recently React won't treat this.props.children as a function as it did in the past.
I just coded a Modal component where its closeModal function should be passed to the children,
render() {
return (
<Modal>
{
(closeModal) => {
return (<span onClick={closeModal}>Click to close modal</span>)
}
}
</Modal>
)
}
Modal looks like this
class Modal extends React.Component {
constructor(props) {
this.state = { show: true }
this.close = this.closeModal.bind(this)
}
closeModal() {
this.setState({ show: false })
}
render() {
if (!this.state.show)
return null
return (
<div>
{ this.props.children }
</div>
)
}
}
I tried to pass function as a prop via this.props.children({ closeModal: this.closeModal }), guess what, this.props.children is not a function according to latest React 16.9.
As a reference for the folks working with GraphQL, I see Apollo client's <Mutation> and <Query> working quite much the same way.
How can it be achieved?
Edit: Why not a duplicate?
Because other answers rely on this.props.children as function whereas recently React is now rendering error demanding a new approach to this issue:
TypeError: this.props.children is not a function
I've answered the updates needed to show what is wrong and how it can be changed in-line below.
class Modal extends React.Component {
constructor(props) {
// 1️⃣ Make sure to make `props` available in this component.
// This call is what makes `this.props` call to be available within `Modal`.
super(props);
this.state = { show: true };
// 2️⃣ Assign a newly bound method to the matching class method
// this.close = this.closeModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
closeModal() {
this.setState({ show: false });
}
render() {
if (!this.state.show) return null;
// 3️⃣ Pass the modal handler to children
// If you had kept `this.close`, then when you pass the handler
// "{ closeModal: this.close }" instead of "{ closeModal: this.closeModal }"
return <div>{this.props.children({ closeModal: this.closeModal })}</div>;
}
}
function App() {
return (
<div className="App">
<Modal>
{/* 4️⃣ destructure `closeModal` */}
{({ closeModal }) => {
return <button onClick={closeModal}>Click to close modal</button>;
}}
</Modal>
</div>
);
}
As Emile Bergeron has kindly pointed out, you can pass this.props.children(this.close) instead of an object but I found it easier to read/use.
You can fork and try or run the code snippet below~
Thanks Emile Bergeron for the suggestion in the comment~
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = { show: true };
// this.close = this.closeModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
closeModal() {
this.setState({ show: false }, () => {
console.log(`Modal closed`);
});
}
render() {
if (!this.state.show) return null;
return <div>{this.props.children({ closeModal: this.closeModal })}</div>;
}
}
function App() {
return (
<div className="App">
<Modal>
{({ closeModal }) => {
return (
<button type="button" onClick={closeModal}>
Click to close modal
</button>
);
}}
</Modal>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
try this one
class Modal extends React.Component {
constructor(props) {
this.state = { show: true }
this.close = this.closeModal.bind(this)
}
closeModal() {
this.setState({ show: false })
}
render() {
if (!this.state.show)
return null
return (
<div>
{ this.props.children(this.close) }
</div>
)
}
}
I am struggling with successfully removing component on clicking in button. I found similar topics on the internet however, most of them describe how to do it if everything is rendered in the same component. In my case I fire the function to delete in the child component and pass this information to parent so the state can be changed. However I have no idea how to lift up the index of particular component and this is causing a problem - I believe.
There is a code
PARENT COMPONENT
export class BroadcastForm extends React.Component {
constructor (props) {
super(props)
this.state = {
numberOfComponents: [],
textMessage: ''
}
this.UnmountComponent = this.UnmountComponent.bind(this)
this.MountComponent = this.MountComponent.bind(this)
this.handleTextChange = this.handleTextChange.bind(this)
}
MountComponent () {
const numberOfComponents = this.state.numberOfComponents
this.setState({
numberOfComponents: numberOfComponents.concat(
<BroadcastTextMessageForm key={numberOfComponents.length} selectedFanpage={this.props.selectedFanpage}
components={this.state.numberOfComponents}
onTextChange={this.handleTextChange} dismissComponent={this.UnmountComponent} />)
})
}
UnmountComponent (index) {
this.setState({
numberOfComponents: this.state.numberOfComponents.filter(function (e, i) {
return i !== index
})
})
}
handleTextChange (textMessage) {
this.setState({textMessage})
}
render () {
console.log(this.state)
let components = this.state.numberOfComponents
for (let i = 0; i < components; i++) {
components.push(<BroadcastTextMessageForm key={i} />)
}
return (
<div>
<BroadcastPreferencesForm selectedFanpage={this.props.selectedFanpage}
addComponent={this.MountComponent}
textMessage={this.state.textMessage} />
{this.state.numberOfComponents.map(function (component) {
return component
})}
</div>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastForm))
CHILD COMPONENT
import React from 'react'
import { createContainer } from 'react-meteor-data'
import { withRouter } from 'react-router'
import { BroadcastFormSceleton } from './BroadcastForm'
import './BroadcastTextMessageForm.scss'
export class BroadcastTextMessageForm extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.unmountComponent = this.unmountComponent.bind(this)
}
handleChange (e) {
this.props.onTextChange(e.target.value)
}
unmountComponent (id) {
this.props.dismissComponent(id)
}
render () {
console.log(this.props, this.state)
const textMessage = this.props.textMessage
return (
<BroadcastFormSceleton>
<div className='textarea-container p-3'>
<textarea id='broadcast-message' className='form-control' value={textMessage}
onChange={this.handleChange} />
</div>
<div className='float-right'>
<button type='button'
onClick={this.unmountComponent}
className='btn btn-danger btn-outline-danger button-danger btn-small mr-3 mt-3'>
DELETE
</button>
</div>
</BroadcastFormSceleton>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastTextMessageForm))
I am having problem with access correct component and delete it by changing state. Any thoughts how to achieve it?
Please fix the following issues in your code.
Do not mutate the state of the component. Use setState to immutably change the state.
Do not use array index as the key for your component. Try to use an id field which is unique for the component. This will also help with identifying the component that you would need to unmount.
Try something like this. As mentioned before, you don't want to use array index as the key.
class ParentComponent extends React.Component {
constructor() {
this.state = {
// keep your data in state, as a plain object
textMessages: [
{
message: 'hello',
id: '2342334',
},
{
message: 'goodbye!',
id: '1254534',
},
]
};
this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
}
handleDeleteMessage(messageId) {
// filter by Id, not index
this.setState({
textMessages: this.state.textMessages.filter(message => message.id !== messageId)
})
}
render() {
return (
<div>
{this.state.textMessages.map(message => (
// Use id for key. If your data doesn't come with unique ids, generate them.
<ChildComponent
key={message.id}
message={message}
handleDeleteMessage={this.handleDeleteMessage}
/>
))}
</div>
)
}
}
function ChildComponent({message, handleDeleteMessage}) {
function handleClick() {
handleDeleteMessage(message.id)
}
return (
<div>
{message.message}
<button
onClick={handleClick}
>
Delete
</button>
</div>
);
}
I am building an isomorphic React app. The workflow I am currently working through is :
User navigates to the /questions route which makes the API call server side and loads the data on the page. This calls the renderData() function like it should and loads all the questions for the user to see.
User clicks add button to add new question and a modal pops up for a user to enter in the form fields and create a new question.
With every change in the modal, the renderData() function is getting called (which it shouldn't). When the user clicks the Create Question button, the renderData() function is also getting called is throwing an error because the state changes.
I can't pinpoint why the renderData() function is getting called every single time anything happens in the modal. Any ideas as to why this is happening and how to avoid it?
Main component :
import React, { Component, PropTypes } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './QuestionsPage.scss';
import QuestionStore from '../../stores/QuestionStore';
import QuestionActions from '../../actions/QuestionActions';
import Loader from 'react-loader';
import QuestionItem from '../../components/QuestionsPage/QuestionItem';
import FloatButton from '../../components/UI/FloatButton';
import AddQuestionModal from '../../components/QuestionsPage/AddQuestionModal';
const title = 'Questions';
class QuestionsPage extends Component {
constructor(props) {
super(props);
this.state = QuestionStore.getState();
this.onChange = this.onChange.bind(this);
this.openModal = this.openModal.bind(this);
this.closeMOdal = this.closeModal.bind(this);
}
static contextTypes = {
onSetTitle: PropTypes.func.isRequired,
};
componentWillMount() {
this.context.onSetTitle(title);
QuestionStore.listen(this.onChange);
}
componentWillUnmount() {
QuestionStore.unlisten(this.onChange);
}
onChange(state) {
this.setState(state);
}
openModal = () => {
this.setState({ modalIsOpen: true});
}
closeModal = () => {
this.setState({ modalIsOpen: false});
}
createQuestion = () => {
const date = new Date();
const q = this.state.question;
q.createdAt = date;
this.setState({ question : q });
QuestionStore.createQuestion(this.state.question);
}
textChange = (val) => {
const q = this.state.question;
q.text = val;
this.setState({ question : q });
}
answerChange = (val) => {
const q = this.state.question;
q.answer = val;
this.setState({ question : q });
}
tagChange = (val) => {
const q = this.state.question;
q.tag = val;
this.setState({ question : q });
}
companyChange = (val) => {
const q = this.state.question;
q.company = val;
this.setState({ question : q });
}
renderData() {
return this.state.data.map((data) => {
return (
<QuestionItem key={data.id} data={data} />
)
})
}
render() {
return (
<div className={s.root}>
<div className={s.container}>
<h1>{title}</h1>
<div>
<Loader loaded={this.state.loaded} />
<FloatButton openModal={this.openModal}/>
<AddQuestionModal
open = {this.state.modalIsOpen}
close = {this.closeModal}
createQuestion = {this.createQuestion}
changeText = {this.textChange}
changeAnswer = {this.answerChange}
changeTag = {this.tagChange}
changeCompany = {this.companyChange}
/>
{ this.renderData() }
</div>
</div>
</div>
);
}
}
export default withStyles(QuestionsPage, s);
Modal Component :
import React, { Component, PropTypes } from 'react';
import QuestionStore from '../../stores/QuestionStore';
import QuestionActions from '../../actions/QuestionActions';
import Modal from 'react-modal';
import TextInput from '../UI/TextInput';
import Button from '../UI/Button';
class AddQuestionModal extends Component {
createQuestion = () => {
this.props.createQuestion();
}
closeModal = () => {
this.props.close();
}
changeText = (val) => {
this.props.changeText(val);
}
changeAnswer = (val) => {
this.props.changeAnswer(val);
}
changeTag = (val) => {
this.props.changeTag(val);
}
changeCompany = (val) => {
this.props.changeCompany(val);
}
render() {
return (
<Modal
isOpen={this.props.open}
onRequestClose={this.closeModal} >
<TextInput
hintText="Question"
change={this.changeText} />
<TextInput
hintText="Answer"
change={this.changeAnswer} />
<TextInput
hintText="Tag"
change={this.changeTag} />
<TextInput
hintText="Company"
change={this.changeCompany} />
<Button label="Create Question" onSubmit={this.createQuestion} disabled={false}/>
<Button label="Cancel" onSubmit={this.closeModal} disabled={false}/>
</Modal>
);
}
}
export default AddQuestionModal;
On click of
It's happening because every change causes you to call the setState method and change the state of the main component. React will call the render function for a component every time it detects its state changing.
The onChange event on your inputs are bound to methods on your main component
Each method calls setState
This triggers a call to render
This triggers a call to renderData
React allows you to change this by overriding a shouldComponentUpdate function. By default, this function always returns true, which will cause the render method to be called. You can change it so that only certain changes to the state trigger a redirect by comparing the new state with the old state.