How rerender cloned childs with new props after they are initially rendered - javascript

The idea is to make a form that will validate the inputs and put all the logic for validating inside the MyForm component. so the only thing that comes from the onsubmit call is a error object and a object with the form values.
i would also like to validate when the user clicks on submit and when the user touched a field with onBlur.
i would like to keep all the logic in MyForm
The problem is that i'm trying to figure out how to change the InputGroup props from MyForm component and display validation errors for example.
how can i change to props of the childs when the user clicks the submit button or a field is touched and call the handleBlur in InputGroup.
also tell me is this is a wrong approach
i managed to change the props of InputGroup from MyForm at runtime.
this will change the label of every child.
children = React.Children.map(this.props.children, (child) =>
React.cloneElement(child, {
label: 'foo'
})
)
...
return (
<form onSubmit={this.validate}>
{children}
</form>
)
NewCustomerContainer:
class NewCustomerContainer extends Component {
handleSubmit(errors, customer) {
// if errors is empty do something with customer
}
render() {
return (
<div className={shared.flexRow}>
<div className={shared.flexColumn}>
<ColumnHeader
headerTitle="New Customer" />
<MyForm formHandler={this.handleSubmit}>
<InputGroup
type="text"
label="Customer Name"
placeholder="Customer Name"
name="customerName"
isRequired={true} />
<InputGroup
type="text"
label="Customer Number"
placeholder="Customer Number"
name="customerNumber" />
<InputGroup
type="text"
label="Customer Email"
placeholder="Customer Email"
name="customerEmail"
isRequired={true}
validateOption="email" />
</MyForm>
</div>
</div>
)
}
}
export default NewCustomerContainer
MyForm
export class MyForm extends Component {
validate(e) {
e.preventDefault()
children = React.Children.map(this.props.children, (child) =>
React.cloneElement(child, {
label: 'foo'
})
)
// handle validation login here
// then call the parent function with the errors and values
this.props.testForm(errors, values)
}
render() {
const children = React.Children.map(this.props.children, (child) =>
React.cloneElement(child, {
})
)
return (
<form onSubmit={this.validate}>
{children}
</form>
)
}
}
InputGroup:
export class InputGroup extends Component {
handleBlur() {
// handle blur here
}
render() {
return (
<div className={shared.formGroup}>
<label className={shared.formLabel}>{this.props.label}</label>
<input className={shared.formError} type={this.props.type} name={this.props.name} placeholder={this.props.placeholder} onBlur={this.handleBlur} />
<span className={shared.formError}>{this.props.foo}</span>
</div>
)
}
}
i'm very new to React and i know i can use redux-form or a other plugin, but i want to challenge myself and understand the concept how children works. there are plenty of examples out there but they do not answer my questions
thanks in advance.

Related

Passing down use state values gives undefined

I have child Component with input, and in the parent I've a button that disabled based on this input.
I thought about creating 'top level' state and pass the values like this:
const Parent = () => {
const [projectName, setProjectName] = useState('')
return (
<>
<Child projectName={projectName} setProjectName={setProjectName} />
<button disabled={projectName.length === 6}/>
</>
)
}
My question is, Is it solid react way to implement this?
In the child component I'm getting undefined for both projectName and setProjectName, why is it happening and how can I solve this?
Child Component:
const Child= ({projectName, setProjectName}) => {
return (
<>
<h2><StyledInlineSpan>Projects / </StyledInlineSpan>Create New Project</h2>
<Input autoFocus placeholder="Project Name" value={projectName} onChange={({ target }) => { setProjectName(target.value) }} />
</>
)
}
You shouldn't get an undefined, I think it is a problem with your Input component. I created a codesandbox with the same use case. As you type into the input, the Child component will change the state variable and the parent uses it.
It is completely fine to create a controlled Child component like this.
Seems like you have mis-spelled Input.
In React if the first letter of any HTML tag is capital, then it is treated as a custom React component.
Simply rename this
<Input autoFocus placeholder="Project Name" value={projectName} onChange={({ target }) => { setProjectName(target.value) }} />
to
<input autoFocus placeholder="Project Name" value={projectName} onChange={({ target }) => { setProjectName(target.value) }} />

REACT: Should HTML forms be Controlled or Uncontrolled components?

I am having a dilemma as to where Where My Form State Should Live.
React Docs mention:
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 simply for holding the state and add it
somewhere in the hierarchy above the common owner component.
I have a simple comment UI.
The user enters a comment in the text area.
The user enters name in input field.
The user clicks post and the comment is displayed below.
Components Highrarchy is as follows:
- CommentSection.jsx ---> main (should "Post" state (?))
-comment-post.jsx ---> User Comment Form
- comment-table.jsx
- comment.jsx
- comment-avatar.jsx
- comment-body.jsx
Issue: In comment-post.jsx the input and textarea fields have onChange() event handler, and there is also a click event handle on submit post button. I can choose to do one of two:
In comment-post.jsx when onChange is trigger send the commentBody to CommentSection.jsx The issue here would be I will be sending the commentBody as soon as user types, then sending name later when it triggers and so on
In comment-post.jsx when onChange is trigger SAVE the value in state, then name the name field in the state, when user clicks submit button send to CommentSection.jsx Benefit is Fields are set first in state, and when the user clicks post they are sent to the parent (As it should be right?)
Should the input fields of the comment i.e. commentBody and Name be
saved in the comment-post.jsx (form component) or the parent?
Right now the state I am doing the 2. Saving form fields in state and then sending the state values on submit.
I think the issue is onChange and onClick are two different handlers, the question is which one should pass the values to parent component ideally?
class CommentSection extends Component {
state = {
posts: []
};
handleCommentPost = post => {
const posts = this.state.posts;
posts.push(post);
this.setState({ posts });
};
render() {
console.log("comment:", this.state.posts);
return (
<React.Fragment>
<h1>comments</h1>
<div className="row bootstrap snippets">
<div className="col-md-6 col-md-offset-2 col-sm-12">
<div className="comment-wrapper">
<Comment_Post onClick={this.handleCommentPost} />
<Comment_Table comments={this.state.posts} />
</div>
</div>
</div>
</React.Fragment>
);
}
}
class Comment_Table extends Component {
render() {
const posts = this.props.comments;
let count = 0;
return (
<div className="panel panel-info">
<hr />
<ul className="media-list">
{posts.map(post => (
<Comment key={count++} comment={post.commentBody} />
))}
</ul>
</div>
);
}
}
class Comment extends Component {
render() {
return (
<li className="media">
<Comment_Avatar userAvatar={this.props.commentAvatar} />
<Comment_Body userComment={this.props.comment} />
</li>
);
}
}
class Comment_Body extends Component {
render() {
const { userComment } = this.props;
return (
<div className="media-body">
<span className="text-muted pull-right">
<small className="text-muted">30 min ago</small>
</span>
<strong className="text-success">#MartinoMont</strong>
<p>
{userComment}
{}
</p>
</div>
);
}
}
class Comment_Post extends Component {
state = {
commentBody: null
};
onCommentChange = e => {
this.setState({ commentBody: e.target.value });
};
onCommentPost = e => {
const commentBody = this.state.commentBody;
if (commentBody !== null) {
this.props.onClick({ commentBody });
}
};
onNameInput = e => {};
onCommentPostError() {}
render() {
return (
<React.Fragment>
<div className="panel-heading p-heading">Comment panel</div>
<div className="panel-body">
<textarea
onChange={this.onCommentChange}
className="form-control"
placeholder="write a comment..."
rows="3"
/>
<label htmlFor="fname" onChange={this.onNameInput}>
Name:{" "}
</label>
<input id="fname" placeholder="John" />
<br />
<button
type="button"
className="btn btn-info pull-right"
onClick={this.onCommentPost}
>
Post
</button>
<div className="clearfix" />
</div>
</React.Fragment>
);
}
}
class Comment_Avatar extends Component {
render() {
return (
<a href="#" className="pull-left">
<img
src="https://bootdey.com/img/Content/user_1.jpg"
alt=""
className="img-circle"
/>
</a>
);
}
}
I think the issue is onChange and onClick are two different handlers, the question is which one should pass the values to parent component ideally?
There are two types of Forms that we use in standard User interface design. First is when any change in the Input saves the change. Second, when after making changes to the Form Elements, you press a submit button and then the changes are saved.
Since you have implemented the second type, your onChange should deal with controlling your TextArea state and your onClick should deal with the submit. So your code is just fine.
I am having a dilemma as to where Where My Form State Should Live.
That depends...In your case you only have one Form, and two Form Elements none of which are re-usable. So these uncontrolled Forms work well for you. However, if you wanted to have a Form Component that is re-usable, or if you wanted to have a Form with 15 Fields in it, you would not want to write a separate onChange handler for each one of them. For this, you would want to make a controlled Form Component that can handle all these things for you. Here's an example of what that would look like.
export default class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
values: {}
};
}
#boundMethod
handleSubmit(event) {
event.preventDefault();
this.props.submit(this.state.values);
}
#boundMethod
handleChange(event) {
const { name, value } = event.target;
const newValues = Object.assign(
{ ...this.state.values },
{ [name]: value }
);
this.setState({
values: newValues
});
}
public render() {
const { values } = this.state;
return (
<form onSubmit={this.handleSubmit} noValidate={true}>
<div>
{React.Children.map(
this.props.children,
child => (
{React.cloneElement(child, {
value: values[child.props.name],
onChange: this.handleChange
})}
)
)}
<div>
<button type="submit">
Submit
</button>
</div>
</div>
</form>
);
}
}
Then you will be able to use this Form class like so:
<Form
submit={values => {
/* work with values */
}}
>
<input type="hidden" name="name" />
<input type="hidden" name="rating" />
</Form>;

Creating a form in React that saves data

I am trying to create a customer details form in react (currently using react-json-form) where I can reuse the values in the inputs to create a saved file that the app can refer to. I have created the form and can output the results but I am unsure how to save the input values for future use or call them back once they are saved.
If anyone has any suggestions or examples of a form that does this then I would be greatly appreciative.
My code is as follows:
import React, { Component } from 'react';
import JSONTree from 'react-json-tree';
import { BasicForm as Form, Nest, createInput } from 'react-json-form';
const Input = createInput()(props => <input type="text" {...props} />);
const UserFields = () => (
<section>
<h3>User</h3>
<div>Name: <Input path="name" /></div>
<div>Email: <Input path="email" /></div>
</section>
);
export default class ExampleForm extends Component {
state = { data: {} };
updateData = data => this.setState({ data });
render() {
return (
<Form onSubmit={this.updateData}>
<Nest path="user">
<UserFields />
</Nest>
<button type="submit">Submit</button>
<JSONTree data={this.state.data} shouldExpandNode={() => true} />
</Form>
);
}
}
A more simple solution would be to use a form, like a semanti-ui-react form, store the information to the state onChange, then convert the info to JSON for storage.
import { Form, Button } from 'semantic-ui-react'
export default class App extends Component {
constructor() {
super()
this.state = {
name: "",
email: ""
}
}
handleChange = (e, {name, value}) => {
console.log(name, value)
this.setState({[name]: value})
}
render() {
return (
<div>
<Form onSubmit={this.sendDataSomewhere}>
<Form.Field>
<Form.Input name="name" value={this.state.name} onChange={this.handleChange}/>
</Form.Field>
<Form.Field>
<Form.Input name="email" value={this.state.email} onChange={this.handleChange}/>
</Form.Field>
<Button type="submit">Submit</Button>
</Form>
</div>
)
}
}
I use a dynamic method of receiving the input from different fields using the name and val attributes. The values captured in state are then accessible by this.state.whatever
Hope this helped

How do I reference a input element from higher order component

I would like to access the input element from my HOC. How can I achieve this?
const TextInput = (props) => {
const allowed = ['readOnly','tabIndex','placeholder'];
const filteredProps = filterProps(props,allowed);
return (
<div>
<label>{props.field.Name}</label>
<input type="text" ref={props.inputRef} key={props.field.Id} className="form-control" id={props.field.Id} name={props.field.Name}
value={props.value}
onChange={props.onChange}
onKeyDown={props.onKeyDown}
{...filteredProps}
/>
</div>
);
}
TextInput.propTypes = {
fieldMetadata: PropTypes.object,
isValid: PropTypes.bool
}
export default withInputMask(withRequired(withReadOnly(withMinMax(withHidden(TextInput)))));
I have tried a few things but this is the latest attempt.
Inside the withInputMask render method I have inserted the following.
return (
<div>
<Component {...this.props} inputRef={el=> this.inputElement=el} isValid={isValid} onChange={this.onChange} placeholder={inputMaskPattern} />
{hasErrors && <span className={hasErrors}>{error}</span>}
</div>
);
}
}
};
export default withInputMask;
when I open the react dev tools and click on withInputMask component this is what I see.

redux get input text value

I have the following reactjs component.
class Login extends Component {
render() {
if (this.props.session.Authenticated) {
return (
<div></div>
);
}
return (
<div>
<input type="text" placeholder="Username" />
<input type="password" placeholder="Password" />
<input
type="button"
value="Login"
onClick={() => this.props.LoginAction("admin", "password")}
/>
</div>
);
}
}
The Login component makes use of a prop that is set via redux, named "session.Authenticated". If an user session is not authenticated, I present a couple of input fields (one for username and another for password) and a Login button. If I press on the Login button I want to emit an action with the username and password values, passed as parameters. Now, how do I get the values of the two input fields for the parameters ?
IOW, I want to remove the hardcoded "admin, password" in the above code with the values of the two input fields. How to achieve this ?
All the examples point to redux-form which is a new dependency that I do not want to add. I want to keep my dependencies minimal and so using only react, redux and react-redux in my project.
Something like this:
class Login extends Component {
constructor(props){
super(props);
this.state = {
model = {
login: "",
password: ""
}
}
}
render() {
if (this.props.session.Authenticated) {
return null;
}
return (
<div>
<input type="text" value={this.state.model.value.login} onChange={e => this.updateModel(e.target.value, 'login')}placeholder="Username" />
<input type="password" value={this.state.model.password }onChange={e => this.updateModel(e.target.value, 'password')} placeholder="Password" />
<input
type="button"
value="Login"
onClick={() => this.props.LoginAction(this.state.model.login, this.state.model.password)}
/>
</div>
);
}
updateModel(value, name){
var model = extend({}, this.state.model);
model[name] = value;
this.setState({model});
}
}

Categories