I have a react-bootstrap modal with two inputs inside.
Modal is displayed when showModal property in the state changes to true.
I update the fieldOne property (also inside the state) when input value changes.
So, whenever I enter something in the input, modal flashes (re-renders) as well.
How to I prevent Modal from re-rendering when I update the state?
Maybe you should split your modal with the inputs into two seperate components. That should fix your rerender issues.
If you don't want a re-render use a variable other than state to hold your data:
constructor (props) {
super(props);
this.state = {
input: ''
};
this.holder = '';
}
handleInput(input) {
this.holder = input;
}
submitInput() {
this.setState({input: this.holder})
}
render () {
return (
<input type="text" onChange={(e) => this.handleInput(e.target.value)} onBlur={() => this.submitInput()} />
)
}
The purpose of state is for React to evaluate if the DOM needs to change, and if it does it re-renders.
I hit the same problem - putting a form in a modal resulted in the modal rerendering on each keypress.
I could probably get around this by splitting out the form from the modal, BUT I wanted the modal and the form in the same component because the modal buttons trigger the form save. Yes there's other ways to handle that too like passing the save function between the split modal and the form, but now it's getting messy.
So my solution was to make the form in the modal uncontrolled. This means that changing the field values does not modify state and therefore the modal does not rerender.
Set the Modal's view condition with separate states solves this issue.
Herewith a demo example, I used two seperate states i.e, firstView & secondView
import Modal from 'react-bootstrap/Modal'
import ModalBody from 'react-bootstrap/ModalBody'
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
demoModal: true,
firstView: true,
secondView: false
};
};
render() {
return (
<div>
<Modal scrollable={true} show={this.state.demoModal} fade={false} style={{ display: "block"}}>
<ModalBody>
<div>
{
this.state.firstView ?
<div>
..code
</div>
:
<></>
}
{
this.state.secondView ?
<div>
..code
</div>
:
<></>
}
</div>
</ModalBody>
</Modal>
</div>
);
}
}
Related
This question already has answers here:
Basic React form submit refreshes entire page
(4 answers)
Closed 3 years ago.
I am pretty new to React so apologies if this is a dumb question, which I suspect it is.
I have a simple React app with a dropdown, button and list. When the button is clicked, the selected item in the dropdown is added to the list. Each item added to the list also has a delete button associated with it.
I need the SelectComponent (dropdown and button) and ListComponent (list and buttons) to know what the items in the list are so they can add/remove items from it, so I am storing the state in the parent App component and passing it down to the children as props, along with a callback function that can update it (using setState()). Here is what I have:
Select Component
class SelectComponent extends Component<SelectProps, {}> {
constructor(props: any) {
super(props);
this.changeHandler = this.changeHandler.bind(this);
this.clickHandler = this.clickHandler.bind(this);
}
changeHandler(event: ChangeEvent<HTMLSelectElement>) {
currentSelection = event.target.value;
}
clickHandler(event: MouseEvent<HTMLButtonElement>) {
this.props.selectedItems.push(currentSelection);
this.props.updateList(this.props.selectedItems);
}
render() {
let optionItems = this.props.options.map((optionItem, index) =>
<option>{optionItem}</option>
);
return (
<form>
<div>
<select onChange={this.changeHandler}>
<option selected disabled hidden></option>
{optionItems}
</select>
<br />
<button type="submit" onClick={this.clickHandler}>Add to list</button>
</div>
</form>
);
}
}
List Component
class ListComponent extends Component<ListProps, {}> {
constructor(props: any) {
super(props);
this.removeListItem = this.removeListItem.bind(this);
}
removeListItem(i: number) {
this.props.selectedItems.filter((selection, j) => i !== j);
this.props.updateList(this.props.selectedItems);
}
render() {
let listItems;
if (this.props.selectedItems) {
listItems = this.props.selectedItems.map((listItem, index) =>
<li>{listItem}<button onClick={() => this.removeListItem(index)}>Delete</button></li>
);
}
return (
<div>
<ul>
{listItems}
</ul>
</div>
);
}
}
main App
class App extends Component<{}, State> {
constructor(props: any) {
super(props);
this.state = {
selectedItems: []
}
this.updateList = this.updateList.bind(this);
}
updateList(selectedItems: string[]) {
this.setState({selectedItems});
}
render() {
return (
<div>
<SelectComponent options={["Cyan", "Magenta", "Yellow", "Black"]} selectedItems={this.state.selectedItems} updateList={this.updateList} />
<ListComponent selectedItems={this.state.selectedItems} updateList={this.updateList} />
</div>
);
}
}
I also have a couple interfaces defining the props and state as well as a variable to hold the currently selected item in the dropdown.
What I want to happen is: the "Add to list" button is pressed, adding the current dropdown selection to the props, then passing the props to the updateList() function in the parent class, updating the state. The parent class should then re-render itself and the child components according to the new state. From what I can tell by looking at the console, this does happen.
However for some reason after it gets done rendering the ListComponent, the app completely reloads, clearing the state and the list and returning the dropdown to it's default value. I can tell because I see Navigated to http://localhost:3000/? in the console right after the ListComponent render function is called.
So what have I done wrong? Again I am pretty new to React so I have a feeling this is something simple I am missing. Any help would be greatly appreciated!
Edit: Forgot to mention (although it is probably obvious) that I am coding this in TypeScript, although I don't think that is related the issue.
If you are working with form and submit handler then you have to set the event.preventDefault() in your submit method.
You have to set preventDefault() in clickHandler method.
clickHandler(event) {
event.preventDefault(); // set it...
this.props.selectedItems.push(currentSelection);
this.props.updateList(this.props.selectedItems);
}
because you're using form and button type submit. Type submit is reason your app reload.
To prevent app reload by default, in the clickHandler function of Select Component, add preventDefault in the top of function
event.preventDefault();
I want to send events down to my React child.
I feel like this is kind of an easy thing to do, so maybe i just have a mental block, and there is something obvious that is staring me in the face.
Anyway, I have a little Test app which illustrates the problem:
export class Test extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let {buttonClicked, textFieldChanged} = this.state
return (
<div>
<button onClick={()=>this.handleClick()}>
Click
</button>
<input type={"text"} onChange={()=>this.handleTextChange()}/>
<Inner buttonClicked={buttonClicked} textFieldChanged={textFieldChanged}/>
</div>
);
}
handleClick(e) {
this.setState({ buttonClicked: true })
}
handleTextChange(e) {
this.setState({textFieldChanged:true})
}
}
class Inner extends React.Component {
render() {
let {buttonClicked, textFieldChanged} = this.props;
return (
<React.Fragment>
<div>Clicked : {buttonClicked ? "CLICKED!" : " "}</div>
<div>Text input : {textFieldChanged ? "TYPED!" : " "}</div>
</React.Fragment>
);
}
}
A button and a textfield live in the parent. Both these widgets can fire off events and change the child component.
This is simply achieved by passing a state value as a property down to the child. Very easy stuff.
However I would like an either/or situation. When I click the button this removes the text event, and vice versa. Ie. I do not want to see a situation like this :
Now there is a very obvious way to fix this by changing the state value to "false" of the other value.
handleClick(e) {
this.setState({ buttonClicked: true, textFieldChanged: false })
}
handleTextChange(e) {
this.setState({textFieldChanged:true, buttonClicked: false})
}
Is there any OTHER way of doing this?
The problem is that I have LOTS and LOTS of even handlers in my component and I don't want to negate the other state properties of the other values.
if i understood you correctly just one function will help - pass the attribute name into it
handleClick(propName) {
this.setState({
...this.state,
[propName]: !this.state[propName]
})
}
Create property lastEventType in parent component state , whenever you click or type - update it. And pass only this property to Inner component
I have a React application coupled to Redux. There is a component rendering a form wrapper (a custom implementation of Formik), while the form inputs themselves are rendered by a child component.
(Not the exact code, but gets the point across.)
...
render() {
const {
config,
updateContactDetails,
errorMessages,
contactDetails,
previousFormValues,
isUpdating,
} = this.props;
const { apiBaseUrl, fetchTimeout, globalId } = config;
const initialValues = previousFormValues || getInitialContactDetailsValues(contactDetails);
if (isUpdating) return <Spinner />;
return (
<Form
initialValues={initialValues}
validate={(values) => validate(values, errorMessages)}
onSubmit={(values) => {
updateContactDetails(apiBaseUrl, globalId, values, fetchTimeout); // dispatch action
}}
>
<ContactDetailsForm content={content} />
</Form>
);
}
...
When you click the submit button in ContactDetailsForm, the value of isUpdating in the Redux store is set to true. As you can see above, that causes the the form to be replaced with a spinner component. However, it is somehow possible to submit the form twice by clicking the button twice.
How can this be? Could there be re-render happening before the one that replaces the form with the spinner? I know I can solve the problem by passing isUpdating into ContactDetailsForm and using it to disable the button, but I still want to illuminate the cause.
EDIT
The reducer looks something like this, in case it helps:
case UPDATE_CONTACT_DETAILS_START: {
return {
...state,
errorUpdatingContactMethods: {},
hasUpdatedContactDetails: false,
isUpdating: true,
contactDetailsValues: action.values,
};
}
You should instead set a disabled property on the button based on the isUpdating prop. It might be that it's just a race condition.
I am having a react-app where i am rendering button in one component and model in another component . The button access the function openModal for opening the modal through refs. i am writing test cases for my application but could not figure out a way to write a test case for checking the modal is opening on button click
Buttons component
<div>
<button type='primary' onClick={() => this.handleClick.showModal()}>
ADD
</button>
<AddConceptModal ref={(instance) => this.handleClick = instance}/>
</div>
Modal component:
class ModalComp extends React.Component {
state = {
visible: false,
}
// show modal handles the logic of opening the modal
showModal = () => {
this.setState({
visible: true,
})
}
render() {
const { visible } = this.state
return (
<div>
<Modal
visible={visible}
>
modal
</Modal>
</div>
)
}
}
export default ModalComp
i tried creating instance like this:
let component = mount(<ModalComp />)
const instance = component.instance()
i even tried spyOn method in jest could do it exactly. how can i write test that simulate the button in button component which calls the showModal() and
i want to check if modal is receiving prop as true after button click simulation
Say I am rendering a signup component on a page. And I have a button that says submit and login. When I click on the login button I want it to replace the signup component without having to go another page. Just wondering, conceptually how would I implement the onclick handler. Would I need to use react router?
You could use a ternary statement to conditionally render components.
Fist declare some sort of variable in state to handle the component switch:
constructor(props) {
super(props);
this.state = {
login: false
}
}
Then in your click handler for your button you would handle the switching of this variable:
onClick = event => {
this.setState({login: !this.state.login})//sets it to opposite of previous value
}
Then in your render you would implement the ternary statement
render() {
return(
this.state.login ? //if login is true
<SomeComponent
onClick={this.onClick.bind(this)}
/>
: //else
<AnotherComponent
onClick={this.onClick.bind(this)}
/>
)
}
And in both components you would have to have a button:
<button onClick={this.props.onClick}>Switch Component</button>
I would use the react-router-dom Link:
import { Link } from 'react-router-dom';
and the button would look something like this:
<button component={Link} to="/yourpage">
Using Router Library is the right way to navigate between components. Since, you do not want to use routing, you can try something like this:
maintain a state variable to check wether the user is logged in, in the parent component where you want to replace the Signup screen with another screen i.e.
constructor(props) { /* Parent Component Constructor */
super(props);
this.state = {
isLoggedIn : false
}
}
and the onLoginClick in Parent Component method will be:
onLoginClick = () => {
this.setState({ isLoggedIn : true })
}
This value will be set to true, when you click on the Login button in the Login Component(Child Component), The onLoginClick method will be passed as props from parent component i.e.
<Button onClick={this.props.onLoginClick}>Login</Button>
Now use this isLoggedIn state variable in the render function of Parent Component like this:
render() {
return(
{!this.state.isLoggedIn &&
<LoginComponent
onClick={this.onLoginClick.bind(this)}
/>
}
{this.state.isLoggedIn &&
<SecondComponent/>
}
)
}