Whenever I set a state in my React app, it clears out my inputs (check box, numbers). Here's a very barebones version that illustrates the issue.
import React, { useState } from "react";
export default function App() {
const [dummy, setDummy] = useState(false);
function handleMint() {
setDummy((dummy) => !dummy);
}
const MintBTN = () => (
<div>
<div>
<input className="form-control" type="number" max="20" min="1" />
<div>
<input type="checkbox" />
</div>
</div>
<div>
<button onClick={handleMint}>mint</button>
</div>
</div>
);
return <MintBTN />;
}
You can try it out yourself. Add numbers and click the check box and hit mint. Whamo-bamo, your inputs magically disappear.
https://codesandbox.io/s/friendly-buck-6g6oh?file=/src/App.js:0-499
I figure there's something I'm doing wrong that I just don't see yet. I need to be able to click the button or do an onChange event for the inputs without them clearing out. For some reason setting state is also making my page styles freak out. Not sure what's going on. Some of my key dependencies if those are helpful. Thanks.
"react": "^17.0.2",
"webpack": "^4.19.1"
Each time you set a state (setDummy), the component is rerendered and will reset you're inputs because they are uncontrolled. You need to make them controlled by using state hooks for those inputs.
import React, { useState } from "react";
export default function App() {
const [dummy, setDummy] = useState(false);
const [number, setNumber] = useState(0);
const [checked, setChecked] = useState(false);
function handleMint() {
setDummy((dummy) => !dummy);
}
return (
<div>
<div>
<input
className="form-control"
type="number"
max="20"
min="1"
value={number}
onChange={e => setNumber(e.target.value)}
/>
<div>
<input
type="checkbox"
checked={checked}
onChange={e => setChecked(e.target.checked)}
/>
</div>
</div>
<div>
<button onClick={handleMint}>mint</button>
</div>
</div>
);
}
Notice the value and checked properties. They are being set by the hook value on each render. Now, notice the onChange property. Each time you interact with the input it will update the hook value and rerender the component. By letting React manage the value and checked properties, it will save the values of the inputs in each hook.
Take a look at Controlled Components and State Hooks
Update
Removed MintBTN function as Nicholas Tower pointed out
You are creating the MintBTN component inside of your App component. This does not work. Every time App renders, you create a brand new type of component. It may have the same text as the previous one, but it's a new type, so react needs to unmount the old one, and mount a new one, which resets the values of stored in any dom elements, and resets any react state.
Instead, create the component once outside of app.
export default function App() {
const [dummy, setDummy] = useState(false);
function handleMint() {
setDummy((dummy) => !dummy);
}
return <MintBTN onClick={handleMint} />;
}
const MintBTN = ({ onClick }) => {
return (
<div>
<div>
<input className="form-control" type="number" max="20" min="1" />
<div>
<input type="checkbox" />
</div>
</div>
<div>
<button onClick={handleMint}>mint</button>
</div>
</div>
);
};
Related
Why the input only taking inputs from second input only?
import React, { useState } from "react";
import Item from "./Components/Item";
import "./ToDo.css";
function ToDo() {
let toDoIs = document.getElementById("toDoInput");
const [ToDo, setToDoIs] = useState("d");
const [ToDoArray, setToDoArray] = useState([]);
return (
<div>
<h1>ToDo</h1>
<input
id="toDoInput"
onChange={() => {
setToDoIs(toDoIs.value);
}}
type="text"
/>
<button
onClick={() => {
setToDoArray([...ToDoArray, { text: ToDo }]);
toDoIs.value = "";
}}
>
Add
</button>
<Item push={ToDoArray} />
</div>
);
}
export default ToDo;
Why the second input only works, which means whenever I use submit the value from second input only stored and displayed. I don't know why this happens.
There's a few problems here...
Don't use DOM methods in React. Use state to drive the way your component renders
Your text input should be a controlled component
When updating state based on the current value, make sure you use functional updates
import { useState } from "react";
import Item from "./Components/Item";
import "./ToDo.css";
function ToDo() {
// naming conventions for state typically use camel-case, not Pascal
const [toDo, setToDo] = useState("d");
const [toDoArray, setToDoArray] = useState([]);
const handleClick = () => {
// use functional update
setToDoArray((prev) => [...prev, { text: toDo }]);
// clear the `toDo` state via its setter
setToDo("");
};
return (
<div>
<h1>ToDo</h1>
{/* this is a controlled component */}
<input value={toDo} onChange={(e) => setToDo(e.target.value)} />
<button type="button" onClick={handleClick}>
Add
</button>
<Item push={toDoArray} />
</div>
);
}
export default ToDo;
I'm learning Node and React and now know how to integrate them and am working on making registration and login using Node and React. I'm going step by step, so currently I'm trying to at least get the inputs and put them into state as an array, and then after I get that I will go to hashing the password, sending the data to Node and the database, et cetera.
At the moment however, I'm a little bit stuck here. I'm trying to enter the username and password into my "details" state and then render it on the screen (or console log it, or whatever), but when I do it it shows up very quickly and then disappears. Why is the details state reverting to an empty array? How do I fix it? I did some research on here but couldn't figure it out.
import { useState } from 'react';
function App() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [details, setDetails] = useState([]);
const readUsername = (e) => {
setUsername(e.target.value);
}
const readPassword = (e) => {
setPassword(e.target.value);
}
const updateDetails = () => {
setDetails([username, password]);
}
return (
<div>
<h1>Register</h1>
<form>
<label htmlFor="username" name="username">Username: </label>
<input htmlFor="username" name="username" onChange={readUsername} />
<br/>
<label htmlFor="password" name="password">Password: </label>
<input htmlFor="password" name="password" type="password" onChange={readPassword} />
<br/>
<button onClick={updateDetails}>Submit</button>
</form>
<h1>{details}</h1>
</div>
);
}
export default App;
with the onChange handler on your input's, it is considered a "controlled" component. you also need to assign the value prop.
<input onChange={readUsername} value={username} />
Forms in React have the default behaviour as in HTML: refreshing the page upon submission.
React state only exists during the component's life. When you refresh the page, the component is unmounted, and the state is lost.
To prevent the page refresh, use a function to handle the form submission, and prevent the default behaviour.
const handleSubmit = (e) => {
e.preventDefault();
}
return (
...
<form onSubmit={handleSubmit}>
...
</form>
...
);
}
View demo on codesandbox
Further reading:
Forms in React
Preventing default behaviour of events
You want to create a controlled input so you should pass the value={password} attribute
like this:
<input htmlFor="username" name="username" onChange={readUsername} value={username} />
Also, I'd change how you handle to form. Change the button to type="submit"
like this
<button type="submit">Submit</button>
And then handle the submit from the <form>
like this:
<form onSubmit={(event) => updateDetails(event)}
And then on the function, you can use the submit event like this for example
const updateDetails = (e) => {
event.preventDefault()
...rest of logic
}
I tried my best to search for a similar question before posting. I've got a Summary component in my project that accepts three user selected props (part #, start date, and end date), calls an API, then displays the fetched data. My problem is that the component re-renders every time the user changes one of the parameters (e.g. picks a new start date).
Ideally, the user would instead click an "Apply" button that would re-render the component using the set of three props. I tried using React.useRef() to create a reference to the component that I would use to update the Summary's state in a button's onClick event but no luck. I would greatly appreciate any advice on how to structure this situation. I'm editing the question to provide an extremely simple example below.
This is a sample application with an App.js and a Summary.jsx component. The code for App.js is as follows:
import React from "react";
import Summary from "./Components/Summary";
function App() {
const [input1, setInput1] = React.useState("");
const [input2, setInput2] = React.useState("");
const [input3, setInput3] = React.useState("");
return (
<>
<input
type="text"
id="input1"
onChange={(e) => setInput1(e.target.value)}
/>
<input
type="text"
id="input2"
onChange={(e) => setInput2(e.target.value)}
/>
<input
type="text"
id="input3"
onChange={(e) => setInput3(e.target.value)}
/>
<button
type="button"
onClick={() => {
alert("button has been clicked.");
}}
>
Apply
</button>
<Summary i1={input1} i2={input2} i3={input3} />
</>
);
}
export default App;
The code for Summary.jsx (contained in a Components folder) is as follows:
import React from "react";
const Summary = (props) => {
return (
<h1>{`Input 1: ${props.i1} Input 2: ${props.i2} Input 3:
${props.i3}`}</h1>
);
};
export default Summary;
You can see that as the user types into any of the inputs, it automatically re-renders the components as the state changes and thus the props that are supplied to the Summary component. Ideally, I would like no change to occur until the user hits the Apply button (I just supplied a bogus alert message as the onClick functionality for now).
If you don't want the Summary component to be re-rendered every time the parent component changes, I suggest using conditional rendering
Have a state isSubmited that defaults False, and set to True when user clicks Apply
Only render Summary when isSubmmited is True. If false, renders nothing (null)
If you want to switch isSummited back, pass a handler function setBack = () => setSubmited(false) as a props to the appropriate component
Something like this:
// App.js
function App() {
const [input1, setInput1] = React.useState("");
const [input2, setInput2] = React.useState("");
const [isSubmitted, setSubmitted] = React.useState(false);
return (
<>
<input
type="text"
id="input1"
value={input1}
onChange={(e) => setInput1(e.target.value)}
/>
<input
type="text"
id="input2"
value={input2}
onChange={(e) => setInput2(e.target.value)}
/>
<button
type="button"
onClick={() => {
alert("button has been clicked.");
setSubmitted(true);
}}
>
Apply
</button>
{/* Ternary operator */}
{isSumitted ? (
<Summary
i1={input1}
i2={input2}
afterDataFetch={() => setSubmitted(false)}
/>
) : null}
</>
);
}
// Summary.js
function Summary(props) {
const { i1, i2, i3, afterDataFetch } = props;
useEffect(() => {
fetchData();
// This will trigger 'setSubmitted(false)'
afterDataFetch();
});
}
Edit: As per request, to implement "keeping the old state and only send new state to Summary when click Submit", I have come up with a solution:
Besides the 3 input states, I also have a data state that is responsible for keeping the old states of the individual input fields (states from the previous Submit)
Therefore, the data state will only get updated when user clicks Submit
// App.js
function App() {
const initial = {
input1: "",
input2: "",
};
const [input1, setInput1] = useState("");
const [input2, setInput2] = useState("");
const [data, setData] = useState(initial);
return (
<>
<input
type="text"
id="input1"
value={input1}
onChange={(e) => setInput1(e.target.value)}
/>
<input
type="text"
id="input2"
value={input2}
onChange={(e) => setInput2(e.target.value)}
/>
<button
type="button"
onClick={() => {
setData({
input1: input1,
input2: input2,
});
}}
>
Apply
</button>
<Summary i1={data.input1} i2={data.input2} />
</>
);
}
// Summary.js
function Summary(props) {
const { i1, i2 } = props;
return <pre>{JSON.stringify({ i1, i2 })}</pre>;
}
export default React.memo(Summary);
Note the use of React.memo in the Summary.js file. This is for some rendering optimization. As you can imagine, the Summary component may get re-rendered (through setInput1 or setInput2), even though the data state has not changed. Therefore, using React.memo, per the docs:
If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
If your Summary component fetches API every time it re-renders, that could be a pretty good optimization (prevent refetching when data has not changed - although you could use solution like "useSWR" to cache response anyway)
I have an app in MeteorJS, which makes use of React (I am ok with JavaScript, but am on a learning curve starting with React). The current search input makes use of the onchange function of the input box BUT this is actually not desired as this slows the app considerably - making requests every time the user types.
I basically want the input to be basic input and then have a button to trigger the search.
Inline code, for calling the searchinput where needed;
<div className="col-md-4 col-xs-12" style={disabledStyling.control}>
<SearchInput placeholder="Search" onChange={this.filterGames} value={filter} />
</div>
searchinput component;
import PropTypes from 'prop-types';
import Icon from '../Icon';
import Styles from './styles';
const SearchInput = ({ placeholder, value, onChange }) => (
<Styles.SearchInput className="SearchInput">
<Icon iconStyle="solid" icon="search" />
<input
type="text"
name="search"
className="form-control"
placeholder={placeholder}
value={value}
onChange={onChange}
/>
</Styles.SearchInput>
);
SearchInput.defaultProps = {
placeholder: 'Search...',
value: '',
};
SearchInput.propTypes = {
placeholder: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
};
export default SearchInput;
Hoping you all could help ;)
Basically what you need to do is use a state to store the value from the onChange event and later, on button click/form submit action you pass this value to the function that will actually fetch data.
Here is a small example on code sandbox where you can see this being applied both on a functional component and on a class component
lets assume your "wrapper" component is something like this:
const Form = () => {
const filterGames = (event) => {
// handle event and calls API
}
return (
<div className="col-md-4 col-xs-12" style={disabledStyling.control}>
<SearchInput placeholder="Search" onChange={filterGames} value={filter} />
</div>
)
}
What we need to do here, is basically adding the state and handle it without calling the API and a button to actually call the API.
const Form = () => {
const [inputValue, setInputValue] = useState('');
const filterGames = (event) => {
// handle event and calls API
}
// this will store the value locally on the state
const handleInputOnChange = (event) => {
setInputValue(event.target.value);
}
return (
<div className="col-md-4 col-xs-12" style={disabledStyling.control}>
<SearchInput placeholder="Search" onChange={handleInputOnChange} value={inputValue} />
<button type='submit' onClick={filterGames}>Submit</button>
</div>
)
}
ps: you can also wrap the input + button with a form and use form.onSubmit instead of button.onClick.
I am building a huge form which is made of atleast 50 inputs.
I have wrote a function in the form component that will save the value of the input to the form state:
PARENT FUNCTION
saveToState(details) {
const { company } = this.state;
company[details.part][details.element] = details.value;
this.setState({ company });
}
PASSING TO CHILD COMPONENT (INPUT)
<FieldInput
label="Name (as shown) *"
part="information"
element="displayName"
saveToState={this.saveToState}
/>
Here is the Input component:
import React, { Component } from 'react';
export default class FieldInput extends Component {
render() {
const { label, part, element, saveToState } = this.props;
return (
<div className="field">
<label>{label}</label>
<div className="ui input">
<input
type="text"
name={`${part}[${element}]`}
onChange={(e) => saveToState({
part,
element,
value: e.target.value
})}
/>
</div>
</div>
);
}
}
In result whenever I type something in the input It's taking it 200-300ms to really display what I wrote in the input, the state is getting updates instantly but whenever I type a character I set the new state of the parent form and update it which updates the whole component. The only way i found around it is to use saveToState within the parent component without passing it down. but that would require 1000's of line of code, Is there any way around this? Thanks!
There are alot of ways you can solve this problem. The easiest one and the fastest one is to use onBlur instead of onChange that way setState will happen not when you key pressing in the input but when the input loses focus.
import React, { Component } from 'react';
export default class FieldInput extends Component {
render() {
const { label, part, element, saveToState } = this.props;
return (
<div className="field">
<label>{label}</label>
<div className="ui input">
<input
type="text"
name={`${part}[${element}]`}
onBlur={(e) => saveToState({
part,
element,
value: e.target.value
})}
/>
</div>
</div>
);
}
}