React Form controlled component inputs - reactjs

I have the following component hierarchy:
App
|
|------|
Results Form
So the Results and Form components are children of the App component. I am trying to implement controlled inputs in the Form. I have my state in the App component as I want to display the data from the form inputs in the Results component. I am getting the Warning: A component is changing an uncontrolled input to be controlled error when I don't set initial values for the state and try to use the form. When I set initial values in the state, these values are displayed in the inputs which is not what I want when a user first goes to the form. What is the best solution to this problem of having to set the state to avoid the React Warning but not wanting to display values in the input prior to the user even using the form?

If you get such an error most likely because you don't have a state that handles the form input:
Here's an example:
App.js
import { useState } from "react";
import Form from "./Form";
import Result from "./Result";
export default function App() {
const [input, setInput] = useState("");
return (
<>
<Form input={input} inputHandler={setInput} />
<Result result={input} />
</>
);
}
Form.js
export default function Form({ input, inputHandler }) {
return (
<form>
<input value={input} onChange={(e) => inputHandler(e.target.value)} />
</form>
);
}
Result.js
export default function Result({ result }) {
return <span>Your input is: {result}</span>;
}

Related

UseEffect and useCallback still causes infinite loop in react project

I can't seem to resolve an infinite loop issue in my react project.
I'm working on a daily-log react app. Let me explain the project briefly. Here is the picture of the code for quick view:
The same code is available at the bottom.
The structure (from top to bottom)
The DailyLog component has a form that uses Question components into which props are passed.
The Question component uses the props to display a question and description. It also contains an Input component into which props are further passed down.
The Input component takes the props and renders the appropriate form input field.
The logic (from bottom to top)
The Input component handles it's own inputState. The state is changed when the user inputs something and the onChangeHandler is triggered.
The Input component also has a useEffect() hook that calls an onInput() function that was passed down as props from DailyLog.
The onInputHandler() in the DailyLog component updates the formState which is the form-wide state containing all input field values. The formState is amended depending on which input field is filled at the time.
The onInputHandler() uses the useCallback() hook which is supposed to stop an infinite loop caused by any parent/child re-renders. But it doesn't work :frowning:
What's wrong in the code? What am I missing here? Code provided below:
//DailyLog.js
import React, { useState, useCallback } from 'react';
import Question from '../components/FormElements/Question';
import questionData from '../components/DailyLog/questionData';
import './DailyLog.css';
const DailyLog = () => {
const [formState, setFormState] = useState();
const onInputHandler = useCallback(
(inputId, inputValue) => {
setFormState({
...formState,
[inputId]: inputValue,
});
},
[formState]
);
return (
<main className="container">
<form action="" className="form">
<Question
id="title"
element="input"
type="text"
placeholder="Day, date, calendar scheme"
onInput={onInputHandler}
/>
<Question
id="focus"
question={questionData.focusQuestion}
description={questionData.focusDescription}
element="textarea"
placeholder="This month's focus is... This week's focus is..."
onInput={onInputHandler}
/>
</form>
</main>
);
};
export default DailyLog;
//Question.js
import React from 'react';
import Input from './Input';
import './Question.css';
const Question = props => {
return (
<div className="form__group">
{props.question && (
<label className="form__label">
<h2>{props.question}</h2>
</label>
)}
<small className="form__description">{props.description}</small>
<Input
id={props.id}
element={props.element}
type={props.type}
placeholder={props.placeholder}
onInput={props.onInput}
/>
</div>
);
};
export default Question;
//Input.js
import React, { useState, useEffect } from 'react';
import './Input.css';
const Input = props => {
const [inputState, setInputState] = useState();
const { id, onInput } = props;
useEffect(() => {
onInput(id, inputState);
}, [id, onInput, inputState]);
const onChangeHandler = event => {
setInputState(event.target.value);
};
// check if question element type is for input or textarea
const element =
props.element === 'input' ? (
<input
id={props.id}
className="form__field"
type={props.type}
value={inputState}
placeholder={props.placeholder}
onChange={onChangeHandler}
/>
) : (
<textarea
id={props.id}
className="form__field"
rows="1"
value={inputState}
placeholder={props.placeholder}
onChange={onChangeHandler}
/>
);
return <>{element}</>;
};
export default Input;
Remove id and onInput from useEffect sensivity list
useEffect(() => {
onInput(id, inputState);
}, [inputState]);
And set default value of inputState to '' as follow:
const [inputState, setInputState] = useState('');
To prevent 'A component is changing an uncontrolled input of type text to be controlled error in ReactJS'. Also you can init formState:
const [formState, setFormState] = useState({title:'', focus:''});

How to store user input data in console log on form submit from child to parent component?

I have my child components within my parent component and I would like to be able to console log what data a user has submitted. However, being new to react I am not sure how I should do this?. Would love some help!
import React, { Component } from 'react';
import './App.css';
import PageOne from './Components/PageOne';
import PageTwo from './Components/PageTwo';
import PageThree from './Components/PageThree';
import PageFour from './Components/PageFour';
import PageFive from './Components/PageFive';
import PageSix from './Components/PageSix';
import PageSeven from './Components/PageSeven';
import { Input, Dropdown, TextArea, Form, Button, Header } from 'semantic-
ui-react'
class App extends Component {
render() {
return (
<div className="App">
<PageOne />
<PageTwo />
<PageThree />
<PageFour />
<PageFive />
<PageSix />
<Button>
Submit Form
</Button>
<br/>
<br/>
</div>
)
}
}
export default App;
We can pass a callback through the Parent component as props to Child and invoke it after handling the form submission.
You cant really "store" it in the console, What are you trying to do exactly ?
Do you want to save the user submitted data to use later in a part of your App? if so you could pass a call back to set the state in your Parent component with the data submitted, this way you can share the data with other Child components through the Parent state
The important thing to understand here is that we pass callbacks as props to Child components when we need to pass data to Parent components and save it to our state this way our Parent can share the state with the rest of our App.
function Parent() {
const logAfterSubmit = data => {
console.log("user submitted data", data);
};
return (
<div className="App">
<Child logAfterSubmit={logAfterSubmit} />
</div>
);
}
function Child({ logAfterSubmit }) {
const [input, setInput] = useState("");
const handleSubmit = e => {
e.preventDefault();
// do something with the data
// call our function from parent with the data
logAfterSubmit(input);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={e => setInput(e.target.value)} />
<button type="submit">Submit</button>
</form>
);
}
SandBox
Welcome to stackoverflow. React is an amazing tool for developing websites. Unique to React is a concept called "state" which is essentially what you're looking for. "State" is React's ability to keep track of data and use it accordingly. In your case, you want to record what a user submits in your child component.
Let's consider the following code:
class Parent extends React.Component{
//define state here which is typically an empty object
state = {
text: ""
}
//this is an event handler we have set up to take the data from state
handleOnSubmit = () => {
event.prevent.default() //stops page from refreshing
console.log(this.state.text)
}
//event handler that is triggered every time a user makes a change to the input
handleOnChange = (event) => {
//unique method in React that lets you update the component state
this.setState({
text: event.target.value
})
}
//all class-based components are called with render
render(){
return(
//we can pass down event handlers/functions we've defined into child components.
<Child handleOnChange={this.handleOnChange} handleOnSubmit={this.handleOnSubmit}/>
)
}
}
Now for our Child Component
//this is known as a stateless-functional component. no need to keep track of state.
const Child = (props) => {
return(
<form onSubmit={props.handleOnSubmit}>
//onChange is a unique-event listener that gets called anytime characters changes inside an input tag. So in this case we update our Parent's state every time we type.
<input onChange={props.handleOnChange} name="text" />
</form>
)
}
So a high-level summary of this. We set up a parent-component and defined functions inside of it that will help keep track of "state" or data. Then we pass down those functions as properties to our Child component. Our child component uses those functions and when they are called, they will update the state in our Parent component. Thus giving you a controlled-input flow, where you are constantly keeping track of data coming into your forms.

React Auto RTL text inputs

I want all my components using input[type=text] to get RTL or LTR direction based on user input automatically.
Back in old days(2 or 3 years ago) I used jQuery to select all these inputs and apply my script like this. But what is the best solution to implement this feature in React?
build your wrapper around Input component and do your logic inside this component :) Then everywhere in the code use your <CustomInput /> instead <input ...>.
EDIT:
enclosing a code example of wrapping input element:
import React from "react";
class CustomInput extends React.Component {
render() {
const {onChange, ...otherProps} = this.props;
// Please provide onChange callback to make this Input element "Controlled"
// otherProps are there for things like default value etc. :)
return(
<input type="text" onChange={onChange} />
);
}
}
export default CustomInput;
and if you will not use any of the lifecycle methods you can even implement this component as a function
import React from "react";
const CustomInput = ({onChange, ...otherProps}) => (
<input type="text" onChange={onChange} />;
);
export default CustomInput;

Unable to capture Redux-form Field values with handleSubmit

I am trying to integrate Redux-form v6 into my project, however no matter how closely I try to replicate the example code, I cannot get a working redux-form.
Everything seems to be connected properly, however the handleSubmit function does not capture any of the values from my form fields.
Any guidance on this issue would be greatly appreciated. My code is below.
Starting with the reducer, nothing seems to be the matter here.
import { reducer as formReducer } from 'redux-form';
export default combineReducers({
form: formReducer
});
Then, I use a container component to connect the form component to redux form, which decorates the form component nicely with all the Redux-form function.
CreateCompany.js
import CreateCompany from '../components/create_company';
import { reduxForm } from 'redux-form';
export default reduxForm({
form: 'company-submission'
})(CreateCompany);
The actual form then looks like this:
CreateCompany.jsx
<form onSubmit={ handleSubmit(values => {console.log("values",values)})}>
<div>
<label htmlFor="group">Group Name (Required)</label>
<Field name="group" component={FormInput} type="text"/>
</div>
<div>
<Field
name="group-type"
component={DropDownSelect}
selectOptions={this.state.groupTypes}
id="group-type"
/>
<label>Group Type</label>
</div>
<button type="submit">Log In</button>
</form>
The text input stateless functions supplied to the Field component.
FormInput.js
(Note: I had to include {...input.value} in the input tag to be able to type into the field. In the example code, only {...input} is used.)
import React from 'react';
const FormInput = ({ id, type, className, input }) => {
className = className || "";
id = id || "";
return (
<input id={id} {...input.value} type={type} className={className}/>
)
}
export default FormInput;
DropDownSelect.js
import React from 'react';
const DropDownSelect = ({ input, selectOptions, id }) => {
const renderSelectOptions = (selectOption) => (
<option key={selectOption} value={selectOption}>{selectOption}</option>
)
return (
<select id={id} {...input}>
{selectOptions.map(renderSelectOptions)}
</select>
);
}
export default DropDownSelect;
Any idea what I am doing wrong?
handleSubmit should be defined outside of you CreateCompany component and passed to it via props. Check out examples

can't type in input fields when using material-ui-redux-form due to re rendering

I'm using material-ui with redux. For some reason I can't type in my input fields whenever I follow the example provided at http://redux-form.com/6.2.0/examples/material-ui/ .
After using chrome redux dev tool I noticed that the state of the inputs is changing when I type but then it's re-rendering the entire component whenever something is typed, which makes it seem like nothing is being typed. Oddly enough, this only occurs when I use the Field component, as is used in the examples. If I just use material-ui components, the form allows typing and it doesn't re render. I've included the entire code to my component. Any help is much appreciated! What am I doing wrong?
import React, { Component } from 'react'
import {Field, reduxForm} from 'redux-form'
import { TextField } from 'redux-form-material-ui'
import RaisedButton from 'material-ui/RaisedButton'
class Login extends Component {
constructor (props) {
super(props)
this.handleFormSubmit = this.handleFormSubmit.bind(this)
}
componentDidMount () {
console.log(this.refs)
this.refs.username // the Field
.getRenderedComponent() // on Field, returns ReduxFormMaterialUITextField
.getRenderedComponent() // on ReduxFormMaterialUITextField, returns TextField
.focus() // on TextField
}
handleFormSubmit ({ username, password }) {
console.log(username, password)
}
render () {
const {
handleSubmit,
pristine,
submitting,
input,
fields: { username, password }
} = this.props
return (
<div className='loginWrapper'>
<form onSubmit={handleSubmit(this.handleFormSubmit)}>
<div id='loginNotch' />
<h1 className='loginHeader'>Login</h1>
<div>
<Field
component={TextField}
name='username'
floatingLabelText='Username'
ref='username' withRef />
</div>
<div>
<Field
component={TextField}
type='password'
name='password'
floatingLabelText='Password'
ref='password' withRef />
</div>
<div>
<RaisedButton
label='Go'
primary />
</div>
</form>
</div>
)
}
}
// TODO: keep property names consistent with server
export default reduxForm({
form: 'login',
fields: ['username', 'password']
})(Login)
Update: I took a look at the docs and removed fields from the export, and it is still not working.
You can clone project from here https://bitbucket.org/kvoth3/loanpayments.git
it's just a simple login screen
Try changing your reducer to
const rootReducer = combineReducers({
form: authReducer
})
ReduxForm expects your redux state structure to be
{
form: {
formName: {}
}
}
If you need to use a different name other than form, you need to provide a getFormState(state) to the reduxForm() decorator.

Resources