send form values from child function component to parent class component - reactjs

I've learned that it's best to minimize the number of class components and to keep all the logic in as few components as possible. I've managed to create a to-do list that only has the 1 class component by putting this in the input tag of the SearchBar component:
onChange={e => props.updateVal(e.target.value)}
That does exactly what I want and sends that 1 piece of data back to the parent's updateVal function.
What I want to figure out is how to do this with 1 form submit that contains several inputs which are all text fields. Is it even possible? I'd like to stay away from refs and changing the component to a class. This is just practice for me as I get better at react and any insights would be appreciated.

Focus on making reusable components that can be used throughout your application.
For example, you can create your own reusable input:
import React from 'react';
// props will consist of "value", "onChange", "name" and a "placeholder"
const Input = props => <input type="text" {...props} />
export default Input;
Now create a container that handles all of the input's values;
import React, { Component } from 'react';
import Input from '../Input';
const fields = [
{
name: "company",
placeholder: "Company"
},
{
name: "email",
placeholder: "Email"
},
{
name: "firstName",
placeholder: "First Name"
},
{
name: "lastName",
placeholder: "First Name"
},
];
class Form extends Component {
state = { firstName: "", lastName: "", email: "", company: "" };
handleChange = ({ target: {name, value} }) => this.setState({ [name]: value });
handleSubmit = e => {
e.preventDefault();
alert(JSON.stringify(this.state, null, 4));
}
render = () => (
<form onSubmit={this.handleSubmit}>
{fields.map(props => <Input key="props.name" onChange={this.handleChange} {...props} /> )}
<button type="submit">Submit</button>
</form>
);
};
export default Form;
Working example (one class component and one reusable component -- click Run code snippet):
const Input = props => <input type="text" {...props} />
const fields = [
{ name: "company", placeholder: "Company" },
{ name: "email", placeholder: "Email" },
{ name: "firstName", placeholder: "First Name" },
{ name: "lastName", placeholder: "First Name" }
];
class Form extends React.Component {
state = { firstName: "", lastName: "", email: "", company: "" };
handleChange = ({ target: {name, value} }) => this.setState({ [name]: value });
handleSubmit = e => {
e.preventDefault();
alert(JSON.stringify(this.state, null, 4));
}
render = () => (
<form onSubmit={this.handleSubmit}>
{fields.map(props => <Input key="props.name" onChange={this.handleChange} {...props}/>)}
<button type="submit">Submit</button>
</form>
);
};
ReactDOM.render(<Form />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You could take it a step further and make the Form reusable; although, it'll be harder to maintain as the form gets more complex with different inputs, field-level validations, and field styling.
I'd suggest learning how to utilize classes before you start jumping into the up-and-coming hooks.

My suggestion is to use Redux-form with react for better form handling ,
LIbrary Reference - Redux Form
Watch This Tech Talk - Before Click Reference

Related

Mantine: how to combine use-form with Select and numbers

I want to build a form that can be used to manage an option that is stored in the database as a number.
However, if I now enter the item from the database into the hook:
const form = useForm({.
initialValues: {
option: 1
},
});
Is this not compatible with the Select component, because it works internally only with strings. So in this case the correct value is not selected:
<Select
label="Option"
{...form.getInputProps("option")}
/>
Now how can I solve this problem in an elegant way? Is there an API/option for this or do I really need to manually convert each numeric value into a string to make this compatible with Mantine?
Thanks a lot.
The code below should be self explanatory. If not, please read the #mantine/form package documentation.
import { Select } from "#mantine/core";
import { useForm } from "#mantine/form";
const data = [
{ value: "", label: "Select an option", disabled: true },
{ value: "1", label: "Option 1" },
{ value: "2", label: "Option 2" },
];
export default function MyForm() {
// create a form with initial values (field: value map)
// that match the `value`s defined in `data:
const form = useForm({
initialValues: {
status: "",
},
});
// create form submit handler:
const formOnSubmit = form.onSubmit(
(values) => console.log(values),
// optional, as form.getInputProps() can make inputs display the error themselves:
(errors) => console.error(errors)
);
return (
<form onSubmit={formOnSubmit}>
<Select
label="Status"
placeholder="Status"
data={data}
{...form.getInputProps("status")}
/>
<Group position="right" mt="md">
<Button type="submit">Submit</Button>
</Group>
</form>
);
}
I believe you can fix this by casting the type of the "value".
This is undocumented, and potentially could break with future releases, but you can do this:
import { Select } from "#mantine/core";
import { useForm } from "#mantine/form";
const data = [
{ value: '', label: 'Select' },
{ value: 2 as any, label: 'New' } // <-- here's the magic
];
export default function MyForm() {
const form = useForm({
initialValues: {
status: ''
},
});
return (
<form onSubmit={form.onSubmit((values) => console.log(values))}>
<Select label="Status" placeholder="Status"
data={data}
{...form.getInputProps('status')} />
<Group position="right" mt="md">
<Button type="submit">Submit</Button>
</Group>
</form>
);
}
I assume that what's happening inside the Select ultimately is some equality checks that will pass with numbers just as well as strings, so you get the correct item selected when it renders, and the correct value out when it submits.
I personally use this with a zodResolver that would complain bitterly about providing strings where it expects a number, and I'm loathe to make my zod schemas more complex to handle this problem.
To be fair, I can appreciate Mantine taking the approach of only supporting strings, since native HTML <select /> elements will always give you strings, and opening the door to anything else is probably a lot of work.
Still, seems like just providing the number value, and keeping typescript happy with as any (or you could do as unknown as string if you're not into the whole brevity thing) works a treat - at least for now!
I use this code:
import { Select } from "#mantine/core";
import { useForm } from "#mantine/form";
const data = [
{ value: '', label: 'Select' },
{ value: 2, label: 'New' }
];
export default function MyForm() {
const form = useForm({
initialValues: {
status: ''
},
});
return (
<form onSubmit={form.onSubmit((values) => console.log(values))}>
<Select label="Status" placeholder="Status"
data={data}
{...form.getInputProps('status')} />
<Group position="right" mt="md">
<Button type="submit">Submit</Button>
</Group>
</form>
);
}

Doesn´t render items React useState

I would like to add new tasks but they are not rendered, here is this part of my code:
setItemsFromBackend([...itemsFromBackend, {id: uuidv4(), content: text }]);
setText("")
}
const [itemsFromBackend, setItemsFromBackend] = useState([{ id: uuidv4(), content: "First task" }]);
const [text, setText] = useState("");
const columnsFromBackend = {
[uuidv4()]: {
name: "Requested",
items: itemsFromBackend,
},
[uuidv4()]: {
name: "To do",
items: [],
},
[uuidv4()]: {
name: "In Progress",
items: [],
},
[uuidv4()]: {
name: "Done",
items: [],
},
};
<div>
<input type="text" value={text} onChange={(e) => setText(e.target.value)}/>
<button onClick={addItem}>Add</button>
</div>
Here is the complete project in codesandbox:
https://codesandbox.io/s/trello-task-yhbmu?file=/src/Kanban.jsx
Any help will be appreciated. Thanks!
setItemsFromBackend({ ...itemsFromBackend, id: uuidv4(), content: text });
itemsFromBackend is an array, so you're spreading in the wrong spot. Try this:
setItemsFromBackend([...itemsFromBackend, {id: uuidv4(), content: text }]);
First thing, instead of a div use a proper form around the form elements. And instead of listening on the button's click event, listen on form submit, so you can add an item by hitting the Enter key too, not just button click.
<form onSubmit={addItem}>
<input type="text" value={text} onChange={(e) => setText(e.target.value)} />
<button>Add</button>
</form>
I think, you should update the way you handle state. At the moment there are different pieces everywhere. You use columns, columnsFromBackend and these also contain the itemsFromBackend state. And with all these it's way too easy to mutate the state without realising it.
In your addItem method you update itemsFromBackend by using setItemsFromBackend and you forgot that you should also update the columns by using setColumns. If you don't use setColumns, React will not be aware of the changes and you won't see the updates because React won't re-render the components.
Not sure what you receive from your backend, but you should either use the columnsFromBackend object as state (which doesn't seem to be flat enough), or you could create a new state for each columns. You kind of use both of these at the moment by storing the requested column in itemsFromBackend.
The following snippet uses only columns and text states, everything unnecessary is removed, and columns is updated in addItem the same way as you do it in onDragEnd:
function Kanban() {
const [columns, setColumns] = useState({
[uuidv4()]: {
name: "Requested",
items: [{ id: uuidv4(), content: "First task" }]
},
[uuidv4()]: {
name: "To do",
items: []
},
[uuidv4()]: {
name: "In Progress",
items: []
},
[uuidv4()]: {
name: "Done",
items: []
}
});
const [text, setText] = useState("");
const addItem = (e) => {
e.preventDefault();
const item = { id: uuidv4(), content: text };
const requestedColumnId = Object.entries(columns).find(
(i) => i[1].name === "Requested"
)[0];
const column = columns[requestedColumnId];
setColumns({
...columns,
[requestedColumnId]: {
...column,
items: [...column.items, item]
}
});
setText("");
};
const onDragEnd = (result, columns, setColumns) => {
// no changes here
}
return (
<div style={{ display: "flex", justifyContent: "center", height: "100%" }}>
<form onSubmit={addItem}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button type="submit">Add</button>
</form>
{/* no changes below */}
You can find the forked sandbox here: https://codesandbox.io/s/trello-task-forked-okix3

Adding new options to form on click in ReactJs

I am doing a React search where user can add multiple filters. The idea is that at first there is only one filter (select and input field) and if user wishes to add more, he can add one more row of (select and input) and it will also take that into account.
I cannot figure out the part on how to add more rows of (select, input) and furthermore, how to read their data as the list size and everything can change.
So I have multiple options in the select array:
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
Now if user selects the first value from the Select box and then types a text in the input box I will get their values and I could do a search based on that.
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends React.Component {
state = {
selectedOption: null,
textValue: null
};
handleOptionChange = selectedOption => {
this.setState({ selectedOption: selectedOption.value });
};
handleTextChange = event => {
this.setState({ textValue: event.target.value });
};
handleSubmit = () => {
console.log(
"SelectedOption: " +
this.state.selectedOption +
", textValue: " +
this.state.textValue
);
};
addNewRow = () => {
console.log("adding new row of filters");
};
render() {
const { selectedOption } = this.state;
return (
<div>
<div style={{ display: "flex" }}>
<Select
value={selectedOption}
onChange={this.handleOptionChange}
options={options}
/>
<input
type="text"
value={this.state.textValue}
onChange={this.handleTextChange}
/>
</div>
<button onClick={this.addNewRow}>AddNewRow</button>
<button onClick={this.handleSubmit}>Submit</button>
</div>
);
}
}
export default App;
I have also created a CodeSandBox for this.
If user clicks on the addNewRow a new row should appear and the previous (search, input) should be selectable without the row that was previously selected.
I don't even really know how I should approach this.
To add new row of inputs on click of button you need to add new input item into the list of inputs, like I have mention below::
import React, { Component } from 'react'
import Select from "react-select";
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends Component {
constructor(props) {
super(props);
this.state = { inputGroups: ['input-0'] };
}
handleSubmit = () => {
console.log("form submitted");
};
AddNewRow() {
var newInput = `input-${this.state.inputGroups.length}`;
this.setState(prevState => ({ inputGroups: prevState.inputGroups.concat([newInput]) }));
}
render() {
return (
<div>
<div>
<div>
{this.state.inputGroups.map(input =>
<div key={input} style={{ display: "flex" }}>
<Select
options={options}
/>
<input
type="text"
// value={this.state.textValue}
// onChange={this.handleTextChange}
/>
</div>
)}
</div>
</div>
<button onClick={() => this.AddNewRow()}>AddNewRow</button>
<button onClick={this.handleSubmit()}>Submit</button>
</div>
);
}
}
export default App;
After click on "AddNewRow" button it will add new input group for you. Now you need to wrap this inputGroup inside "Form" to get data of each inputGroup on click of submit.
I hope it will resolve your issue.

Getting error on React Component on Render

As I an New to ReactJS.
What i am doing is when i type is any field State should be update in particular field -
As This is my LoginComponet and Setting small Form -
import React, { Component } from 'react';
import '../css/style.css';
export class LoginCompoent extends Component {
constructor(props) {
super(props);
this.state = {
field: {
phone: {
value: '',
validations: [],
errors: []
},
password: {
value: '',
validations: [],
errors: []
}
}
};
this.handelChangeEvent = this.handelChangeEvent.bind(this);
}
componentDidMount() {
}
handelChangeEvent(event) {
this.setState({
field: {
[event.target.id]: {
'value': event.target.value
}
}
});
}
render() {
console.log(this.state);
return (
<div className="loginMainDiv" >
<div className="">
<input className="login_inp" placeholder="Mobile Number"
value={this.state.field.phone.value}
onChange={this.handelChangeEvent}
type="text" name="phone" id="phone"
/>
<input className="login_inp" placeholder="Password"
value={this.state.field.password.value}
onChange={this.handelChangeEvent}
type="password" name="password" id="password"
/>
<button className="login_btn" >Login Safely</button>
</div>
</div>
);
}
}
Expected Result - on console.log(this.state);
when I type 9 in phone field -
field: {
phone: {
value: '9',
validations: [],
errors: []
},
password: {
value: '',
validations: [],
errors: []
}
}
Getting Result -
field: {
phone: {
value: '9'
}
}
I don't know why all fields are suddenly hidden when i update only phone field. ?
Because of this password is not setting in the form. ERROR - this.state.field.password is undefined ???
Deep merging issues, as you set a name property to your input, this should work:
this.setState(prevState => ({
field: {
...prevState.field,
[event.target.name]: {
'value': event.target.value
}
}
}));
In your handleChangeEvent function, you are updating the value of the field in the state:
this.setState({field: {
[event.target.id]: {
'value': event.target.value
}
}})
This will obviously overwrite the existing value of the field.
In your case, I would recommend using the callback function inside the setState. Please see the docs.
For example, if you want to update the value of the phone but also you want the value of the password to remain unchanged, You could do something like this:
handleChangeEvent = (event) => {
this.setState((prevState) => {
return {field: {
[event.target.id]: event.target.value,
...prevState.field
}
};
});
}
As I have tried all the Above answers but facing the error.
ERROR- Uncaught TypeError: Cannot read property 'id' of null on this.setState( prevState => ( {} ) ).
The Problem was - the reference of event are not maintain in async call that's why event.target is becoming null and getting above Error.
I got the concept of event.persist() which helps to maintain all the references of the event and make the Wrapper to the browser event with async calls.
You can go to this Article Reference

semantic ui react Setting dropdown value to state

how to have dropdowns selected value in state.here is my code iam getting value for name field but dropdown not working, can anyone find out what i am missing?
MyComponent.js
import React,{Component} from 'react';
class MyComponent extends Component{
state={
data:{
name:'',
subject:''
}
}
onChange = e =>
this.setState({
data: { ...this.state.data, [e.target.name]: e.target.value }
},()=>{
console.log(this.state.data);
}
)
render(){
const {data}=this.state;
const subjects= [
{text: '1',value: 'kannada'},
{text: '2', value: 'english'},
{text: '3',value: 'hindhi'}
]
return(
<div>
<Input
name="name"
onChange={this.onChange}
placeholder='Your name ...'
/>
<Dropdown
placeholder='Select Subject'
name="subject"
onChange={this.onChange}
selection
options={subjects}
/>
</div>
)
}
}
export default MyComponent;
how to have selected dropdown value in state?, iam getting changed value for name field but for dropdown not getting.
handleChange = (e, { value }) => this.setState({ value })
Add value prop to Dropdown
render(
const { value } = this.state;
return(
<Dropdown
placeholder='Select Subject'
name="subject"
onChange={this.handleChange}
selection
options={subjects}
value={value}
/>)
)
If anyone is using react hooks and semantic ui react, this is how I got it to work, without having to create a separate change handler function for it.
const options = [
{ key: "1", text: "Speaker", value: "SPEAKER" },
{ key: "2", text: "Event Planner", value: "EVENT_PLANNER" }
];
const [values, setValues] = useState({
firstName: "",
userType: ""
});
const onChange = (event, result) => {
const { name, value } = result || event.target;
setValues({ ...values, [name]: value });
};
<Form.Dropdown
placeholder="I am a.."
name="userType"
label="User Type"
selection
onChange={onChange}
options={options}
value={values.userType}
/>
What kept throwing me off was the 'result' that the onChange function takes as an argument. Since the options are stored as objects in an array, the correct way to access their values is with the 'result' and not 'event.target.'
One more way to use DropDown in React Semantic. it worked perfectly for me.
const options = [
{ key: 'ex1', text: 'Example 1', value: 'Example 1' },
{ key: 'ex2', text: 'Example 2', value: 'Example 2' },
]
Method to set value
handleSelectChange=(e,{value})=>this.setState({stateValue:value})
In Form
<Form.Select fluid label='List Example' options={options}
placeholder='List Example'
value={value}
onChange={this.handleSelectChange} />
You can use the Form.Field also.
For more information.
constructor(props) {
super(props);
this.state={
subject : ""
}
}
handleChange = (e, result) => {
const { name, value } = result;
this.setState({
[name]: value
});
};
render() {
const options = [
{ key: 'ex1', text: 'Example 1', value: 'Example 1' },
{ key: 'ex2', text: 'Example 2', value: 'Example 2' },
];
return(
<div>
<Form>
<Form.Field
placeholder="Subject"
name="subject"
label="Subject"
control={Dropdown}
fluid
selection
onChange={this.handleChange}
options={options}
value={this.state.subject}
/>
</Form>
</div>
);
}

Resources