I'm trying to build reusable form and input components and so far was able to achieve what I am after, here is how they are used in a LoginPage component
import React from 'react'
import classNames from 'classnames/bind'
import styles from './style.scss'
import Form from 'components/Form'
import Input from 'components/Input'
const cx = classNames.bind(styles)
export default class LoginPage extends React.Component {
constructor (props) {
super(props)
this.loginUser = this.loginUser.bind(this)
}
loginUser () {
console.log(`
Email: ${this.refs.email.state.value} Password: ${this.refs.password.state.value}
`)
}
render () {
return (
<main className={cx('LoginPage')}>
<div className={cx('container')}>
<Form onSubmit={this.loginUser}>
<Input type='email' placeholder='email' ref='email' />
<Input type='password' placeholder='password' ref='password' />
<Input type='submit' value='Log in' />
</Form>
</div>
</main>
)
}
}
The question I'm asking is if using this.refs.email.state.value is a valid approach to this, to get that input components value from its state?
I don't think you need refs for your case. Remember that they should not be used when the normal data flow of props can accomplish the same. From the docs
If you have not programmed several apps with React, your first
inclination is usually going to be to try to use refs to "make things
happen" in your app. If this is the case, take a moment and think more
critically about where state should be owned in the component
hierarchy. Often, it becomes clear that the proper place to "own" that
state is at a higher level in the hierarchy. Placing the state there
often eliminates any desire to use refs to "make things happen" –
instead, the data flow will usually accomplish your goal.
Another warning sign should be that string refs are considered legacy and will be deprecated.
A better and safer way of achieving what you need is to handle the input change separately and keep that in the local component state. So your code would look something like this:
// ...imports...
const cx = classNames.bind(styles)
const initialState = {
eamil: '',
password: '',
};
export default class LoginPage extends React.Component {
constructor (props) {
super(props);
this.loginUser = this.loginUser.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
let nextState = {};
nextState[e.target.name] = e.target.value;
this.setState(nextState);
}
loginUser () {
e.preventDefault();
console.log(`
Email: ${this.state.email} Password: ${this.state.password}
`)
}
render () {
return (
<main className={cx('LoginPage')}>
<div className={cx('container')}>
<Form onSubmit={this.loginUser}>
<Input
type='email'
value={this.state.email}
placeholder='email'
onChange={this.handleChange}
/>
<Input
type='password'
value={this.state.password}
placeholder='password'
onChange={this.handleChange}
/>
<Input type='submit' value='Log in' />
</Form>
</div>
</main>
)
}
}
Here the state is at the top of the form component. You could make it even more general and have a form Redux store with its own actions and reducers, but it should give you an idea.
Let me know if you have questions on the code.
Please use this.refs.email.value instead of this.refs.email.state.value.
Yes it is a standard way in react
OR
You can attach eventListener onClick and keep email in state.
export default class LoginPage extends React.Component {
constructor (props) {
super(props)
this.loginUser = this.loginUser.bind(this)
}
loginUser () {
console.log(`
Email: ${this.refs.email.value} Password: ${this.refs.password.value}
`)
}
changeEmail(event) {
this.setState({
email: event.target.value
}
render () {
return (
<main className={cx('LoginPage')}>
<div className={cx('container')}>
<Form onSubmit={this.loginUser}>
<Input type='email' value={this.state.value} onClick={this.changeEmail} placeholder='email' ref='email' />
<Input type='password' placeholder='password' ref='password' />
<Input type='submit' value='Log in' />
</Form>
</div>
</main>
)
}
}
Related
I am new to this topic.
In the parent component App I have two siblings : SideMenu and Document
The idea is that the user inputs values (SideMenu) which will be renedered on the Document. There will be more than 20 inputs. Since this is the first time I do this sort of state management, what are the best or maybe easiest approaches for this attempt of project.
function App() {
const [fullName, setFullName] = useState("")
const [address, setAddress] = useState("")
return (
<div className='app'>
<SideMenu />
<Document />
</div>
)
}
export default App
const SideBar = () => {
return (
<div>
<div className='input-group'>
<label>Full Name:</label>
<input type='text' />
</div>
<div className='input-group'>
<label>Address:</label>
<input type='text' />
</div>
</div>
)
}
const Document = () => {
return (
<div>
<h1>{fullName}</h1>
<p>{address}</p>
</div>
)
}
You can create an object for your form and store the form inputs in this object. Shared state can be stored in the most closest and common component (in your situation this is your parent component) according to your child components. [1]
When you make an update from a child component other child component that is sharing state will be syncronized and your state will be updated. You shouldn't use redux like state management tools unless you are need to set a global state.
I have made a revision for your example, this scenario allows you to pass the state in the parent component to the child components and update the state in the parent component from the child components.
I used a common event handler in the parent component, this functions captures the html event and we parse this event and update the state via this function. [2][3]
import "./styles.css";
import { useState } from "react";
import SideBar from "./SideBar";
import Document from "./Document";
export default function App() {
const [values, setValues] = useState({
fullName: "",
address: "",
postalCode: ""
});
function handleChange(event) {
setValues({ ...values, [event.target.name]: event.target.value });
}
return (
<div className="app">
<SideBar values={values} setValues={handleChange} />
<Document values={values} setValues={handleChange} />
</div>
);
}
export default function Document({ values }) {
return (
<div>
<h1>Document</h1>
<p>Full Name: {values.fullName}</p>
<p>Address: {values.address}</p>
<p>Postal Code: {values.postalCode}</p>
</div>
);
}
export default function Sidebar({ setValues }) {
return (
<div>
<div className="input-group">
<label>Full Name:</label>
<input type="text" name="fullName" onChange={setValues} />
</div>
<div className="input-group">
<label>Address:</label>
<input type="text" name="address" onChange={setValues} />
</div>
<div className="input-group">
<label>Address:</label>
<input type="text" name="postalCode" onChange={setValues} />
</div>
</div>
);
}
Code Sandbox Link: https://codesandbox.io/s/stackoverflow-74961591-wpmcnd
[1]: Passing Props to a component: https://beta.reactjs.org/learn/passing-props-to-a-component
[2]: Updating Objects in State: https://beta.reactjs.org/learn/updating-objects-in-state
[3]: HTML Change Event: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event
The default, go-to solution would be to use a container component that controls your form inputs state (this would be App in your case). Just pass the values and setters down one level by props and everything should be ok, simple and predictable.
If things start to get complicated then libraries such as Formik or react-hook-form help a lot. When it comes to managing multiple or complex forms that may also need validation they are your best bet. I suggest you take this approach.
Using Redux for this kind of situation is a huge anti-pattern. Redux global store should be only used for global state, not local form state.
Context API is well suited when you need to pass data to multiple deeply nested children. This way you do not need to pass props dozens of levels down the tree. However, it is usually used by 3rd party libraries such as the ones mentioned above (all of them).
You can use Formik library for handling many inputs. Wrap both components inside Formik and use formik's methods.
import { Formik } from 'formik';
<Formik
initialValues={{ fullName: '', address: '' }}
onSubmit={(values) => {
alert(JSON.stringify(values, null, 2));
}}
>
{({handleChange, values, handleSubmit}) => (
<form onSubmit={handleSubmit}>
<div className='app'>
<SideMenu
handleChange={handleChange}
/>
<Document values={values} />
<button type="submit">Submit</button>
</form>
)}
</Formik>
You dont need to create multiple states for each input. handlChange will handle itself. You just need add name or id attribute to input. Also you can access values of each input using the values parameter like values.fullName.
const SideBar = ({handleChange}) => {
return (
<div>
<div className='input-group'>
<label>Full Name:</label>
<input
type='text'
onChange={handleChange}
name="fullName"
/>
</div>
<div className='input-group'>
<label>Address:</label>
<input
type='text'
onChange={handleChange}
name="address"
/>
</div>
</div>
)
}
const Document = ({values}) => {
return (
<div>
<h1>{values.fullName}</h1>
<p>{values.address}</p>
</div>
)
}
Suppose I have a component like this -
const MyForm = ({ formId }) => (
<div>
<input type="text" placeholder="Full name"></input>
<input type="text" placeholder="Email"></input>
</div>
)
export default MyForm;
And then I have my App.js like so -
import React from "react";
import MyForm from "./MyForm";
const App = () => (
<div id="app">
<MyForm formId="formOne"></MyForm>
<MyForm formId="formTwo"></MyForm>
<button onClick={
() => {
// Here, when the user clicks the button,
// I want to get values of both the textboxes,
// from both the component instances
}
}>Submit</button>
</div>
)
export default App;
So basically, what I want is - when the button is clicked, I want to be able to retrieve the values of the textboxes. One way to do this is to raise an event from inside MyForm.js so that every text change is bubbled up to the parent via a callback function prop, but that feels too cumbersome, especially if the form has a lot of fields. Is there any simple or direct way to do this? Do I need to involve global state management tools like Redux?
State inside a component is specific only to that component, the parent , children or sibling of a component have no idea of the state. The only way to communicate the value from one component to another component is via props . In your case, what we need is a state to reside at the App which can then be passed as a prop to both the MyForm Components.
App.js
const [ formState, setFormState ] = useState({ formOne: {fullName: '', Email: ''}, formTwo: '' })
const updateFormValues = (formId, key, value) => {
const stateCopy = JSON.parse(JSON.stringify(formState));
const formToUpdate = stateCopy[formId];
formToUpdate[key] = value;
setFormState(stateCopy)
}
<MyForm formId="formOne" values={formState.formOne} updateFormValues={updateFormValues}></MyForm>
<MyForm formId="formTwo" values={formState.formTwo} updateFormValues={updateFormValues}></MyForm>
MyForm.js
const MyForm = ({ formId, values, updateFormValues }) => {
const onInputChange = (e, key) => {
updateFormValues(formId, key, e.target.value)
}
return(
<div>
<input type="text" onChange={(e) => onInputChange(e, 'fullName'} value={values.fullName} placeholder="Full name"></input>
<input type="text" onChange={(e) => onInputChange(e, 'email'} value={values.email} placeholder="Email"></input>
</div>
)}
export default MyForm;
To have access to data inside children components you need to lift the state to the parent component.
One-way data flow
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the components that need the state in the hierarchy).
Either the common owner or another component higher up in the hierarchy should own the state.
If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common owner component.
One way to do this:
import React, { useState } from "react";
function MyForm(props) {
const { handleChange, values } = props;
return (
<div>
<label htmlFor="name">Your name</label>
<input
type="text"
placeholder="Full name"
onChange={handleChange}
value={values.name}
id="name"
name="name"
/>
<label htmlFor="email">Your email</label>
<input
type="email"
placeholder="Email"
onChange={handleChange}
value={values.email}
id="email"
name="email"
/>
</div>
);
}
function App() {
const [values, setValues] = useState({ name: "", email: "" });
const handleChange = (event) => {
const updatedForm = { ...values, [event.target.name]: event.target.value };
setValues(updatedForm);
};
return (
<div id="app">
<MyForm
formId="formOne"
values={values}
handleChange={handleChange}
></MyForm>
<button
onClick={() => {
console.log(values);
}}
>
Submit
</button>
</div>
);
}
export default App;
I am just starting to learn react and I'm currently building a form, so far I've created a parent component 'Form' and I've separated the rest of the inputs as components and each component has its own state. My question is how to get that state data from the children's components and use it in the parent component 'Form' when submitting the form?
Here is my parent component
import React, { Component } from "react";
import Name from "components/Name";
import Email from "components/Email";
import Select from "components/Select";
import Bio from "components/Bio";
class Form extends Component {
handleSubmit = event => {
event.preventDefault();
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="shape rectangle"></div>
<div className="shape triangle"></div>
<div className="shape circle"></div>
<Name />
<Email />
<Select />
<Bio />
<button type="submit" className="btn">
Submit
</button>
</form>
);
}
}
export default Form;
And one of the child component
import React, { Component } from "react";
// Create Name component for name && email inputs
class Name extends Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
lastName: ""
};
}
// Handle first name input on change event
handleFirstNameChange = event => {
this.setState({
firstName: event.target.value
});
};
// Handle last name input on change event
handleLastNameChange = event => {
this.setState({
lastName: event.target.value
});
};
// Render labels and name inputs
render() {
const { firstName, lastName } = this.state;
return (
<div className="form-names">
<label htmlFor="firstName">Name</label>
<br/>
<input
type="text"
name="firstName"
value={firstName}
placeholder="First Name"
id="firstName"
onChange={this.handleFirstNameChange}
/>
<input
type="text"
name="lastName"
value={lastName}
placeholder="Last Name"
id="lastName"
onChange={this.handleLastNameChange}
/>
</div>
);
}
}
export default Name;
To accomplish this you would need to "Lift the state" up to a common parent component (known as ancestor component), in your case, this would be the <Form> component. Then you would pass down the values to each corresponding child component as props.
It would look something like this:
import React, { Component } from "react";
import Name from "./Name";
// More imports go here..
class Form extends Component {
state = {
firstName: "",
lastName: ""
};
handleSubmit = event => {
event.preventDefault();
};
// Handle first name input on change event
handleFirstNameChange = event => {
this.setState({
firstName: event.target.value
});
};
// Handle last name input on change event
handleLastNameChange = event => {
this.setState({
lastName: event.target.value
});
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<Name
firstName={this.state.firstname}
lastName={this.state.lastName}
handleFirstNameChange={this.handleFirstNameChange}
handleLastNameChange={this.handleLastNameChange}
/>
{/* More components go here.. */}
<p>Current state:</p>
{JSON.stringify(this.state)}
</form>
);
}
}
export default Form;
Working example
More info: Lifting state up from the official React docs.
you can use a single state in the parent component , and pass the firstname and lastname in the props, your code will became:
import React, { Component } from "react";
import Name from "components/Name";
import Email from "components/Email";
import Select from "components/Select";
import Bio from "components/Bio";
class Form extends Component {
state = {
firstName: '',
lastName: '',
}
onChange = (e) => {
this.setState({[e.target.name] : e.target.value})
}
handleSubmit = event => {
event.preventDefault()
}
render() {
return (
...
<Name onChange={this.onChange} firstName={this.state.firstName} lastName={this.state.lastName} />
...
<button type="submit" className="btn">
Submit
</button>
</form>
)
}
}
export default Form;
and your child component became:
import React, { Component } from "react";
// Create Name component for name && email inputs
class Name extends Component {
render() {
const { firstName, lastName , onChange} = this.props;
return (
<div className="form-names">
<label htmlFor="firstName">Name</label>
<br/>
<input
type="text"
name="firstName"
value={firstName}
placeholder="First Name"
id="firstName"
onChange={onChange}
/>
<input
type="text"
name="lastName"
value={lastName}
placeholder="Last Name"
id="lastName"
onChange={onChange}
/>
</div>
);
}
}
export default Name;
My thought is to do sth like the following, passing the data as props to the component. But it doesn't make sense to me as when you click the submit button, the form gather data from the parent component. It may better keep the form in the parent component and group all the input data in state.
import React, { Component } from "react";
import Name from "components/Name";
import Email from "components/Email";
import Select from "components/Select";
import Bio from "components/Bio";
class Form extends Component {
constructor(props){
super(props)
this.state = {
firstName = "",
lastName = "",
(and other input data you need)...
}
}
handleSubmit = event => {
event.preventDefault();
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="shape rectangle"></div>
<div className="shape triangle"></div>
<div className="shape circle"></div>
<Name firstName={this.state.firstName} lastName={this.state.lastName}/>
<Email (inputData as props)/>
<Select (inputData as props)/>
<Bio (inputData as props)/>
<button type="submit" className="btn">
Submit
</button>
</form>
);
}
}
export default Form;
You could either use something like Context or Redux, which would allow you to create a universal store and retrieve state from any component or create methods in your parent component, pass them down to it's children, and invoke them from within that child component. For example, you could have handleFirstNameChange in Form and pass it down to Name. In doing so you'd now keep props like name in Form as well and pass that down as well.
I am currently writing my first React Project for a class assignment. I am trying to make a login page that navigates to a new dashboard page. I do not want any fancy security, so I wanted it just to have "if password === this password then go to dashboard, if not then error message.
I have the button working fine without validation, and I have my handlers for the text input working as I can display what is typed by using this.state.username and this.state.password in my login-form.js file.
The problem I can't figure out is how to reference/use those states in my login-button.js file so I can create that if statement validator? Can anyone help?
Here is my login-form.js file:
import React from 'react';
import "./login-form.css";
import logo from './../../logo-beesecure-2-tm.png';
import Login_btn from './../login-button/login-button';
class Login_Form extends React.Component {
constructor(props){
super(props);
this.state = { username: '', password: '' };
}
handleChange = ({ target }) => {
this.setState({ [target.name]: target.value });
};
render() {
return (
<div className='login-container'>
<img src={logo} className="App-logo" alt="logo" />
<p>LOGIN</p>
<form onSubmit="" className="login-form">
<input
type="text"
placeholder="Username"
name="username"
value={this.state.username}
onChange={this.handleChange}
/>
<input
type="password"
placeholder="Password"
name="password"
value={this.state.password}
onChange={this.handleChange}
/>
</form>
<Login_btn />
<h2>Your username is: {this.state.username}</h2>
<h2>Your password is: {this.state.password}</h2>
</div>
);
}
}
export default Login_Form;
And here is my login-button.js file:
import './login-button.css';
import React from 'react';
import { useHistory } from "react-router-dom";
import Login_Form from '../login-form/login-form';
function Login_btn() {
let history = useHistory();
function handleClick() {
history.push("/dashboard");
}
return (
<button className="Login-Button" onClick={handleClick}>Login</button>
);
}
export default Login_btn;
Thank you in advance!
You can pass in the states from your <Login_Form /> into your <Login_btn /> by using props like so:
<Login_btn username={this.state.username} password={this.state.password} />
Then you can reference the props in your <Login_btn />:
function Login_btn(props) {
let history = useHistory();
function handleClick() {
const { username, password } = props;
history.push("/dashboard");
}
return (
<button className="Login-Button" onClick={handleClick}>Login</button>
);
}
You can read more about props here: https://reactjs.org/docs/components-and-props.html
This question already has answers here:
Access React Context outside of render function
(5 answers)
Closed 3 years ago.
I've followed a few online examples, where they have a counter and an increment function in Context, and on a distant component, call the increment method and the results shows. All great, but ... I am trying to expand on this and create a login box, that sets an isAthenticated flag.
I have a very basic context:
import React from 'react';
const Context = React.createContext();
export class Provider extends React.Component {
state = {
isAuthenticated: false,
user: {
name: "Craig",
email: "craig#here.com"
},
changeEmail: (newEmail) => {
let user = this.state.user;
user.email = newEmail;
console.log(user);
this.setState({ user: user})
},
changeAuthenticated: () => {
this.setState ({ isAuthenticated: !this.state.isAuthenticated });
}
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
export const Consumer = Context.Consumer;
In it I allow the user to change email, and change isAuthenticated state.
My component (Remove style stuff) looks like this:
import React from 'react';
import { Input, Label, Button, Container } from 'reactstrap';
import { Consumer } from '../context';
class Login extends React.Component {
render() {
return (
<Consumer>
{value => {
return (
<Container style={containerStyle} >
<div style={loginBoxStyle}>
<div>
<h3>Login</h3>
</div>
<div style={loginBoxFieldsStyle}>
<div style={loginBoxFieldStyle}>
<div style={loginBoxLabelStyle}>
<Label for="email">Email:</Label>
</div>
<div style={loginBoxLabelStyle}>
<Input type="email" name="email" id="email" placeholder="Your Email" value={value.user.email} onChange={e=>value.changeEmail(e.target.value)} />
</div>
</div>
</div>
<div style={loginBoxFieldsStyle}>
<div style={loginBoxFieldStyle}>
<div style={loginBoxLabelStyle}>
<Label for="password">Password:</Label>
</div>
<div style={loginBoxLabelStyle}>
<Input type="password" name="password" id="password" placeholder="Your Password" />
</div>
</div>
</div>
<div style={loginBoxButtonStyle}>
<Button color="info" onClick={value.changeAuthenticated}>Login</Button>
</div>
</div>
</Container>
)}
}
</Consumer>
)
}
}
export default Login;
So when I change the email, the Context state is updated. And when I click the Login button, for now, it simply toggles IsAuthenticated.
I don't want the state to update as I type in the email box. I'd prefer to update the state when the Login button is clicked. So I feel I need a local component state, or something, which updates that state when I edit the data in the text boxes. And then updates the Context when I click Login.
But... How do I set up the state? 'values' (from context) is only available inside the Render. I need to set my component state outside of the render. So how would I go about achieving this?
My login button onClick should also fire a local method which has all the validation etc, and then update my route to redirect to a page on success. But then it needs access to the Context.UpdateMethod - from outside of the tags. Not sure how to achieve this.
You should probably just create a sub-component and then use the props to initialize the state.
class Login extends React.Component {
render() {
return (
<Consumer>
{value => (
<Container style={containerStyle}>
<SubComponent
changeAuthenticated={value.changeAuthenticated}
// ...etc