How to reset input value after clicking submit button? - javascript

import React, { useState } from 'react';
import './StockQuotes.css';
import { createMuiTheme, withStyles, makeStyles, ThemeProvider } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import TableContainer from '#material-ui/core/TableContainer';
import TableHead from '#material-ui/core/TableHead';
import TableRow from '#material-ui/core/TableRow';
import Paper from '#material-ui/core/Paper';
import stockData from '../util/stockData';
import { useDispatch } from 'react-redux';
import { getStocksOwned } from '../slices/stocksOwnedSlice';
const StockQuotes = () => {
const [sharesToBuy, setSharesToBuy] = useState(stockData);
const dispatch = useDispatch();
const handleChange = (event, index) => {
stockData[index].owned = parseInt(event.target.value);
setSharesToBuy(stockData);
}
const handleClick = (event, index) => {
event.preventDefault();
dispatch(getStocksOwned(sharesToBuy));
stockData[index].owned = 0;
}
const StyledTableCell = withStyles((theme) => ({
head: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
body: {
fontSize: 14,
},
}))(TableCell);
const StyledTableRow = withStyles((theme) => ({
root: {
'&:nth-of-type(odd)': {
backgroundColor: theme.palette.action.hover,
},
},
}))(TableRow);
const useStyles = makeStyles((theme) => ({
margin: {
margin: theme.spacing(1),
},
table: {
minWidth: 700,
},
}));
const classes = useStyles();
const theme = createMuiTheme({
palette: {
primary: {main: '#00e676'},
},
});
return(
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="customized table">
<TableHead>
<TableRow>
<StyledTableCell>Stock Name</StyledTableCell>
<StyledTableCell align="right">Current Price</StyledTableCell>
<StyledTableCell align="right">Shares</StyledTableCell>
<StyledTableCell align="right">Order</StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{stockData.map((stock, index) => (
<StyledTableRow key = {index} >
<StyledTableCell component="th" scope="row">
{stock.name}
</StyledTableCell>
<StyledTableCell align="right">${stock.price}</StyledTableCell>
<StyledTableCell align="right"><input type="number" onChange={event => handleChange(event, index)}></input></StyledTableCell>
<StyledTableCell align="right">
<ThemeProvider theme={theme}>
<Button variant="contained" color="primary" className={classes.margin} onClick={event => handleClick(event, index)}>
BUY
</Button>
</ThemeProvider>
</StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
)
}
export default StockQuotes;
I'm trying to get stockData[index].owned to 0 after I click submit button. Usually straight forward, but this time 8 input fields are created through mapping stockData json file which contains 8 objects. So if I put value property by input tag then type, entire 8 input field changes. So I coded this way except after I click, I get "TypeError: Cannot assign to read only property 'owned' of object '#'". What can I do?

A few things going on here:
stockData appears to just be an initial value for setting the component state. Once state is initialized, update and read from sharesToBuy so those changes flow to your component tree.
Don't mutate objects/state. That can cause bugs and prevent your component from updating properly. Instead, create a new copy with updated values.
Make your input controlled and update the value based on state changes.
// Convenience function for updating state.
const updateOwnedValue = (index, owned) => {
// Instead of mutating the array/object, create a copy with the updated values
// and then update state so the changes flow down to your components.
const newState = sharesToBuy.map((stock, idx) => (
idx === index ? { ...stock, owned } : stock
));
setSharesToBuy(newState);
};
const handleChange = (event, index) => {
updateOwnedValue(index, parseInt(event.target.value));
}
const handleClick = (event, index) => {
event.preventDefault();
dispatch(getStocksOwned(sharesToBuy));
updateOwnedValue(index, 0);
}
...
// Iterate over component state instead of the imported value.
{sharesToBuy.map((stock, index) => (
...
<input
type="number"
onChange={event => handleChange(event, index)}
value={
// This makes your input a controlled component.
// The value will respond to state changes.
stock.owned
}
/>
...
))}

in handleClick function, at last reset your setSharesToBuy
as like, setSharesToBuy('');

Clearing an Input
In order to reset the input upon hitting submit, you want the input to be a controlled component. This means that is gets its value from you passing it a value prop rather than storing it locally. Your handler for the submit event is then tasked with setting that value to 0.
Material UI supports both controlled and uncontrolled components so this should be an easy change, but it's made complicated by the messy control of data in your app. Where does the this entered value come from? Which value needs to change in order to set it to 0?
Data Structure
You need to think about what data exists and where it is stored. Data from from your json file should be used once to populate the initialState of your app or its redux store and that's it.
Right now you are actually mutating the stockData value which you imported from the json file. This is really bad design.
You can have some information which is stored in redux and some which is stored in the local component state, but it shouldn't be the same data in both places. The local state would be for local changes, like the number entered in the form before hitting the "Buy" button. While the current price for each stock should be stored in redux and accessed via useSelector.
This Revision isn't perfect, but hopefully it puts you on the right track.
Nested Components
In the demo that I linked to above you might notice that the input component loses focus when you type a character. The reason that this happens is that you have defined the components StyledTableCell and StyledTableRow inside of your StockQuotes component. That means that these components will get recreated every time that the state changes. The input is not seen as the same input because the table cell that it's inside of is a different table cell and that's why it loses focus.
You want to define your styled components as top level components outside of your StockQuotes component. (I tried just copy and pasting them outside but this introduced other errors. As a temporary hack solution you could useMemo to avoid re-creation, but this is not ideal.)

Related

Why in react input field loses focus? [duplicate]

In my component below, the input field loses focus after typing a character. While using Chrome's Inspector, it looks like the whole form is being re-rendered instead of just the value attribute of the input field when typing.
I get no errors from either eslint nor Chrome Inspector.
Submitting the form itself works as does the actual input field when it is located either in the render's return or while being imported as a separate component but not in how I have it coded below.
Why is this so?
Main Page Component
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actionPost from '../redux/action/actionPost';
import InputText from './form/InputText';
import InputSubmit from './form/InputSubmit';
class _PostSingle extends Component {
constructor(props, context) {
super(props, context);
this.state = {
post: {
title: '',
},
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onChange(event) {
this.setState({
post: {
title: event.target.value,
},
});
}
onSubmit(event) {
event.preventDefault();
this.props.actions.postCreate(this.state.post);
this.setState({
post: {
title: '',
},
});
}
render() {
const onChange = this.onChange;
const onSubmit = this.onSubmit;
const valueTitle = this.state.post.title;
const FormPostSingle = () => (
<form onSubmit={onSubmit}>
<InputText name="title" label="Title" placeholder="Enter a title" onChange={onChange} value={valueTitle} />
<InputSubmit name="Save" />
</form>
);
return (
<main id="main" role="main">
<div className="container-fluid">
<FormPostSingle />
</div>
</main>
);
}
}
_PostSingle.propTypes = {
actions: PropTypes.objectOf(PropTypes.func).isRequired,
};
function mapStateToProps(state) {
return {
posts: state.posts,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actionPost, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(_PostSingle);
Text Input Component
import React, { PropTypes } from 'react';
const InputText = ({ name, label, placeholder, onChange, value, error }) => {
const fieldClass = 'form-control input-lg';
let wrapperClass = 'form-group';
if (error && error.length > 0) {
wrapperClass += ' has-error';
}
return (
<div className={wrapperClass}>
<label htmlFor={name} className="sr-only">{label}</label>
<input type="text" id={name} name={name} placeholder={placeholder} onChange={onChange} value={value} className={fieldClass} />
{error &&
<div className="alert alert-danger">{error}</div>
}
</div>
);
};
InputText.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.string,
error: PropTypes.string,
};
InputText.defaultProps = {
value: null,
error: null,
};
export default InputText;
Submit Button Component
import React, { PropTypes } from 'react';
const InputSubmit = ({ name }) => {
const fieldClass = 'btn btn-primary btn-lg';
return (
<input type="submit" value={name} className={fieldClass} />
);
};
InputSubmit.propTypes = {
name: PropTypes.string,
};
InputSubmit.defaultProps = {
name: 'Submit',
};
export default InputSubmit;
it is because you are rendering the form in a function inside render().
Every time your state/prop change, the function returns a new form. it caused you to lose focus.
Try putting what's inside the function into your render directly.
<main id="main" role="main">
<div className="container-fluid">
<FormPostSingle />
</div>
</main>
===>
<main id="main" role="main">
<div className="container-fluid">
<form onSubmit={onSubmit}>
<InputText name="title" label="Title" placeholder="Enter a title" onChange={onChange} value={valueTitle} />
<InputSubmit name="Save" />
</form>
</div>
</main>
This happened to me although I had keys set!
Here's why:
I was using a key from a text field. Inside the same block; I had an input field to update the value of the same text field. Now, since component keys are changing, react re-renders the UI. Hence loosing focus.
What to take from this:
Don't use keys which are constantly changing.
What's happening is this:
When your onChange event fires, the callback calls setState with the new title value, which gets passed to your text field as a prop. At that point, React renders a new component, which is why you lose focus.
My first suggestion would be to provide your components keys, particularly the form and the input itself. Keys allow React to retain the identity of components through renders.
Edit:
See this documentation on keys: https://reactjs.org/docs/lists-and-keys.html#keys
Example:
<TextField
key="password" // <= this is the solution to prevent re-render
label="eMail"
value={email}
variant="outlined"
onChange={(e) => setEmail(e.target.value)}
/>
Had the same issue and solved it in a quick & easy manner: just calling the component with {compName()} instead of <compName />
For instance, if we had:
const foo = ({param1}) => {
// do your stuff
return (
<input type='text' onChange={onChange} value={value} />
);
};
const main = () => (
<foo param1={true} />
);
Then, we just need to change the way we call the foo() component:
const main = () => (
{foo({param1: true})}
);
By adding
autoFocus="autoFocus"
in the input worked for me
<input
type="text"
autoFocus="autoFocus"
value = {searchString}
onChange = {handleChange}
/>
You have to use a unique key for the input component.
<input key="random1" type="text" name="displayName" />
The key="random1" cannot be randomly generated.
For example,
<div key={uuid()} className='scp-ren-row'>
uuid() will generate a new set of string for each rerender. This will cause the input to lose focus.
If the elements are generated within a .map() function, use the index to be part of the key.
{rens.map((ren,i)=>{
return(
<div key={`ren${i+1}`} className='scp-ren-row'>
{ren}{i}
</div>)
}
This will solve the issue.
I also had this problem, my problem was related to using another component to wrap the textarea.
// example with this problem
import React from 'react'
const InputMulti = (props) => {
const Label = ({ label, children }) => (
<div>
<label>{label}</label>
{ children }
</div>
)
return (
<Label label={props.label}>
<textarea
value={props.value}
onChange={e => props.onChange(e.target.value)}
/>
</Label>
)
}
export default InputMulti
when the state changed, react would render the InputMulti component which would redefine the Label component every time, meaning the output would be structurally the same, but because of JS, the function would be considered !=.
My solution was to move the Label component outside of the InputMulti component so that it would be static.
// fixed example
import React from 'react'
const Label = ({ label, children }) => (
<div>
<label>{label}</label>
{ children }
</div>
)
const InputMulti = (props) => {
return (
<Label label={props.label}>
<textarea
value={props.value}
onChange={e => props.onChange(e.target.value)}
/>
</Label>
)
}
export default InputMulti
I've noticed that people often place locally used components inside the component that wants to use it. Usually to take advantage of function scope and gain access to the parent component props.
const ParentComp = ({ children, scopedValue }) => {
const ScopedComp = () => (<div>{ scopedValue }</div>)
return <ScopedComp />
}
I never really thought of why that would be needed, since you could just prop-drill the props to the internal function and externalise it from the parent comp.
This problem is a perfect example of why you should always externalise your components from each other, even if they are used in one module. Plus you can always use smart folder structures to keep things close by.
src/
components/
ParentComp/
ParentComp.js
components/
ScopedComp.js
I had a similar issue when using styled-components inside a functional component. The custom input element was losing focus every time I typed a character.
After much searching and experimenting with the code, I found that the styled-components inside the functional component was making the input field re-render every time I typed a character as the template literal syntax made the form a function although it looks like an expression inside Devtools. The comment from #HenryMueller was instrumental in making me think in the right direction.
I moved the styled components outside my functional component, and everything now works fine.
import React, { useState } from "react";
import styled from "styled-components";
const StyledDiv = styled.div`
margin: 0 auto;
padding-left: 15px;
padding-right: 15px;
width: 100%;
`;
const StyledForm = styled.form`
margin: 20px 0 10px;
`;
const FormInput = styled.input`
outline: none;
border: 0;
padding: 0 0 15px 0;
width: 100%;
height: 50px;
font-family: inherit;
font-size: 1.5rem;
font-weight: 300;
color: #fff;
background: transparent;
-webkit-font-smoothing: antialiased;
`;
const MyForm = () => {
const [value, setValue] = useState<string>("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if(value.trim() === '') {
return;
}
localStorage.setItem(new Date().getTime().toString(), JSON.stringify(value));
setValue('');
}
return (
<StyledDiv>
<StyledForm onSubmit={handleSubmit}>
<FormInput type="text"
id="inputText"
name="inputText"
placeholder="What Do You Want To Do Next?"
value={value}
onChange={handleChange}/>
</StyledForm>
</StyledDiv>
)
}
export default MyForm;
The best way to use styled-components in cases like this would be to save them in separate files and import them.
My issue was it was rerendering in a stateless component in the same file. So once I got rid of that unecessary stateless component and just put the code in directly, I didn't have unecessary rerenders
render(){
const NewSocialPost = () =>
<div className='new-post'>
<input
onChange={(e) => this.setState({ newSocialPost: e.target.value })}
value={this.state.newSocialPost}/>
<button onClick={() => this._handleNewSocialPost()}>Submit</button>
</div>
return (
<div id='social-post-page'>
<div className='post-column'>
<div className='posts'>
<Stuff />
</div>
<NewSocialPost />
</div>
<MoreStuff />
</div>
I'm new to React, and have been running into this issue.
Here's what I did to solve:
First move all of your components into your components folder and then import them where you want to use them
Make sure all of your form elements get a name and id property
Make sure all components as you walk up the tree get a unique key
Someone smarter than me can probably tell us why we can skip step one and keep everything inline so to speak, but this just helped me organize the code.
I think the real issue is React is rerendering everything (as already stated) and sometimes that rerender is happening on a parent component that doesn't have a key but needs one.
My problem was with ExpansionPanel components wrapping my custom components for form inputs. The panels needed key as well!
Hope this helps someone else out there, this was driving me crazy!
Basically create a ref and assign it to the input element
const inputRef = useRef(null); // Javascript
const inputRef = useRef<HTMLInputElement>(null); // Typescript
// In your Input Element use ref and add autofocus
<input ref={inputRef} autoFocus={inputRef.current === document.activeElement} {...restProps} />
This will keep the input element in focus when typing.
The problem is with dynamic render() caused by useState() function so you can do this for example.
in this code you should use onChange() to get just the new updated data and onMouseLeave() to handle the update but with condition that data is changed to get better performance
example child
export default function Child(){
const [dataC,setDataC]=useState()
return(<Grid>
<TextField
.
.
onChange={(r)=> setDataC(r.target.value) }
onMouseLeave={(e)=> {
if(dataC!=props.data) { // to avoid call handleupdate each time you leave the textfield
props.handlechange(e.target.value) // update partent.data
}
}
/>
</Grid>)
}
exmple parent
export default function Parent(){
const [data,setData]=useState()
return(
<Grid>
<Child handlechange={handlechanges} data={data}/>
</Grid>)
}
I was facing the same issue, as soon as I type any character, it was losing focus. adding autoFocus props helped me to resolve this issue.
TypeScript Code Snippet
Solution -
Add a unique key to the input element because it helps React to identify which item has changed(Reconciliation). Ensure that your key should not change, it has to be constant as well as unique.
If you are defining a styled component inside a react component. If your input element is inside that styled component then define that styled component outside the input's component. Otherwise, on each state change of the main component, it will re-render your styled component and input as well and it will lose focus.
import React, { useState } from "react";
import styled from "styled-components";
const Container = styled.div`
padding: 1rem 0.5rem;
border: 1px solid #000;
`;
function ExampleComponent() {
// Container styled component should not be inside this ExampleComponent
const [userName, setUserName] = useState("");
const handleInputChange = event => {
setUserName(event.target.value);
};
return (
<React.Fragment>
<Container> {/* Styled component */}
<input
key="user_name_key" // Unique and constant key
type="text"
value={userName}
onChange={handleInputChange}
/>
</Container>
</React.Fragment>
);
}
export default ExampleComponent;
In my case, I had this on a child,
//in fact is a constant
const RenderOnDelete=()=>(
<> .
.
<InputText/>
.
.
</>
)
//is a function that return a constant
const RenderOnRadioSelected=()=>{
switch (selectedRadio) {
case RADIO_VAL_EXIST:
return <RenderOnExist/>
case RADIO_VAL_NEW:
return <RenderOnNew/>
case RADIO_VAL_DELETE:
return <RenderOnDelete/>
default:
return <div>Error</div>
}
}
and this in the parent
return(
<>
.
<RenderOnRadioSelected/>
.
</>
)
Y solved it by not calling a component but a function() or a constant, depending on the case.
.
.
.
//in fact is a constant
const RenderOnDelete=(
<> .
.
<InputText/>
.
.
</>
)
//is a function that return a constant
const RenderOnRadioSelected=()=>{
switch (selectedRadio) {
case RADIO_VAL_EXIST:
return {RenderOnExist}
case RADIO_VAL_NEW:
return {RenderOnNew}
case RADIO_VAL_DELETE:
return {RenderOnDelete}//Calling the constant
default:
return <div>Error</div>
}
}
and this in the parent
return(
<>
.
{RenderOnRadioSelected()}//Calling the function but not as a component
.
</>
)
Adding yet another answer: This happened to me when returning a higher order component inside another component. Eg instead of:
/* A function that makes a higher order component */
const makeMyAwesomeHocComponent = <P, >(Component: React.FC<P>) => {
const AwesomeComponent: React.FC<P & AwesomeProp> = (props) => {
const { awesomeThing, ...passThroughProps } = props;
return (
<strong>Look at: {awesomeThing}!</strong>
<Component {...passThroughProps} />
);
}
return AwesomeComponent;
}
/* The form we want to render */
const MyForm: React.FC<{}> = (props) => {
const MyAwesomeComponent: React.FC<TextInputProps & AwesomeProp> =
makeMyAwesomeHocComponent(TextInput);
return <MyAwesomeComponent awesomeThing={"cat"} onChange={() => { /* whatever */ }} />
}
Move the call to create the higher order component out of the thing you're rendering.
const makeMyAwesomeHocComponent = <P, >(Component: React.FC<P>) => {
const AwesomeComponent: React.FC<P & AwesomeProp> = (props) => {
const { awesomeThing, ...passThroughProps } = props;
return (
<strong>Look at: {awesomeThing}!</strong>
<Component {...passThroughProps} />
);
}
return AwesomeComponent;
}
/* We moved this declaration */
const MyAwesomeComponent: React.FC<TextInputProps & AwesomeProp> =
makeMyAwesomeHocComponent(TextInput);
/* The form we want to render */
const MyForm: React.FC<{}> = (props) => {
return <MyAwesomeComponent awesomeThing={"cat"} onChange={() => { /* whatever */ }} />
}
Solution for this problem is to use useCallback It is used to memoize functions which means it caches the return value of a function given a set of input parameters.
const InputForm = useCallback(({ label, lablevalue, placeholder, type, value,setValue }) => {
return (
<input
key={label}
type={type}
value={value}
onChange={(e) => setIpValue(e.target.value)}
placeholder={placeholder}
/>
);
},[]);
Hope it will solve your problem
If you happen to be developing atomic components for your app's design system, you may run into this issue.
Consider the following Input component:
export const Input = forwardRef(function Input(
props: InputProps,
ref: ForwardedRef<HTMLInputElement>,
) {
const InputElement = () => (
<input ref={ref} {...props} />
);
if (props.icon) {
return (
<span className="relative">
<span className="absolute inset-y-0 left-0 flex items-center pl-2">
<label htmlFor={props.id} className="p-1 cursor-pointer">
{icon}
</label>
</span>
<InputElement />
</span>
);
} else {
return <InputElement />;
}
});
It might seem like a simple optimization at first to reuse your input element across both branches of your conditional render. However, anytime the parent of this component re-renders, this component re-renders, and when react sees <InputElement /> in the tree, it's going to render a new <input> element too, and thus, the existing one will lose focus.
Your options are
memoize the component using useMemo
duplicate some code and define the <input> element in both branches of the conditional render. in this case, it's okay since the <input> element is relatively simple. more complex components may need option 1
so your code then becomes:
export const Input = forwardRef(function Input(
props: InputProps,
ref: ForwardedRef<HTMLInputElement>,
) {
if (props.icon) {
return (
<span className="relative">
<span className="absolute inset-y-0 left-0 flex items-center pl-2">
<label htmlFor={props.id} className="p-1 cursor-pointer">
{icon}
</label>
</span>
<input ref={ref} {...props} />
</span>
);
} else {
return <input ref={ref} {...props} />;
}
});
I did the following steps:
Move dynamic component outside a function
Wrap with useMemo function
const getComponent = (step) =>
dynamic(() => import(`#/components/Forms/Register/Step-${step}`), {
ssr: false,
});
And call this function inside the component by wrapping useMemo:
const CurrentStep = useMemo(() => getComponent(currentStep), currentStep]);
I'm very late but I have been tracking down this issue for days now and finally fixed it. I hope it helps someone.
I'm using Material-ui's Dialog component, and I wanted the dialog to show when a menu item was clicked. Something like so:
import React, { useState } from "react";
import {
Menu,
MenuItem,
Dialog,
DialogContent,
TextField,
} from "#mui/material";
const MyMenu = () => {
const [open, setOpen] = useState(false);
return (
<Menu>
<MenuItem>option 1</MenuItem>
<MenuItem onClick={() => setOpen(!open)}>
option 2
<Dialog open={open}>
<DialogContent>
<TextField />
</DialogContent>
</Dialog>
</MenuItem>
</Menu>
);
};
I was having issues with the TextField losing focus, but only when hitting the a, s, d, c and v keys. If I hit any one of those keys, it would not type anything in the textfield and just lose focus. My assumption upon fixing the issue was that some of the menu options contained those characters, and it would try to switch focus to one of those options.
The solution I found was to move the dialog outside of the Menu component:
const MyMenu = () => {
const [open, setOpen] = useState(false);
return (
<>
<Menu>
<MenuItem>option 1</MenuItem>
<MenuItem onClick={() => setOpen(!open)}>
option 2
</MenuItem>
</Menu>
<Dialog open={open}>
<DialogContent>
<TextField />
</DialogContent>
</Dialog>
</>
);
};
I am unable to find anyone with my specific issue online, and this was the post that came up at the top in my searches so I wanted to leave this here. Cheers
I am not authorised to comment then it must be an answer. I had similar issue and Answer from Alex Yan was corect.
Namely I had that function
const DisplaySearchArea =()=>{return (arrayOfSearchFieldNames.map((element, index)=>{return(<div key ={index} className = {inputFieldStyle}><input placeholder= {arrayOfPlaceholders[index]} type="text" className='border-0'
value={this.state[element]}
onChange={e => {this.setState({ [element]: e.target.value }); console.log(e.target)}}
onMouseEnter={e=>e.target.focus()}/></div>)}))}
that behaves OK with FF and not with Chrome when rendered as <DisplaySearchArea />
When render as {...} it's OK with both. That is not so 'beaty' looking code but working, I have already been told to have tendency to overuse lambdas.
Thanks, Alex. This way I solved my issue:
constructor(props, context) {
...
this.FormPostSingle = this.FormPostSingle.bind(this);
}
FormPostSingle() {
const onChange = this.onChange;
const onSubmit = this.onSubmit;
const valueTitle = this.state.post.title;
return (
<form onSubmit={onSubmit}>
<InputText name="title" label="Title" placeholder="Enter a title" onChange={onChange} value={valueTitle} />
<InputSubmit name="Save" />
</form> );
}
render() {
let FormPostSingle = this.FormPostSingle
return...
}
set the correct id, make sure no other component has same id, set it unique, and it should not change on state update, most common mistake is updating the id with changed value on state update
I had this issue, it was being cause by react-bootstrap/Container, once I got rid of it, included a unique key for every form element, everything worked fine.
For the ones on React Native facing the issue where the text input goes out of focus after typing in single character.
try to pass your onChangeText to your TextInput component.
eg:
const [value, setValue] = useState("")
const onChangeText = (text) => {
setValue(text)
}
return <TextInput value={value} onChangeText={onChangeText} />
This is a great question, and I had the same problem which was 3 parts.
RandomGenerated keys.
Wrong event type.
wrong react JSX attribute.
Keys: when you use random keys each rerender causes react to lose focus (key={Math.random()*36.4621596072}).
EventTypes: onChange cause a rerender with each key stroke, but this can also cause problems. onBlur is better because it updates after you click outside the input. An input, unless you want to "bind" it to something on the screen (visual builders), should use the onBlur event.
Attributes: JSX is not HTML and has it's own attributes (className,...).
Instead of using value, it is better to use defaultValue={foo} in an input.
once I changes these 3 things it worked great. Example below.
Parent:
const [near, setNear] = useState( "" );
const [location, setLocation] = useState( "" );
<ExperienceFormWhere
slug={slug}
questionWhere={question_where}
setLocation={handleChangeSetLocation}
locationState={location}
setNear={setNear}
nearState={near}
key={36.4621596072}/>
Child:
<input
defaultValue={locationState}
className={slug+"_question_where_select search_a_location"}
onBlur={event => setLocation(event.target.value)}/>
I did it with a useRef on input and useEffect
For me this was happening inside Material UI Tabs. I had a search input filter which filtered the table records below it. The search input and table were inside the Tab and whenever a character was typed the input would lose focus (for the obvious reason of re render, the whole stuff inside a tab).
I used the useRef hook for input field ref and then inside my useEffect I triggered the input's focuswhenever the datalist changed. See the code below
const searchInput = useRef();
useEffect(() => {
searchInput.current.focus();
}, [successfulorderReport]);
If working with multiple fields – and they have to be added and removed dynamically for whatever reason – you can use autofocus. You have to keep track of the focus yourself, though. More or less like this:
focusedElement = document.activeElement.id;
[…]
const id = 'dynamicField123'; // dynamically created.
<Input id={id} key={id} {...(focusedElement === id ? { autoFocus: true } : {})} />
This issue got me for a second. Since I was using Material UI, I tried to customize one of the wrapper components of my form using the styled() API from material UI. The issue was caused due to defining the DOM customization function inside my render function body. When I removed it from the function body, it worked like a charm. So my inspection is, whenever I updated the state, it obviously tried to refresh the DOM tree and redeclare the styled() function which is inside the render body, which gave us a whole new reference to the DOM element for that wrapper, resulting in a loss of focus on that element. This is just my speculation, please enlighten me if I am wrong.
So removing the styled() implementation away from the render function body solved the issue for me.
This is silly, but... are you (reader, not OP) setting disabled={true} ever?
This is a silly contribution, but I had a problem very much like the one this page is talking about. I had a <textarea> element inside a component that would lose focus when a debounce function concluded.
Well, I realized I was on the wrong track. I was setting the <textarea> to disabled={true} whenever an auto-save function was firing because I didn't want to let the user edit the input while their work was being saved.
When a <textarea> is set to be disabled it will lose focus no matter what trick you try shared here.
I realized there was zero harm in letting the user continue to edit their input while the save was occurring, so I removed it.
Just in case anyone else is doing this same thing, well, that might be your problem. 😅 Even a senior engineer with 5 years of React experience can do things that dumb.

Trying to get data from child of a child in ReactJS

I’ve read that you can’t really pass props upwards like that, but you can do so through functions. I went and found a workaround and it worked.
My problem is: I’m trying to get data to App from 2 layers down - App > DataInput > ValidateUser - so I can pass it over from App to 2 other different components that have nothing to do with filling the form.
I can get the data from ValidateUser back to DataInput, just before sending it to App I’m logging the result and it’s all as expected. The problem begins when I try to send it to App and the data I receive is undefined.
I checked a few times to see if I was making a typo or logical error while implementing the second data call. Unless I completely missed it, nothing. I started to think that, maybe, then, the problem might be with the execution order. App is read first so, maybe, it wasn’t getting updated once I assigned the value further down the execution line? But then, I’m updating the state when I click the button and it prints out the undefined and the blank object again being called from App while I can see the object is fully filled when it’s last called over the handler in DataInput…
I’m probably missing something here.
App
import "./App.css";
import Box from "#mui/material/Box";
import React from "react";
import DataInput from "./components/DataInput";
import UserDataTable from "./components/UserDataTable";
const App = () => {
let userData;
let formSubmited = false;
const getUserData = (params) => {
console.log(params);
userData = { ...userData, ...params };
console.log(userData);
};
return (
<div className="App">
<Box
sx={{
width: 1000,
height: 600,
backgroundColor: "rgba(197, 202, 255, 1)",
m: 4,
border: 1.4,
borderColor: "rgba(140, 150, 255, 1)",
}}
>
<DataInput sendData={getUserData} />
<UserDataTable />
<div className="Author">
<h3>André Lourenço</h3>
</div>
</Box>
</div>
);
};
export default App;
DataInput
import {
TextField,
Select,
MenuItem,
FormControl,
InputLabel,
} from "#mui/material";
import { LoadingButton } from "#mui/lab";
import React, { useRef, useState } from "react";
import SaveIcon from "#mui/icons-material/Save";
import "../styles/DataInput.css";
import ValidateUser from "./ValidateUser";
export default function DataInput(props) {
//State & Ref Hooks
const [country, setCountry] = useState("");
const handleCountryChange = (event) => setCountry(event.target.value);
const countryInputRef = useRef();
const nameInputRef = useRef();
const surnameInputRef = useRef();
const birthdayInputRef = useRef();
const [rawData, setRawData] = useState("");
// I/O
let returnUserData;
const handleFormSubmission = (event) => {
event.preventDefault();
let data = {
name: "",
surname: "",
country: "",
birthday: "",
};
data.name = nameInputRef.current.value;
data.surname = surnameInputRef.current.value;
data.country = countryInputRef.current.value;
data.birthday = birthdayInputRef.current.value;
setRawData(data);
};
const getValidatedData = (params) => {
returnUserData = { ...returnUserData, ...params };
returnUserData.isSubmited = true;
console.log(returnUserData);
};
const handleSendData = (data) => props.sendData(data);
return (
<div className="DataInput">
<form>
<div className="Input-Boxes">
<div className="Box-Name">
<TextField
sx={{ input: { color: "blue" } }}
label="Name"
inputRef={nameInputRef}
required
/>
</div>
<div className="Box-Surname">
<TextField
sx={{ input: { color: "blue" } }}
label="Surname"
inputRef={surnameInputRef}
required
/>
</div>
<div className="Box-Country">
<FormControl variant="filled" sx={{ minWidth: 220 }}>
<InputLabel id="demo-simple-select-filled-label">
Countries
</InputLabel>
<Select
required
labelId="demo-simple-select-filled-label"
id="demo-simple-select-filled"
label="Countries"
value={country}
autoWidth
onChange={handleCountryChange}
inputRef={countryInputRef}
>
<MenuItem value={"Brazil"}> Brazil </MenuItem>
<MenuItem value={"Portugal"}> Portugal </MenuItem>
</Select>
</FormControl>
</div>
<div className="Box-Birthday">
<TextField
sx={{ input: { color: "blue" } }}
label="Birthday"
inputRef={birthdayInputRef}
type="date"
InputLabelProps={{ shrink: true }}
required
/>
</div>
</div>
<div className="Button-Save">
<LoadingButton
loadingPosition="end"
endIcon={<SaveIcon />}
variant="outlined"
type="submit"
onClick={handleFormSubmission}
>
Save
</LoadingButton>
<ValidateUser data={rawData} sendData={getValidatedData} />
</div>
</form>
{handleSendData(returnUserData)}
</div>
);
}
Your implementation to send data to App component is correct. But you don't need handleSendData function and you are calling it from return which is wrong. You can send data to App from inside getValidatedData function like below.
const getValidatedData = (params) => {
returnUserData = { ...returnUserData, ...params };
returnUserData.isSubmited = true;
console.log(returnUserData);
props.sendData(returnUserdata)
};
You Have To Chooses
1.use Glabal State
2.declare states in top-parent component and send them to children components
as props
Trying to find a work around to get data back up to the parent components has never worked to well for me. I would recommend using a library such as Redux. With Redux you can put data in its current state into a Store. This data can then be called into any component within your React App. React-Redux has hooks that allows you to get this data. Although Redux does take a good amount of set-up and a learning curve it might work for your projects needs. Redux-Toolkit can help with some of the set-up.
Some tips on using Redux. Redux wraps your React app, so make sure that you don't miss this step. Your gonna need a Store that will hold the current state of your data. Your gonna use hooks to store and get your data. Primarily the useDispatch hook to update your Store data and useSelector to grab your data.
Once you have Redux all set-up you can do useDispatch(returnUserData). To save your data to the store. You could then call the data into App.js using let data = useSelector(state => state.data.setdata). Of course what is actually inside of useSelector needs to match your Store set-up.

React-Native: how to update a components value (and display the updated value) when a separate component's value changes

In the code below, for React-Native, I would like the the NumericInput for 'count' to update and be displayed when the number of 'containers' changes. The input for 'count' must be able to be modified by user input as well as be the result of the calculation in the onChange function from 'containers'.
How can I do this in React-Native?
import React, { useState } from 'react';
import NumericInput from 'react-native-numeric-input';
const Plot = () => {
const [count, setCount] = useState(0);
const [countainers, setContainers] = useState(0);
return (
<View>
{/* input for number of containers ... each container has 10 items */}
<NumericInput
value={countainers}
onChange={c => { setContainers(c); setCount(containers * 10); }}
/>
{/* input for total count of items */}
<NumericInput
onChange={c => setCount(c)}
value={count}
/>
</View>
);
}
export default Plot;
It turns out the 'react-native-numeric-input' api is broken for the version and dependency versions I have. The code that was posted should have worked just fine as-is but just didn't. Changing it to any other component (and I chose a simple TextInput that allows 0-9 only) proved this to be the case.

State from react useState is updated when key property is used but requires useEffect or similar method to update otherwise

I have the following component:
import React, { useState } from "react";
import { FormControl, TextField } from "#material-ui/core";
interface IProps {
text?: string;
id: number;
onValueChange: (text: string, id: number) => void;
placeholder: string;
}
export const QuestionTextRow: React.FC<IProps> = (props) => {
const [item, onItemChange] = useState(props.text);
const onChange = (e: React.FormEvent<HTMLInputElement>) => {
const newValue = e.currentTarget.value;
onItemChange(newValue);
props.onValueChange(newValue, props.id);
};
return (
<>
<FormControl fullWidth>
<div>{props.text}</div>
<TextField
aria-label="question-text-row"
onDragStart={(e) => {
e.preventDefault();
e.stopPropagation();
}}
value={item}
onChange={(ev: React.ChangeEvent<HTMLInputElement>): void => {
onChange(ev);
}}
/>
</FormControl>
</>
);
};
It is rendered via the following component:
const renderQuestionOptions = (id: number): JSX.Element => {
const item = props.bases.find((x) => x.sortableId === id);
if (!item) return <> </>;
return (
<div className={classes.questionPremiseRow}>
<div className={classes.rowOutline}>
<QuestionOptionsSortableRow item={item} isDisabled={false} onClickRow={onClickBasisRow}>
<QuestionTextRow
text={item.text ? item.text.text : ""}
id={item.sortableId}
onValueChange={basisValueChanged}
placeholder={intl.formatMessage({ id: "question.create.basis.row.placeholder" })}
></QuestionTextRow>
</QuestionOptionsSortableRow>
</div>
</div>
);
};
It renders the following list:
As you can see props.text and useState item from props.text are rendered equally. If props.text is updated it does not reflect on useState though.
https://stackoverflow.com/a/53846698/3850405
I can solve it by useEffect to make it work:
useEffect(() => {
onItemChange(props.text);
}, [props.text]);
https://reactjs.org/docs/hooks-effect.html
https://stackoverflow.com/a/54866051/3850405
However If I add key={`${item.text?.text}-${item.sortableId}`} to QuestionTextRow it will work without using useEffect. How come?
I know a static unique key should be used but would it not be the same result if key={item.uniqueId} was used?
https://www.npmjs.com/package/react-key-index
The argument passed to useState is the initial state much like setting
state in constructor for a class component and isn't used to update
the state on re-render
https://stackoverflow.com/a/43892905/3850405
However If I add key={${item.text?.text}-${item.sortableId}} to
QuestionTextRow it will work without using useEffect. How come?
That is because of reconciliation. In react, when on one render you have say:
<SomeComponent key={1}/>
If on next render you render same component (at the same place) with different key, say:
<SomeComponent key={2}/>
React will destroy instance related to previous component and create a new instance for this one, hence the useState inside that component will be initialized with the provided text property once again (like when you created the component first time).
If the key is same for some component on previous and next renders and you just change some other props, in this case the component is re-rendered (no instance destroyed), that's why you didn't see the text property reflected in state.
Sometimes it can be tricky to copy props to state like you have in your useEffect solution, I recommend you read this post, it is about classes but same ideas apply.

Handle React re-rendering

I have a similar situation like the one in the sandbox.
https://codesandbox.io/s/react-typescript-fs0em
Basically what I want to achieve is that Table.tsx is my base component and App component is acting like a wrapper component. I am returning the JSX from the wrapper file.
Everything is fine but the problem is whenever I hover over any name, getData() is called and that is too much rerendering. Here it is a simple example but in my case, in real, the records are more.
Basically Table is a generic component which can be used by any other component and the data to be displayed in can vary. For e.g. rn App is returning name and image. Some other component can use the Table.tsx component to display name, email, and address. Think of App component as a wrapper.
How can I avoid this getData() to not to be called again and again on hover?
Can I use useMemo or what approach should I use to avoid this?
Please help
Every time you update the "hover" index state in Table.jsx it rerenders, i.e. the entire table it mapped again. This also is regenerating the table row JSX each time, thus why you see the log "getData called!" so much.
You've effectively created your own list renderer, and getData is your "renderRow" function. IMO Table shouldn't have any state and the component being rendered via getData should handle its own hover state.
Create some "row component", i.e. the thing you want to render per element in the data array, that handles it's own hover state, so when updating state it only rerenders itself.
const RowComponent = ({ index, name }) => {
const [hov, setHov] = useState();
return (
<div
key={name}
onMouseEnter={() => setHov(index)}
onMouseLeave={() => setHov(undefined)}
style={{ display: "flex", justifyContent: "space-around" }}
>
<div> {name} </div>
<div>
<img
src={hov === index ? img2 : img1}
height="30px"
width="30px"
alt=""
/>
</div>
</div>
);
};
Table.jsx should now only take a data prop and a callback function to render a specific element, getData.
interface Props {
data: string[];
getData: () => JSX.Element;
}
export const Table: React.FC<Props> = ({ data, getData }) => {
return (
<div>
{data.map((name: string, index: number) => getData(name, index))}
</div>
);
};
App
function App() {
const data = ["Pete", "Peter", "John", "Micheal", "Moss", "Abi"];
const getData = (name: string, index: number, hov: number) => {
console.log("getData called!", index);
return <RowComponent name={name} index={index} />;
};
return <Table data={data} getData={getData} />;
}

Categories

Resources