How to get React state array to permanently update - javascript

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
}

Related

Is it possible for my custom input not to work in shadow-dom?

I have a very simple react component with input:
const Search = ({ onchange, t }) => {
const [value, setValue] = useState('');
useEffect(() => {
console.log('value', value);
}, [value]);
return (
<div className={'user-search'}>
<input
placeholder={t('placeholder')}
className={'search-input'}
onChange={(e) => setValue(e.target.value)}
type="text"
/>
<div className={'search-icon'}>
<icon className={'icon'} size={'normal'} name="search" />
</div>
</div>
);
};
At the same time, I use a library with client components - most of them create a shadow-root in the DOM. Nevertheless, I can drop my components inside them via {children} or sometimes props.
Well, and I have a problem with this Search component: when I use it loosely anywhere in the project I get a console.log with "value" after typing something in the input. On the other hand, when I put it into the component with modal and accordion (from the client library) - input completely stops working and doesn't respond.
Could the non-functioning input have something to do with shadow-dom? Or do I need to provide more information?

React: Setting useState() hook clears out input fields?

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>
);
};

React how to re-render component only when the user clicks a button

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)

How to access input elements from a React function

I currently have a form that is auto generated based on the amount of "Activities" a current user has. Each activity has a name and a value. My goal is to submit these values to the backend to update them. Yet I can't figure out how to reference these inputs. I was trying to read about using "ref"s, yet they come back with {current:null}
Here is the auto generated list (excuse the placeholder text)
When I inspect console here is what I find from the ref:
Here is my code:
import React, { useEffect } from "react";
import { useDispatch, useStore } from "react-redux";
import { connect } from "react-redux";
import * as actions from "../store/actions/patientSide";
export function ActivityTemplates() {
const dispatch = useDispatch();
const store = useStore();
const ref = React.createRef();
useEffect(() => {
// Update the document title using the browser API
dispatch(actions.getTodaysActivityTemplates());
}, []);
const activities = store.getState().patientSide.todays_activities;
const listedStuff = activities.map((activity) => (
<div>
{activity.activity_name}
<label for="value"> Value: </label>
<input
type="number"
id="value"
defaultValue={activity.value}
min="0"
max="10"
></input>
</div>
));
const saveActivities = () => {
var inputs = ref;
console.log(inputs);
// Insert code to run the call to the backend
};
return (
<div>
<h1> Activity Templates</h1>
<form id="form" onSubmit={saveActivities()} ref={ref}>
{listedStuff}
<input type="submit" name="save" />
</form>
</div>
);
}
export default ActivityTemplates;
I am very new to React and JS and honestly have very little idea of what I'm doing, but if someone could point me in the right direction that would be awesome!
EDIT: After sleeping on it, I've realized I've just been trying to force react into my HTML. I believe I should instead use a React Form Hook, and do it properly from the ground up.
<form onSubmit={handleOnSubmit}>
<label>User Name</label>
<input type="text" name="username" /><br/>
<label>Password</label>
<input type="password" name="password" /><br/>
<input type="submit" value="Submit" />
</form>
const handleOnSubmit = (event) => {
const formData = new FormData(event.target);
const formDetails = {};
event.preventDefault();
for (let entry of formData.entries()) {
formDetails[entry[0]] = entry[1];
};
console.log("formDetails", formDetails);
}
You are getting the input fields value from "FormData" on onSubmit.
const saveActivities = (event) => {
event.preventDefault();
const data = new FormData(event.target);
// Insert code to run the call to the backend
}
You need to store the value
const [value, setValue] = React.useState();
Then give your input a onChange={e => setValue(e.target.value)}
I would change the id though

How can I make use of React.js refs without a variable?

Given the source code for a simple login form, see below. You see I want to use the username text field's value when I click the form's submit button. Since I need a reference to the actual DOM node to retrieve the value, I'm setting the usernameElement variable to that node.
const Login = ({ onLogin }) => {
let usernameElement
const handleSubmit = event => {
event.preventDefault()
onLogin(usernameElement.value)
}
return <form onSubmit={handleSubmit}>
<input
type="text"
name="username"
ref={node => { usernameElement = node }}
/>
<button type="submit">Login</button>
</form>
}
So, how would I make an functional approach to that problem, or simply get rid of the let variable?
Apparently, the correct approach to this is to make use of the component's state, meaning you need a stateful component instead of the stateless one.
// untested
class Login extends Component {
state = { username: '' }
handleChange = event => {
this.setState({ username: event.target.value })
}
handleSubmit = event => {
event.preventDefault()
this.props.onLogin(this.state.username)
}
render = () =>
<form onSubmit={this.handleSubmit}>
<input
type="text"
name="username"
value={this.state.username}
onChange={this.handleChange}
/>
<button type="submit">Login</button>
</form>
}
Using this, the username is always bound to this.state.username. The handleSubmit method can just use the username from the component's state.
See this page for further information: https://facebook.github.io/react/docs/forms.html

Categories