Refreshing issue when submitting form with react-redux-form - reactjs

Issue
Important: This has been solved; see below explanation.
I've been having this issue of the page refreshing when submitting a react-redux-form. I have found a lot of similar issues since this is the default behavior of submitting a form in HTML; however I haven't found anything related to this specific library (react-redux-form).
I've also tried to apply what was suggested in other cases, mainly the event.preventDefault(), but I don't know how to use the event object with this library as they recommend the following syntax:
<LocalForm onSubmit={(values) => this.handleSubmit(values)}>
I've tried with values.event, but that was unsuccessful.
Below what I intend to have:
import React, { Component } from 'react';
import Hall from './HallComponent';
import { Row, Button, Label } from 'reactstrap';
import { Redirect } from 'react-router';
import { LocalForm, Control } from 'react-redux-form';
class Endgame extends Component {
constructor(props) {
super(props);
this.state = {
redirect: false
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(values) {
const date = new Date();
this.props.addScore({
date: date.toLocaleDateString("en-GB"),
name: values.name,
score: this.props.points
});
this.setState({ redirect: true });
}
render() {
if (this.state.redirect) {
return (
<Redirect to="/hall" />
);
}
else {
return(
<div className="container">
<div className="row">
<div className="col-12 col-md-5 m-1">
<p>Your score is: {this.props.points}</p>
<p>Add you name to the Hall of Fame</p>
<LocalForm onSubmit={(values) => this.handleSubmit(values)}>
<Row className="form-group">
<Label htmlFor="name">Nickname</Label>
<Control.text model=".name" id="name" name="name" placeholder="Your Name" className="form-control" />
</Row>
<Button type="submit">Submit</Button>
</LocalForm>
</div>
<div className="col-12 col-md-5 m-1">
<Hall scores={this.props.scores} />
</div>
</div>
</div>
);
}
}
}
export default Endgame;
Solution
I've found that the problem was not coming from the submit button but the way I had arranged my components.
I'm still not entirely sure of what actually happens behind the scene, but my Hall component above was unmounted and re-mounted each time the addScore() e.g. a redux action was fired. I found out that this could happen if the state of the parent component was modified somehow this could trigger re-mounting of children components.
I've migrated the state locally to Hall component connecting it to redux store and it now works properly.

its because you have a button of type "submit". Its default action is to refresh the page.
<Button type="submit">Submit</Button>
You can put an evt.preventDefault() in your handle submit function to stop the page from refreshing, but I would encourage you to just change the button type and put your handleSubmit function on the onClick event like this.
<Button type="button" onClick={this.handleSubmit}>Submit</Button>

Related

form submit re-renders page in react

I have a form in a react component.
When I click the submit button, I call signin(). In signin(), an error occurs as I can see the output in the chrome console, but it flashes by so quickly, I can't read it. Then the page refreshes and the error message is gone from the console.
Why is my form re-rendering the page? And how can I suppress the re-rendering so that I can read the error?
Here is my component:
import React, { Component } from 'react';
import './login.scss';
import axios from '../axios/axiosInstance';
class Login extends Component {
constructor() {
super();
this.usernameRef = React.createRef();
this.passwordRef = React.createRef();
}
signin() {
axios.get('/auth', {headers: {
username: this.usernameRef.current.value,
password: this.passwordRef.current.value}}).then(response => {
console.log('response=', response);
}).catch(err => {
console.log('err=', err);
});
}
render() {
return (
<div className="login-container">
<form onSubmit={this.signin}>
<div className="flex-row-end">
<div className="form-element flex-column-end">
<input type="text"
placeholder="Username or email"
name="username"
ref={this.usernameRef}
required />
</div>
<div className="form-element flex-column-end">
<input type="password"
placeholder="Password"
name="password"
ref={this.passwordRef}
required />
</div>
<div className="login-submit">
<button className="submit-login-button" type="submit"><i className="fas fa-sign-in-alt"> </i>Sign In</button>
</div>
</div>
</form>
</div>
);
}
};
export default Login;
As you can see, in signin(), I am using axios to send a request to my backend with user credentials. On the backend, the logs show no record of receiving the request. Therefore, the error must be happening before the request is sent. But I need a way to suppress the re-rendering of the page so that I can see what the error message is.
Thank you very much.
Change the signature of signin to take the form submission event, you'll need to prevent the default action on it. This keeps the page from reloading.
signin(e) {
e.preventDefault();
...
The error tells you that reference from component is not define yet.
Because method is not bound to this when used as a function:
<form onSubmit={e => this.signin(e)}>
and then put e.preventDefault() inside signin(e) which prevent the blink after you submit the form.
If I'm not mistaken axios will make the http request asynchronous, so you might have to use the event.persist() if this is not the case, preventDefault() should work as they mentioned in the answers above, also it is recommended to call the api in the componentDidMount()

Content in reactstrap modal continues to exist after closing using enzyme/jest

I'm trying to do some testing with enzyme and jest in react, and things work fine when I open a modal e.g. input fields in the modal aren't there and the modal state is false (as intended) when I try to find them using
expect(wrapper.find("input")).toHaveLength(0);
and do exist after I've opened the modal using
const edit = wrapper.find("Button.update-button");
edit.simulate("click");
expect(wrapper.find("input")).toHaveLength(2);
which all works (including the modal state turning to true after it opens) as intended. But when I close the modal, the state gets toggled off correctly, but the modal content (e.g. the input boxes and buttons in the modal) still exist when I try:
expect(wrapper.find("input")).toHaveLength(0);
I still somehow have 2 input fields that shouldn't be there as the modal is closed.
Here is my code for the component I am trying to test if that helps:
/*
Artefact Component displays just UI for the Artefact itself and it's information.
*/
import React, { Component } from "react";
import DeleteArtefact from "../DeleteArtefact";
import UpdateArtefact from "../UpdateArtefact";
import {
Card,
CardImg,
CardTitle,
CardBody,
ButtonGroup,
Button,
CardFooter
} from "reactstrap";
class Artefact extends Component {
// Initialise State
state = {
updatemodal: false,
deletemodal: false
};
// Toggle function for toggling modal open/close
toggleUpdate = () => {
this.setState({
updatemodal: !this.state.updatemodal
});
};
toggleDelete = () => {
this.setState({
deletemodal: !this.state.deletemodal
});
};
prepareUpdateState = () => {
this.props.editUpdate(this.props.artefact);
this.toggleUpdate();
};
render() {
const {
artefact,
onChange,
onUpdateClick,
editUpdate,
onDeleteClick
} = this.props;
return (
<Card>
<CardImg
src={artefact.img}
alt={`Image for Artefact ${artefact.name}`}
/>
<CardBody>
<CardTitle>
<h6>{artefact.name}</h6>
</CardTitle>
</CardBody>
<CardFooter>
<ButtonGroup>
<Button
className="update-button"
color="dark"
onClick={this.prepareUpdateState}
>
Edit
</Button>
<Button
className="delete-button"
color="dark"
onClick={this.toggleDelete}
>
Delete
</Button>
</ButtonGroup>
<UpdateArtefact
artefact={artefact}
onChange={onChange}
onUpdateClick={onUpdateClick}
editUpdate={editUpdate}
toggle={this.toggleUpdate}
modal={this.state.updatemodal}
/>
<DeleteArtefact
_id={artefact._id}
onDeleteClick={onDeleteClick}
toggle={this.toggleDelete}
modal={this.state.deletemodal}
/>
</CardFooter>
</Card>
);
}
}
export default Artefact;
And here is the UpdateArtefact Component that has the modal I'm trying to test:
/*
UpdateArtefact Component is a child Component of ArtefactGallery and
creates a new Artefact by using functions onChange() and updateClick()
and editUpdate() which are passed as props from ArtefactGallery and
passes state back up and makes api calls using axios.
*/
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input
} from "reactstrap";
class UpdateArtefact extends Component {
// Passes state up to ArtefactGallery component and updates the artefact.
onSubmit = e => {
e.preventDefault();
this.props.onUpdateClick(this.props.artefact._id);
this.props.toggle();
};
// Sets state in ArtefactGallery to the initial values of the artefact
// to prepare for any edits to be made in the case that some fields have
// no change, so that there are no null fields.
prepareUpdateState = () => {
this.props.editUpdate(this.props.artefact);
this.props.toggle();
};
render() {
const { artefact } = this.props;
return (
<div style={{ marginLeft: "1rem" }}>
<Modal isOpen={this.props.modal} toggle={this.props.toggle}>
<ModalHeader toggle={this.props.toggle}>
Edit Artefact
</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label>Artefact</Label>
<Input
type="text"
name="name"
id="artefactName"
defaultValue={artefact.name}
onChange={this.props.onChange}
/>
<Label>Image</Label>
<Input
type="text"
name="img"
id="artefactImg"
defaultValue={artefact.img}
onChange={this.props.onChange}
/>
<Button
className="modal-submit-button"
color="dark"
style={{ marginTop: "2rem" }}
block
>
Submit
</Button>
</FormGroup>
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
export default UpdateArtefact;
So basically I just want to know what the reason if for why the modal content is still being picked up by enzyme and how to fix this. I've tried searching all over but couldn't find an answer so I'm guessing there's something obvious that I'm missing.
See, your components does not use conditional rendering like
{someFlag && <SomeElement>}
but just pass down isOpen prop:
<Modal isOpen={this.props.modal} toggle={this.props.toggle}>
so probably Modal just hides its props.children and input is kept.
As a workaround you may validate against ModalComponentYouHaveRendered.props().isOpen instead of checking amount of inputs
You can try using:
wrapper.update()
after closing the modal.
In this way, the wrapper should get updated.

How to listen to localstorage value changes in react?

I want to show a button when user is logged.If user is not logged then I m not showing button.When user logged i will set local storage values.when i set local storage in login Component,Header component must listen to that event and show the button.I m using addEventListener for listening.But its not listening.
I don't know where to listen in header Component.
// HeaderComponent(header.js):
class HeaderComponent extends Component {
componentDidMount(){
if(typeof window!='undefined'){
console.log(localStorage.getItem("token"));
window.addEventListener("storage",function(e){
this.setState({ auth: true});
})
}
}
render() {
return (
<div className="header">
<div className="container">
<div className="header-content">
<img src={logo} alt="logo"></img>
<div className="nav-links" >
<ul >
<li>Home</li>
<li>About</li>
<li>Services</li>
<li><NavLink activeClassName="active" to="/upload" >Upload</NavLink></li>
<li><NavLink activeClassName="active" to="/signup"> Sign Up</NavLink></li>
{ this.state.auth? <li onClick={this.onLogout}>Logout</li> :null}
</ul>
</div>
</div>
</div>
</div>
);
}
}
//loginComponent(login.js)
class LoginComponent extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(event) {
const data = {
username: document.getElementById('name').value,
password: document.getElementById('password').value
}
axios.post(`http://localhost:4000/user/login`, data).then(res => {
this.props.history.push("/");
localStorage.setItem("token",res.data.token);
localStorage.setItem("auth",true);
}).catch(err => console.log(err));
}
render() {
return (
<section class="log-in">
<div class="card-col">
<form>
<h3>LOG IN</h3>
<div class="form-controls">
<input id="name" type="text" placeholder="username" class="input"></input>
</div>
<div class="form-controls">
<input id="password" type="password" placeholder="password" class="input"></input>
</div>
<button type="submit" onClick={this.onSubmit} class="button" >Log in</button>
</form>
</div>
</section>
)
}
}
The current answers are overlooking a really simple and secure option: window.dispatchEvent.
Where you set your localStorage item, if you dispatch an event at the same time then the eventListener in the same browser tab (no need to open another or mess with state) will also pick it up:
const handleLocalStorage = () => {
window.localStorage.setItem("isThisInLocalStorage", "true");
window.dispatchEvent(new Event("storage"));
};
window.addEventListener('storage', () => {
console.log("Change to local storage!");
// ...
})
EDIT:
Because this seems to be helpful, I'd also recommended checking out the useLocalStorage hook from the usehooks-ts team. You don't need to install it as a package; you can just copy the hook wholesale. This hook makes use of the solution I originally shared, but adds a whole lot more sophisticated logic to it.
Please take note of two things
storage event works only when the same application opened in two browser tabs (it is used to exchange info between different tabs of the same app). Storage event will not fire when both components shown on the same page.
When adding event listerner, you're passing function(), not array function. function() doe not capture this so you should explicitly bind(this) or change it to arrow function.
For example
window.addEventListener("storage",(function(e){
this.setState({ auth: true});
}).bind(this));
Or do with arrow function
window.addEventListener("storage",(e) => {
this.setState({ auth: true});
});
Here is simple example.
Be sure to open it in two tabs (the same link). Store value in one tab and see this value in another tab.
I found a really bad hack to accomplish this:
I have a Toolbar and a Login Component where the Toolbar component listens to changes in localStorage and displays the logged-in user name when the Login Component updates local storage if authentication is successful.
The Toolbar Component
(similar to the Header component in your case)
const [loggedInName, setLoggedInName] = useState(null);
useEffect(() => {
console.log("Toolbar hi from useEffect")
setLoggedInName(localStorage.getItem('name') || null)
window.addEventListener('storage', storageEventHandler, false);
}, []);
function storageEventHandler() {
console.log("hi from storageEventHandler")
setLoggedInName(localStorage.getItem('name') || null)
}
function testFunc() {
console.log("hi from test function")
storageEventHandler();
}
Add a hidden button to your Toolbar component. This hidden button will call the testFunc() function when clicked which will update the logged-in user's name as soon as local storage is updated.
<button style={{ display: 'none' }} onClick={testFunc} id="hiddenBtn">Hidden Button</button>
Now, in your Login component
.
.
.
//login was successful, update local storage
localStorage.setItem("name",someName)
//now click the hidden button using Javascript
document.getElementById("hiddenBtn").click();
.

How to change the state in conditional - React

I need to change the state of sibling components in my React App.
I use state and setstate
I need to change the state of sibling components. When loading the page, there must exist (visible in the page) <BkUser /> and when clicking "button id =" ds-visual "it must be deleted (<BkUser /> mustn't exist) and there must exist <BkDescanso />.
When you click on <BkSleep /> (in the div parent) you should remove <BkDescanso /> and show <BkUser />
This is the web.
There should never be <BkUser/> and <BkSleep> at the same time. <Bkuser /> is the blue block and <BkDescanso /> is the red block
This is my code:
Edit: I edit my original code because I fix the problem. This is the final OK Code. In the end the most important thing was the state conditional
{
this.state.usuario ? (<BkUser handleClick = {this.handleClick} usuario={this.state.usuario}/>): (<BkDescanso handleClick = {this.handleClick} usuario={this.state.usuario}/>)}
import React, { Component } from 'react';
class Header extends Component {
constructor(props) {
super(props);
this.state = {
usuario: true,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
usuario: !state.usuario
}));
//alert("Works button");
}
render(){
return (
<header className="header">
<div className="configuracion">
{
this.state.usuario
? (
<BkUser handleClick = {this.handleClick} usuario={this.state.usuario}/>
)
: (
<BkDescanso handleClick = {this.handleClick} usuario={this.state.usuario}/>
)}
<div className="content-btn">
<button id="config" className='btn btn--rounded'><span className="ico-configuracion"></span></button>
<button id="salir" className='btn btn--rounded'><span className="ico-exit"></span></button>
</div>
</div>
</header>
);
}
}
class BkUser extends Component{
render(){
return ((
<div className='usuario'>
<img src="../img//usuario.svg" alt="Imagen usuario"/>
<div className="content-usuario">
<span id="nm-usuario" className="h4">Hermione Jane Granger</span>
<span id="tp-usuario" className="h5">Supervisor</span>
</div>
<div className="content-descansos">
<div className="botones">
<button id="ds-visual" className='btn btn--rounded' onClick={this.props.handleClick}><span className="ico-visual"></span></button>
<button id="ds-admin" className='btn btn--rounded'><span className="ico-tiempo-administrativo"></span></button>
<button id="ds-otros" className='btn btn--rounded'><span className="ico-descanso"></span></button>
</div>
<div className="ds-actual">
<span id="ds-tipo">Administrativo</span>
<span id="ds-tiempo">00:08:47</span>
</div>
</div>
</div>
));
}
}
class BkDescanso extends Component {
render(){
return ((
<div className='usuario descanso' onClick={this.props.handleClick}>
<h3>Finalizar descanso</h3>
</div>
));
}
}
export default Header;
Right now handleClick works but always exist BkUser and BkDescanso. I need only one to exist. If you click on id = "ds-visual" the bkUser block should disappear and BkDescanso appear. Then if you click on div className = 'user rest' in BkUser there should only be BkDescanso.
I think that it is not able to know when it is true and when it is false to show or hide
Thanks a lot for the help.
You're missing two things:
First you have to pass the handleClick function to the BkUser component, and then you have to call it via this.props.handleClick.
...
<BkUser handleClick={this.handleClick} usuario={this.state.usuario} />
....
<button
id="ds-visual"
className="btn btn--rounded"
onClick={this.props.handleClick}
>
ds-visual
<span className="ico-visual" />
</button>
CodeSandbox here.
Read more here.
You can change the state of the siblings by passing a function from the parent via props into them.
In the end your siblings are the children of their parent.
You can read this articles on how to change the state of child components.
React js change child component's state from parent component
https://medium.freecodecamp.org/react-changing-state-of-child-component-from-parent-8ab547436271
An other thing you could look into would be React Redux.

checkbox hiding and showing component - react

I am building a small feature that has a checkbox styled as slider that, when turned on and off, should display another component - BatchWidget. The way I have it currently set up, it works on initial page load, and then hides as intended. However, when I go to "toggle" it back on to show the component again, it does not work. Is there an easy solution to this?
const Slider = (props) => {
return (
<div className="slider-container">
<label className="switch">
<input type="checkbox" checked={props.checked} onClick= {props.showWidget} />
<span className="slider round" />
</label>
<p className="batch-slider-title"> Batch Widget </p>
</div>
);
};
const Settings = ({showSlider}) => {
return (
<div className="settings">
<i className="icon-gear" onClick={() => showSlider()} />
</div>
);
}
class WidgetContainer extends Component {
constructor() {
super();
this.state = {
checked: true,
isSliderDisplayed: false,
};
this.showWidget = this.showWidget.bind(this);
this.showSlider = this.showSlider.bind(this);
}
showWidget() {
this.setState({
checked: !this.state.checked,
});
}
showSlider() {
this.setState({
isSliderDisplayed: !this.state.isSliderDisplayed,
});
}
render() {
const displayBatchWidget = this.state.checked ? <BatchWidget /> : null;
const displaySlider = this.state.isSliderDisplayed ? <Slider checked={this.state.checked} showWidget={this.showWidget} /> : null;
return (
<div>
<Settings showSlider={this.showSlider} />
{displaySlider}
{displayBatchWidget}
</div>
);
}
}
When I try to debug, it shows:
Warning: Failed form propType: You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`. Check the render method of `Slider`.
I think it is self-explanatory.
I've changed the line with checkbox to:
<input type="checkbox" checked={props.checked} onChange= {props.showWidget} />
Now, the batchWidget should hide and show on each click.
Reactjs matrial ui table check box hide
first do
<Table selectable={false}>
<TableHeader displaySelectAll={false} adjustForCheckbox={false}>
this method hide table header check box
then do <TableBody displayRowCheckbox={false}>
it hide table body checkbox
it work perfect.
reactjs

Resources