Why is my page reloading after onChange trigger update? - javascript

I have added different forms in different methods but when I type anything in input fields, the page reloads keeping the states and again I have to click on the field and type and same cycle happens. It is working fine if I add everything in return. Can somebody tell explain why is this happening and how to stop it?
I am also sharing a piece of code.
function MyForm() {
const [commentForm, setCommentForm] = useState({
Comment: "",
});
const onCommentChange = (obj) => {
setCommentForm((prevState) => {
return {
...prevState,
...obj,
};
});
};
const IForm = () => (
<Table>
<CardBody>
<Row>
<Col className="col-2">
<Label>Comment: </Label>
</Col>
<Col className="col-1">
<Input type="text"
value={commentForm.Comment}
onChange={(e) =>
onCommentChange({ Comment: e.target.value })} />
</Col>
</Row>
</CardBody>
</Table>
);
return (
<div>
<IForm />
</div>
)
}
export default MyForm

that's because you define IForm as A component inside the current component which is not correct. so you have two solutions.
1 - move IFORM Component outside the current react.
function MyForm() {
const [commentForm, setCommentForm] = React.useState({
Comment: ""
});
const onCommentChange = (obj) => {
setCommentForm((prevState) => {
return {
...prevState,
...obj
};
});
};
return (
<div>
<IForm commentForm={commentForm} onCommentChange={onCommentChange} />
</div>
);
}
export default MyForm;
const IForm = ({ commentForm, onCommentChange }) => (
<Table>
<CardBody>
<Row>
<Col className="col-2">
<Label>Comment: </Label>
</Col>
<Col className="col-1">
<Input type="text"
value={commentForm.Comment}
onChange={(e) =>
onCommentChange({ Comment: e.target.value })} />
</Col>
</Row>
</CardBody>
</Table>
);
2 - declare the IForm as a normal function inside the current component.
function MyForm() {
const [commentForm, setCommentForm] = React.useState({
Comment: ""
});
const onCommentChange = (obj) => {
setCommentForm((prevState) => {
return {
...prevState,
...obj
};
});
};
const form = () => (
<Table>
<CardBody>
<Row>
<Col className="col-2">
<Label>Comment: </Label>
</Col>
<Col className="col-1">
<Input type="text"
value={commentForm.Comment}
onChange={(e) =>
onCommentChange({ Comment: e.target.value })} />
</Col>
</Row>
</CardBody>
</Table>
);
return <div> {form()} </div>;
}
export default MyForm;

The reason is that the IForm component is declared inside the MyForm Component. Which means that whenever the state of MyForm Component changes it will refresh its dom tree. And when the dom will rerender the functional component IForm will be executed again that's why you'll always lose the focus of the input but you never lose the state of the MyForm component.
To stop it from being happening either declare the IForm Component outside of the MyForm component or move the jsx of the IForm inside the Return of MyFOrm component.

You should just setCommentForm the value. I don't think you need to spread the prevState.
What you want to achieve is to set the state value to the new one.
Also, you don't have any useEffect right?

Related

More than needed React components re-rendering when typing in input

I am taking input from a search input field using searchInput and setSearchInput useState hook and after I press submit button, I call fetchSearchData function providing it the input text and setCompanies hook, where companies are updated with the fetched list of companies from the API.
Then companies are passed to another component CompanyList where a map function is called there.
The problem is whenever I type in the search field, the CompanyList component is re-rendered although I did not press submit. I understand that setSearchInput will re-render SearchBar component whenever I type in it, but I don't get why CompanyList re-renders.
Search page source code:
const Search = () => {
const [companies, setCompanies]=useState([]); //List of companies returned from searching
const [searchInput, setSearchInput] = useState(""); //Search field input
//Update search text whenever the user types in
const onSearchChange = (e) => {
setSearchInput(e.target.value)
}
//use the API providing it the search input, and
//setCompanies hook to update list of companies
const onSearchSubmit = (e) => {
e.preventDefault()
fetchSearchData(searchInput, setCompanies)
}
return (
<div>
<Container>
<Row className={"searchFilterBar"}>
<Col sm={6} md={8} className={"searchBar"}>
<SearchBar onSubmit={onSearchSubmit} onChange={onSearchChange} value={searchInput} />
</Col>
<Col sm={6} md={4} className={"filterBar"}>
</Col>
</Row>
<CompanyList companies={companies} ></CompanyList>
<Row>
</Row>
</Container>
</div>
)
}
export default Search;
SearchBar component source code:
const SearchBar = ({value,onSubmit, onChange}) => {
return (
<Form
className="search-form"
onSubmit={onSubmit}
>
<div className="input-group">
<span className="input-group-text rubik-font">
<i className="icon ion-search"></i>
</span>
<input
className="form-control rubik-font"
type="text"
placeholder="Search for companies that start with..."
onChange={onChange}
value={value}
/>
<Button className="btn btn-light rubik-font" type="submit">Search </Button>
</div>
</Form>
)
}
CompanyList component source code:
function MapDataToCompanyList(response) {
console.log(response); //Logging occurs here
if(!response || response===undefined || response.length===0)
{
return (<ErrorBoundary message={noCompaniesError.message}></ErrorBoundary>)
}
return response.map((company) => {
return (
<Col key={company._id} xs={12} md={6} lg={4} className="mt-2">
<CompanyCard
id={company._id}
logo={company.logo}
title={company.name}
logoBackground={company.logoBackground}
progLangs={company.progLangs}
backend={company.backend}
frontend={company.frontend}
url={company.url}
>
</CompanyCard>
</Col>
)
})
}
const CompanyList = (props) => {
const {companies} = props
return (
<div>
<Container className="mt-3">
<Row>
{
MapDataToCompanyList(companies)
}
</Row>
</Container>
</div>
)
}
export default CompanyList;
FetchSearchData function source code:
export const fetchSearchData = (query, cb)=>{
const uri = process.env.NODE_ENV === 'development' ?
`http://localhost:3000/api/companies/name/${query}` :
``;
axios.get(uri, {
timeout: MAX_TIMEOUT
})
.then((response)=>{
cb(response.data.data)
})
.catch((error)=>{
console.log(error)
})
}
As seen above, empty list of companies is logged when the page first loads, then I typed three characters and the it logged three time which means the map function called three times.
Even then if I pressed submit and retrieved list of companies normally, whenever I type it will keep printing the array of companies that was fetched.
Sorry if I missed something, I am still new to React.
When you call setSearchInput(e.target.value), Search component will re-render cause its state has changed. Search component re-renders means every tag nested in it will re-render (except the ones passed via children). That is the normal behaviour of React. If you want to avoid that, you would wanna use React.memo for CompanyList. Or you could use useRef to bind the input like so:
const Search = () => {
const [companies, setCompanies] = useState([]); //List of companies returned from searching
const inputRef = React.useRef(null);
//use the API providing it the search input, and
//setCompanies hook to update list of companies
const onSearchSubmit = (e) => {
e.preventDefault();
fetchSearchData(inputRef.current.value, setCompanies);
inputRef.current.value = "";
};
return (
<div>
<Container>
<Row className={"searchFilterBar"}>
<Col sm={6} md={8} className={"searchBar"}>
<SearchBar inputRef={inputRef} onSubmit={onSearchSubmit} />
</Col>
<Col sm={6} md={4} className={"filterBar"}></Col>
</Row>
<CompanyList companies={companies}></CompanyList>
<Row></Row>
</Container>
</div>
);
};
export default Search;
const SearchBar = ({ onSubmit, inputRef }) => {
return (
<Form className="search-form" onSubmit={onSubmit}>
<div className="input-group">
<span className="input-group-text rubik-font">
<i className="icon ion-search"></i>
</span>
<input
ref={inputRef}
className="form-control rubik-font"
type="text"
placeholder="Search for companies that start with..."
/>
<Button className="btn btn-light rubik-font" type="submit">
Search
</Button>
</div>
</Form>
);
};
I don't get why CompanyList re-renders.
Because it's nested in your Search component, and it's not React.memo'd (or a PureComponent).
Yes, the component is updated, but that doesn't mean it necessarily causes a DOM reconciliation.
In any case, React is completely at liberty of calling your component function as many times as it likes (and indeed, in Strict Mode it tends to call them twice per update to make sure you're not doing silly things), so you should look at side effects (such as console logging) in your component function (which you shouldn't have in the first place) as performance guidelines.
You do not need to maintain a state for input field. You can use useRef and pass it to input like below.
<input
ref={inputRef}
className="form-control rubik-font"
type="text"
placeholder="Search for companies that start with..."
/>
And you can get get value inside onSearchSubmit using inputRef.current.value
This will not re-render you component on input change.

More generic way to set an objects value within React handleChange

I have a component in React, I need to take a value from the radio button being checked and then set that value within an object in my Formik values. If a different radio button is selected within the group I need to set the previously selected one to false, is there a standard way of doing this? I'm using an object within my Formik values as the field holds a date as well as the attribute from the radio button, you can see where I place the date into the object using handleTime, so I can't just null the field and place the new item in.
I'm currently doing the following in my component to update the Formik time_frame value.
import React, {Component} from 'react';
import {Col, Row} from 'react-bootstrap';
import Radio from "./radio";
export default class EventTimeFrame extends Component {
state = {
eventTimeFrame: [
{id: 1, value: "one_off", label: "ONE OFF", checked: false},
{id: 2, value: "recurring", label: "RECURRING", checked: false},
]
}
handleOccurance = value => {
let timeCopy = {...this.props.values.time_frame}
if (value.target.checked) {
if (value.target.value === "one_off") {
timeCopy[value.target.value] = true
timeCopy["recurring"] = false
} else {
timeCopy[value.target.value] = true
timeCopy["one_off"] = false
}
}
this.props.onChange("time_frame", timeCopy)
this.setState(prevState => ({
eventTimeFrame: prevState.eventTimeFrame.map(
el => el.value === value.target.value ? {...el, checked: true} : el
)
}))
};
handleTime = value => {
let timeCopy = {...this.props.values.time_frame}
timeCopy["start"] = new Date(value.target.value);
this.props.onChange("time_frame", timeCopy)
};
render() {
return (
<div>
<Row>
<Col>
<h4 className="ui centered question-header text-center">ONE OFF OR RECURRING EVENT?</h4>
</Col>
</Row>
<Row>
{
this.state.eventTimeFrame.map((timeFrame) => {
return (
<Radio name="time_frame" key={timeFrame.value}
onChange={this.handleOccurance} checked={timeFrame.checked} {...timeFrame} />
)
})
}
</Row>
<Row>
<Col>
<h4 className="question-header date-text">PLEASE ENTER THE FIRST DAY OF YOUR EVENT</h4>
</Col>
<Col>
<input type="date" className="form-control date" name="start"
onChange={this.handleTime}/>
</Col>
</Row>
</div>
)
}
}
I feel like there has to be a standard way of dealing with things like this
You're correct by suspecting there's a simpler way 😃 Some feedback:
Don't store the eventTimeFrame in state, a constant will do.
The standard way of using radio input values in Formik is to define one value in initialState for the entire group. Formik will set its value to the selected option.
Storing all options in values would have been fine if you were using checkboxes instead of radio buttons.
You wrote your own custom onChange handlers, but it would be simpler to just use Formik's provided onChange handlers. I only use my own handler if I have to, for example when formatting a value before setting it with setFieldValue()
See below:
Live Demo
// outside of component
const timeFrameTypes = [
{ id: "one_off", label: "ONE OFF" },
{ id: "recurring", label: "RECURRING" }
];
// in component's render function:
<Formik
initialValues={{
time_frame: {
type: "",
start: ""
}
}}
onSubmit={async (values) => {
// do something with values
}}
>
{({ handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<div>
<Row>
<Col>
<h4 className="ui centered question-header text-center">
ONE OFF OR RECURRING EVENT?
</h4>
</Col>
</Row>
<Row>
{timeFrameTypes.map((timeFrameType) => {
return (
<Field
key={timeFrameType.id}
component={Radio}
name="time_frame.type"
id={timeFrameType.id}
label={timeFrameType.label}
/>
);
})}
</Row>
<Row>
<Col>
<h4 className="question-header date-text">
PLEASE ENTER THE FIRST DAY OF YOUR EVENT
</h4>
</Col>
<Col>
<Field name="time_frame.start">
{({ field }) => <input type="date" {...field} />}
</Field>
</Col>
</Row>
</div>
<br />
<button type="submit">Submit</button>
</form>
);
}}
</Formik>

change Component name with map function

I am trying to change component name.
It is normally like <Dashboard/> and <Table/>. But I want to make like
const names = [ {"name":Dashboard},{"name":Table}]
names.map(c => { <c.name />}
render(){
return(
{names.map(c => {
<Panel>
<Panel.Body>
<Row>
<Col md={4}>
<FormControl
/>
</Col>
</Row>
<hr />
<c.name bla={bla} />
<hr />
</Panel.Body>
</Panel>
)}
According to the doc, you need to declare a capitalized variable first:
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
and then you can call your component name by using your variable.
And in your case, it would be :
{names.map((c) => {
const CapitalizedComponent = c.name;
return <CapitalizedComponent />;
})}
Please also notice that the map function in your code didn't return anything.
working example in sandbox

What typescript type should I use for Reactstrap Input Ref?

I am linking a ref to an <Input> element but having trouble getting the right type to define it as. Currently I have this:
const [location, setLocation] = useState<GeolocationPosition>();
const [search, setSearch] = useState<string>('');
const searchInputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
navigator.geolocation.getCurrentPosition(position => {
setLocation(position);
})
}, [])
const changeSearchHandler = () => {
setSearch(searchInputRef.current!.value);
}
return (
<>
<Row>
<Col className='header'>
<h3 className='mt-3'>IsItOpen?</h3>
</Col>
</Row>
<Row className='mt-4'>
<Col lg={{ size: 6, order: 0, offset: 3}}>
<Card>
<CardHeader>
<CardTitle>
<Input type='text' ref={searchInputRef} onChange={changeSearchHandler} />
</CardTitle>
</CardHeader>
<CardBody>
Your results!
</CardBody>
</Card>
</Col>
</Row>
</>
);
The error is coming from the line of the <Input> telling me:
Type RefObject is not assignable to type LegacyRef
If I change the ref type to Input then I get an error on the value on the setSearch call saying:
Property "Value" does not exist on type Input
I'm not sure what the correct way to go about this is, and I don't want to use any.
React-strap provides another prop, innerRef to let you access the underlying <input />:
const searchInputRef = useRef<HTMLInputElement>(null);
const changeSearchHandler = () => {
setSearch(searchInputRef.current!.value);
};
// ..
<Input
type="text"
innerRef={searchInputRef}
onChange={changeSearchHandler}
/>
Whereas the ref lets you access the react-strap's Input component.

OnClick related function from react component doesn't work

I am trying to pass a state to a component so i can update it's state whenever i type in a component's text field. However this is not working i am not sure why. Most of the examples I've found online were dealing with the similar problems on the same class. However i need to juggle this state between components.
Not only the state doesn't change but if i add the "value={information}" part in the textfield it doesn't let me type.
Here is an example of the code.
Class that uses the component:
class SomeClass extends Component {
state = {
information: '',
};
handleInfoChange(event) {
this.setState({
information: event.target.value,
});
}
render(){
return(
<div>
<TesteComponent
information={this.state.information}
handleInfoChange={this.handleInfoChange}
/>
Component code:
const TesteComponent = ({information}, handleInfoChange) => (
<Dialog
disableEscapeKeyDown
disableBackdropClick
>
<DialogContent>
<DialogContentText>
<p>Emails:</p>
<TextField value={information} className="bootstrapInput" onChange={() => handleInfoChange}/>
</DialogContentText>
</DialogContent>
</Dialog>
PS: I posted solely the part that is giving me trouble since the component in it's entirety works for the exception of the Onchange method problem i am having.
PS2: I forgot to add handleInfoChange being passed to the component in the question. It ahs been updated now.
TesteComponent doesn't have access to handleInfoChange. You can pass that function as a property like this
<TesteComponent
information={this.state.information}
handleInfoChange={this.handleInfoChange}
/>
and then in TesteComponent change it to
const TesteComponent = (props) => (
<Dialog
disableEscapeKeyDown
disableBackdropClick
>
<DialogContent>
<DialogContentText>
<p>Emails:</p>
<TextField value={props.information} className="bootstrapInput" onChange={() => props.handleInfoChange}/>
</DialogContentText>
</DialogContent>
</Dialog>
Firstly, you are not passing handleInfoChange function to TesteComponent as props
Secondly, you can not destructure and use arguments without destructuring together. You should instead write const TesteComponent = ({information, handleInfoChange}) => ( after passing the handleInfoChange as props
const TesteComponent = ({ information , handleInfoChange }) => (
<Dialog
disableEscapeKeyDown
disableBackdropClick
>
<DialogContent>
<DialogContentText>
<p>Emails:</p>
<TextField value={information} className="bootstrapInput" onChange={() => handleInfoChange}/>
</DialogContentText>
</DialogContent>
</Dialog>
SomeClass
class SomeClass extends Component {
state = {
information: '',
};
handleInfoChange(event) {
this.setState({
information: event.target.value,
});
}
render(){
return(
<div>
<TesteComponent
information={this.state.information}
handleInfoChange={this.handleInfoChange}
/>
)
}
}
class SomeClass extends Component {
state = {
information: ''
};
// changed to arrow function to bind 'this'
handleInfoChange = event => {
this.setState({information: event.target.value});
}
render() {
return(
<div>
<TesteComponent
information={this.state.information}
// pass handleInfoChange as a prop
handleInfoChange={this.handleInfoChange}
/>
</div>
);
}
}
const TesteComponent = ({information, handleInfoChange}) => (
<Dialog disableEscapeKeyDown disableBackdropClick>
<DialogContent>
<DialogContentText>
<p>Emails:</p>
<TextField
className="bootstrapInput"
value={information}
onChange={handleInfoChange}
/>
</DialogContentText>
</DialogContent>
</Dialog>
);
first of all you should bind your click event and set in state and here i am going to print change value in console ....
here is my code try this one....
class SomeClass extends Component {
state = {
information: '',
};
this.handleInfoChange= this.handleInfoChange.bind(this);
handleSubmit = event => {
event.preventDefault();
}
handleInfoChange(event) {
this.setState({
information: event.target.value,
console.log(this.state.information);
});
}
render(){
return(
<div>
const TesteComponent = ({information}, handleInfoChange) => (
<Dialog
disableEscapeKeyDown
disableBackdropClick
>
<form onSubmit={this.handleSubmit}>
<DialogContent>
<DialogContentText>
<p>Emails:</p>
<TextField value={information} className="bootstrapInput" onChange={this.handleInfoChange}/>
</DialogContentText>
</DialogContent>
</Dialog></div></form>

Categories

Resources