Display a variable from a form in React - reactjs

I am new to react and I am trying to make an application that gets the output from a form and displays it in a div. So far, I have only managed to get the result from the form with a simple useState() approach.
To display it, I have tried creating a variable "isSubmitted" to keep track whether or not the variable was submitted, in order to display it only when the user stops typing!
const Example0 = () => {
var isSubmitted;
const [email, setEmail] = useState("");
const handleEmail = (event) => {
setEmail(event.target.value);
isSubmitted = false;
console.log(isSubmitted);
};
const handleSubmit = (event) => {
event.preventDefault();
isSubmitted = true;
console.log(isSubmitted);
};
return (
<Fragment>
<h1>Form example</h1>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={email} onChange={handleEmail} />
</div>
<button type="submit">Submit</button>
</form>
<div>{isSubmitted ? email : "no user"}</div>
</Fragment>
);
};
This part, does not work. The email variable changes but the display doesn't. Does it have something to do with how React works? I come from a HTML background.
<div>{isSubmitted ? email : "no user"}</div>
Help is much appreciated.

Your page needs to be rendered, so you can see the data already in your page.
In reactJS, there are states,
The state is a built-in React object that is used to contain data or
information about the component. A component's state can change over
time; whenever it changes, the component re-renders. source
So you need to use a state to make your component renders again and see the data.
simply create a new state like this:
const [isSubmitted, setIsSubmitted] = React.useState(false);
then change the states when you need, In your example you have to use it like this:
const handleEmail = (event) => {
setEmail(event.target.value);
setIsSubmitted(false);
console.log(isSubmitted);
};
const handleSubmit = (event) => {
event.preventDefault();
setIsSubmitted(true);
console.log(isSubmitted);
};

You should be using a state to control the boolean for if the user has submitted their email or not.
Why can't you use a normal variable?
Every time you're updating a state variable, the component will re-render, and each time it re-renders, you're setting your isSubmitted flag to an undefined. Additionally, even if it wasn't undefined, it wouldn't update your component to show the email because non-state variables won't trigger a re-render, so updating isSubmitted to true won't trigger your ternary statement you have, and re-rendering it will just take it back to undefined, which leaves both scenarios without a render of your div tag content.
Solution
If you want something to conditionally render, you should use useState, its the whole point of react, to "react" accordingly and update your DOM. It's one of the biggest issues I see new learners grasp when first touching React.
Here's my approach to it, which won't update the end tag to whatever the value is after you submit it (I assume you'd want this implemented):
const [email,setEmail] = useState("");
const [isSubmitted, setIsSubmitted] = useState(false);
/**
* handles the Email input, does not update if the form has been submitted or if the string is empty.
* #param {*} e
*/
const handleEmail = (e) => {
if(e.target.value.length > 0 && isSubmitted === false) {
setEmail(e.target.value);
}
};
const handleSubmit = (e) => {
e.preventDefault();
setIsSubmitted(true);
};
return (
<>
<h1>Form example</h1>
<form onSubmit={(e) => handleSubmit(e)}>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" onChange={handleEmail} />
</div>
<button type="submit">Submit</button>
</form>
{isSubmitted ? <p>Thank you for submitting your email: {email}</p> : null}
</>

Related

The state does not change the first time, react

Why, when the user enters data into the form for the first time, 'users' remains an empty array, as it was, and only after the second time button is pressed, the data is written to 'setUser?
import Card from "../UI/Card";
import Button from "../UI/Button";
const UserForm = (props) => {
const [users, setUsers] = useState([]);
const [data, setData] = useState({ username: "", age: "" });
const submitHandler = (e) => {
e.preventDefault();
setUsers([...users, data]);
console.log("2", users);
props.getUsers(users);
};
return (
<Card className={classes.container}>
<div>Username</div>
<input
type="text"
onChange={(e) => {
setData({ ...data, username: e.target.value });
}}
></input>
<div>Age (Years)</div>
<input
type="number"
onChange={(e) => setData({ ...data, age: e.target.value })}
></input>
<Button onClick={submitHandler}>Add User</Button>
</Card>
);
};
export default UserForm;
....................
React State update takes time. So you need a useEffect hook with state as a argument in it. so whenever the state change, this hook triggers and perform your state related functions.
useEffect(() => {
console.log(users)
props.getUsers(users);
}, [users]);
it does change but how react works ?
when you update the value of one react hook and if this value is different from the previous one the component re-render with the new value of the hook.
in you code you are trying to :
console.log("2", users);
this is just before the component re-render so the new value is not available yet, but directly after submitHandler the component will re-render with new value of users
you can understand better if you try to console log() from inside your jsx
return (
<Card className={classes.container}>
{console.log('this is a new render and this is my users value :',users)}
</Card>
);
learn more here about React component lifecycle

React: Passing local state to parent component

I'm trying to create a reusable "Modal" component in React. The Modal component will have some input fields and a submit button and when user clicks the Submit button in the Modal. the modal should be closed and the data user entered in the input fields should be passed up to the parent component through a callback function declared in parent component. The code is working fine but i think(not sure) it's not the right solution as i had to create "React state" in both the Modal(child) component and the parent component where the Modal component is used. As you can see(sandbox code) the state is repetitive in both components and i was wondering how can i keep the state in only one place. My understanding is I'd definitely need to create a state in Modal component for the input fields to keep track of changes but i am not sure how can i use that data in parent component without creating same state
Here is a Sandbox that i created to show what i've done so far:
https://codesandbox.io/s/jovial-matsumoto-1zl9b?file=/src/Modal.js
In order to not duplicate state I would enclose the inputs in the modal in a form element and convert the inputs to uncontrolled inputs. When the form is submitted grab the form field values and pass in the formData callback and reset the form. Explicitly declare the button to be type="submit".
const Modal = (props) => {
const onFormSubmit = (e) => {
e.preventDefault();
const firstName = e.target.firstName.value;
const lastName = e.target.lastName.value;
props.formData({ firstName, lastName });
e.target.reset();
};
if (!props.show) {
return null;
} else {
return (
<div className="modal" id="modal">
<form onSubmit={onFormSubmit}>
<input type="text" name="firstName" />
<input type="text" name="lastName" />
<button className="toggle-button" type="submit">
Submit
</button>
</form>
</div>
);
}
};
It seems the crux of your question is about the code duplication between your parent component and a modal. What I would really suggest here is to decouple the modal from any specific use case and allow any consuming parent components to pass a close handler and children components.
This keeps the state in the parent, and thus, control.
Example modal component:
const Modal = ({ onClose, show, children }) =>
show ? (
<div className="modal" id="modal">
<button type="button" onClick={onClose}>
X
</button>
{children}
</div>
) : null;
Parent:
function App() {
const [isShowModal, setIsShowModal] = useState(false);
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
const showModal = (e) => {
setIsShowModal((show) => !show);
};
const closeModal = () => setIsShowModal(false);
const onFormSubmit = (e) => {
e.preventDefault();
const firstName = e.target.firstName.value;
const lastName = e.target.lastName.value;
setFirstName(firstName);
setLastName(lastName);
e.target.reset();
closeModal();
};
return (
<div className="App">
<h2>First Name is : {firstName}</h2>
<h2>Last Name is : {lastName}</h2>
<button className="toggle-button" onClick={showModal}>
Show Modal
</button>
<Modal show={isShowModal} onClose={closeModal}>
<form onSubmit={onFormSubmit}>
<input type="text" name="firstName" />
<input type="text" name="lastName" />
<button className="toggle-button" type="submit">
Submit
</button>
</form>
</Modal>
</div>
);
}
Here it's really up to you how you want to manage the interaction between parent and modal content. I've shown using a form and form actions so your state isn't updated until a user submits the form. You can use form utilities (redux-form, formix, etc...) or roll your own management. Life's a garden, dig it.
You can define these states in your App component :
const [showModal, setShowModal] = useState(false);
const [user, setUser] = useReducer(reducer, {
firstName: "",
lastName: ""
});
const toggleModal = () => {
setShowModal(!showModal);
};
And pass them through props when you render your Modal component :
<div className="App">
<h2>First Name is : {firstName}</h2>
<h2>Last Name is : {lastName}</h2>
<button className="toggle-button" onClick={toggleModal}>
Show Modal
</button>
<Modal
show={showModal}
hide={() => toggleModal(false)}
user={user}
updateUser={setUser}
/>
</div>
Then, in your Modal component, define states for firstName and lastName :
const Modal = (props) => {
const [firstName, setFirstName] = useState(props.user.firstName);
const [lastName, setLastName] = useState(props.user.lastName);
const onFirstNameChange = ({ target }) => {
setFirstName(target.value);
};
const onLastNameChange = ({ target }) => {
setLastName(target.value);
};
// ...
}
And now you can submit changes like this from your Modal component :
const onFormSubmit = (e) => {
props.updateUser({
firstName,
lastName
});
props.hide();
};

React.js setState not getting update wiith onclick

This is my sample code, it's very basic i just want to console fname once submit is clicked.
When i pressed first time i get an empty line but when pressed second time the button i get some empty value. I am attaching the screenshot for the console.
I dont want to change my code to a class and use some method to get the value in console.
screenshot for console output
import React,{useState} from 'react'
export const anyComponent = () => {
const [fname, setFname] = useState('')
const submit = (event) =>{
setFname({fname: [event.target.value] })
console.log(fname)
}
return(
<div>
<input name="fname" type="text" placeholder="Enter your First Name" />
<button onClick={submit}>Submit</button>
</div>
)
}
From MDN Docs:
The target property of the Event interface is a reference to the object onto which the event was dispatched.
In your case, event.target would point to the button and not input.
What you need is a reference to the input, you can use useRef hook for it like this
import React, { useState, useRef } from "react";
export default anyComponent = () => {
const inputEl = useRef(null);
const [fname, setFname] = useState("");
const submit = event => {
setFname({ fname: [inputEl.current.value] });
};
console.log(fname);
return (
<div>
<input
name="fname"
ref={inputEl}
type="text"
placeholder="Enter your First Name"
/>
<button onClick={submit}>Submit</button>
</div>
);
};
Also, setState is asynchronous, that's why you wouldn't see the result in the console just after calling setFname. However you'd see the updated fName in console on the next render, that's why I've moved it out of the submit.
Without useRef
Alternatively, you can add onChange handler on input and update the state fname from there
function App(){
const [fname, setFname] = useState("");
const submit = () => {
console.log(fname);
};
const handleChange = ev => {
setFname({ fname: [ev.target.value] });
}
return (
<div>
<input
name="fname"
onChange={handleChange}
type="text"
placeholder="Enter your First Name"
/>
<button onClick={submit}>Submit</button>
</div>
);
};
Your console log should be outside submit method. Or log event.target.value instead fname.

Reactjs - TypeError: Cannot read property 'value' of null

I am new and still practicing in reactjs. I am having a problem in my input form, whenever I type any key I always get TypeError: Cannot read property 'value' of null
this is my code:
import React, { useState } from 'react';
export default function FormPractice() {
const [isDefault, setIsDefault] = useState('');
const [balance, setBalance] = useState({amount: null});
return (
<div className="input">
<input placeholder="enter balance here" onChange={e => setBalance(form => ({...form, [e.target.value]: e.target.value}))} value={isDefault}/>
</div>
)
}
Thanks for the help.
React re-uses events. When you use a functional update, by the time the function is called, the event has already been wiped for reuse. To use the event within the functional update you would need to call e.persist() which will take that event out of the reuse pool and let it keep its values.
To keep it inline, it would look like this:
onChange={e => {e.persist(); setBalance(form => ({...form, [e.target.value]: e.target.value}))}}
Or to make it more readable, move it into its own function:
const onChange = (e) => {
e.persist();
setBalance(form => ({...form, [e.target.value]: e.target.value}));
}
However, the simplest available solution would be to not use the functional update at all. You are replacing state values, but you are not setting any values derived from the previous state. So it is safe to use a normal update:
onChange={e => setBalance({...balance, [e.target.value]: e.target.value})}
Now the event reuse is a non-issue.
Side note: [e.target.value]: e.target.value this doesn't really make sense. You're setting an object key with the name of the new value to the same value.
It seems like maybe you've seen [e.target.name]: e.target.value before and modified it. I would suggest using the name, and then giving the input the name of the property you want to update.
Here is a simplified example:
const {useState, useEffect} = React;
const Example = () => {
const [state, setState] = useState({ input1: '' });
const onChange = (e) => {
setState({[e.target.name]: e.target.value});
}
return (
<input
name="input1"
placeholder="enter balance here"
onChange={onChange}
value={state.input1}
/>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Reactjs parent state is not refreshed when the children input is changed

I created a component and wanted to wrap text input and add some functionality to it.
<Input value={this.state.name} />
In child component there is remove button beside text input in order to clear text when it is clicked.
const Input = (props) => {
let textInput = null;
const removeText = (e) =>{
e.preventDefault();
textInput.value = '';
textInput.focus();
}
return(<div>
<input ref={(input) => { textInput = input; }} {...props} />
<button
onClick = {removeText}
></button>
</div>)
}
As the Input component is function, I used ref in order to access input and manipulate it. The problem is the parent state is not updated when it is changed by functions in child component. Consider that I don't want to use props and pass the function to update parent state. By the way, I don't know this approach whether is correct or not.
Define removeText function in a component where you are calling Input component. Also avoid using refs for input field you no need ref for sure instead you can have event handler function which will set the value in state
removeText = (e) =>{
e.preventDefault();
this.setState({name: ''});
}
handleInput = e => {
this.setState({name: e.target.value});
}
And pass down functions to Input component as a prop like
<Input value={this.state.name} removeText={this.removeText} handleInput={this.handleInput} />
Now, change the Input component to something like below
const Input = (props) => {
return(<div>
<input value={props.value} onChange={e => props.handleInput(e)}/>
<button
onClick = {() => this.removeText()}
></button>
</div>)
}
This way it updates the parent state value. This is so called callbacks in react

Resources