I am trying to add one cascaded form(inner) field dynamically on click of a button +Add Content. The array for the field is getting updated but the view is still same.
However, when I try to add the outer field on click of a button +Add Heading dynamically its getting added with no issues.
Below is the stackblitz url for reference. Thanks in advance.
https://stackblitz.com/edit/react-utjwsu?embed=1&file=index.js
You're only ever rendering one Content field, and the {this.AddContentInput} isn't valid syntax. You can edit the AddContentBox method to render all of the Content fields:
Original:
...
<FormGroup>
<Label className="set-label-pos upload-img" for="Heading">Content</Label>
<input className="form-control" type="text"/>
</FormGroup>
{this.AddContentInput}
...
Replaced with:
...
{ this.AddContentFields(element) }
...
and
AddContentFields(element) {
return element.Content.map(content => {
return (
<FormGroup>
<Label className="set-label-pos upload-img" for="Heading">Content</Label>
<input className="form-control" type="text"/>
</FormGroup>
);
})
}
Here's an edited version of the app with my changes: https://stackblitz.com/edit/react-lcy2te
Your code is working fine and the state is updating perfectly. The problem is that the Content part is added only once in your code. I just used a function which you already added in it
{this.addContentTextBox(element.Content)}
Just replace the AddContentBox function with this:
AddContentBox(){
return this.state.content.map((element,i)=>(
<div className="header-content" key={i} >
<div className="heading-content-wrapper">
<FormGroup>
<Label className="set-label-pos upload-img" for="Heading">Heading</Label>
<input className="form-control" type="text"/>
</FormGroup>
{this.addContentTextBox(element.Content)}
{this.AddContentInput}
<FormGroup>
<Button color="success" onClick={this.AddContentInput.bind(this,i)}>+ Add Content</Button>
</FormGroup>
</div>
</div>
))
}
You have the code in that file, but i think you forgot to add it in the function.
Hope it helps :)
You are mixing the content variable. In your function AddContentBox() you are using this.state.content.map(...)
this.state.content is an array of object like {Heading: '', Content: [{value: ''}]}
BUT in your function AddContentInput() your are pushing an object inside an object of this array contents[index].Content.push({value:''})
Depending on your needs you should either push in the root array contents.push({Heading: '', Content: [{value: ''}]} OR iterate the right array in your function AddContentBox() and use this.state.content[0].Content.map(...)
Related
While using
vee-validate#4.4.5
vue#3.1.4
I encountered following problem:
<Form #submit="onSubmit" :validation-schema="schema">
<div class="mb-3">
<label for="edit-email" class="form-label">E-mail</label>
<Field
id="edit-email"
class="form-control"
name="email"
type="text"
:validateOnInput="true"
/>
<ErrorMessage class="invalid-feedback" name="email" />
</div>
<button #click="checkEmail" type="button">Check e-mail address</button>
<!-- non-important part of the form -->
</Form>
I'd like to manually validate <Field /> when the button is clicked (error message should appear) and then I want to use the field's value to perform another action.
I wrote following code:
import { useFieldValue } from 'vee-validate';
// setup
setup() {
const schema = object({
email: string().required().email().label("e-mail"),
});
function verifyEmail(v) {
const emailValue = useFieldValue("email");
console.log(emailValue); // empty ComputedRefImpl
}
return {
schema,
verifyEmail,
};
},
}
And this doesn't work.
emailValue is returned as ComputedRefImpl with an undefined value.
I guess I'm mixing Components & Composition API flavors of vee-validate. But still don't know how to resolve this issue.
Created sandbox with this: https://codesandbox.io/s/vue3-vee-validate-form-field-forked-xqtdg?file=/src/App.vue
Any help will be much appreciated!
According to vee-validate's author:
https://github.com/logaretm/vee-validate/issues/3371#issuecomment-872969383
the useFieldValue only works as a child of a useForm or useField.
So it doesn't work when <Field /> component is used.
I have some tags that display text they conditionally render <input /> tags by checking whether the edit state is true or false. When true, instead of showing text, I render an <input /> tag to make inline edit.
Everything works well. The only problem is, when one <button> tag changes the edit state to true, then, instead of showing input field for editing where Edit was clicked, every tag renders their input field.
How do I limit this rendering of input field for only those tags from where the edit state was changed by the Edit button click?
My code:
const [ edit, setEdit ] = useState(false);
const isEdit = edit;
<div>
<p>{ !isEdit ? (<span>Email: {userProfile.email} <button onClick={e=>setEdit(!edit)}>Edit</button></span>) : (<span>Email:
<input type="text" placeholder="email"
name="email" onChange={e=>setEmail(e.target.value)}/>
<button type="submit" onClick={addUserEmail}>Save</button></span>
)}
</p>
<p>About: { !isEdit ? (<span> {userProfile.about} <button onClick={e=>setEdit(!edit)}>Edit</button>
</span>) :
(<span>
<input type="text" placeholder="about"
name="about" onChange={e=>setAbout(e.target.value)}
/>
<button type="submit" onClick={addUserAbout}>Save</button>
</span>)
)}
</p>
</div>
There are a couple of solutions, but the cleanest way would probably be to separate those editable fields into their own component since each of them has its own state.
For example, you can create a generic EditableField component similar to this one:
function EditableComponent({defaultIsEditing = false, renderText, renderInput}) {
const [ isEditing, setIsEditing ] = useState(defaultIsEditing);
if(!isEditing){
//Non-edit mode
return (<span> {renderText()} <button onClick={e=>setEdit(!edit)}>Edit</button></span>);
}
//Edit mode
return renderInput();
}
Then use it as this:
<div>
<EditableComponent
renderText={() => <>Email: {userProfile.email}</>}
renderInput={() => (<span>Email:
<input type="text" placeholder="email" name="email" onChange={e=>setEmail(e.target.value)}/>
<button type="submit" onClick={addUserEmail}>Save</button>
</span>)}
/>
{/* ...repeat for all fields */}
</div>
This solution ensures that you don't repeat the same logic over and over. With two fields you might be okay just making two state variables (e.g. isEdit1, isEdit2), but the more you add the more cumbersome it will become.
Another alternative would be to store the name of the input you're editing as state, this will ensure that only one field is edited at a time, but you need to take care of saving old fields when starting to edit new ones
I am creating a Sign in and Sign up page. Both sign in and sign up will be shown on the same page, without redirecting it to another page.
Right now, I have two functions showSignUp and showSignIn. These two returns the form. I then render them by calling {this.showSignIn()}. I have a button says "Sign In", and a Link saying "Create a new account". Clicking on the create a new account should show the "Sign up" form. I inserted my showSignIn() function below. The showSignUp function is similar to this.
But I am unsure how should I solve the clicking on a link calls another function.
{I solved the problem by creating one js file for sign in and one for sign up previously. But this cause alot of repetitive code. That is why I am trying to merging them.}
return (
<form>
<div className="login-screen">
<div className="formField">
<label className="formField-Label" htmlFor="name">
E-mail
</label>
<input
type="email"
id="email"
className="formField-Input"
placeholder="Enter your e-mail address"
name="email"
value={email}
onChange={this.handleChange}
/>
</div>
<div className="formField">
<label className="formField-Label" htmlFor="password">
Password
</label>
<input
type="password"
id="password"
className="formField-Input"
placeholder="Enter your password"
name="password"
value={password}
onChange={this.handleChange}
/>
</div>
<div className="formField">
<button className="form-button" onClick={this.signin}>
Sign In
</button>
<Link to={this.showSignUp} className="form-link">
Create a new account
</Link>
</div>
</div>
</form>
);
I suppose your Link comes from react-router-dom, so it only accepts string or object as to prop, see https://reacttraining.com/react-router/web/api/Link.
You should replace it with a <button onClick={this.showSignUp}>Create...</button>
Note that your showSignUp function should trigger a state change in order to render again and change the form displayed. It would look something like :
state = {
showSignUp: false,
}
renderSignUp() {
return (
<form>
// ...
<button onClick={() => this.setState({ showSignUp: false })} className="form-link">Create a new account</button>
</form>
);
}
renderSignIn() {
// Returns your sign in form
}
render() {
return this.state.showSignUp ? this.renderSignUp() : this.renderSignIn();
}
I second adesurirey's answer, but it doesn't have to be a button, you can attach and onClick listener to just about any html element so if you wanted it to behave more like a normal link it could even be an <a /> tag with no new route or a <span> that you style to look like a <Link>
So I’ve spent the last few weeks learning react and now I’m trying to do so with firebase.
Right now, I am trying to make it possible to edit and delete a blog post. I’ve made a few similar things over the last few weeks with express, rails etc so I thought I got a pretty good handle on it so right now I’m trying to compare and know the differences between them.
Most of the functions have been easily refactored to work with what I’m doing now so I’m not sure exactly how to tweak this one to work here or if it won’t work at all which would then turn to how do I properly do it here.
The original code I used last time that I tried to edit for this was:
getInitialState(){
return {editable: false}
},
handleEdit(){
if( this.state.editable){
var title = this.refs.title.value;
var article= this.refs.article.value;
var id = this.props.post.id;
var post = {id: id, title: title, article: article};
this.props.handleUpdate(post);
}
this.setState({editable: !this.state.editable})
},
render: function(){
var title= this.state.editable? <input className= "form-control" type='text' ref='title' defaultValue={this.props.post.title}/>:
<h3>{this.props.post.title}</h3>;
var article= this.state.editable ? <input className= "form-control" type='text' ref='article' name="logEntryEdit" defaultValue={this.props.post.article}/>:
<p className="postText">{this.props.post.article}</p>;
return(
<div >
<div className="text-center postTitle">
{title}
</div>
<hr/>
<br/>
<div className="text-center" id="postText">
{article}
</div>
<br/>
<hr/>
<button className="btn btn-danger delete-button" onClick={this.props.handleDelete}>Delete</button>
<button className="btn btn-primary" onClick={this.handleEdit}>{this.state.editable ? 'Submit' : 'Edit'}</button>
</div>
)
Naturally I edited the item names and such. How I set up my edit page was by first passing the selected post via the id that firebase generates into a single view page and then pass it in into the edit page. The delete should be from that view page as well.
This is the edit where I tried to put in the above function:
constructor(props){
super(props);
this.state = {
title: '',
body: '',
post:{},
};
}
componentDidMount(){
database.ref(`posts/${this.props.match.params.postId}`)
.on('value', snapshot => {
this.setState({post: snapshot.val()});
});
this.postId= this.props.match.params.postId;
}
//edit function goes here
render() {
return (
<div className="container">
<form onSubmit={this.onHandleUpdate}>
<div className="form-group">
<input value={this.state.post && this.state.post.title} className="form-control" type="text" name="title" ref="title" placeholder="Title" onChange={this.onInputChange}/>
</div>
<br/>
<div className="form-group">
<input value={this.state.post && this.state.post.body} className="form-control" type="text" name="body" ref="body" placeholder="Body" onChange={this.onInputChange}/>
<br/>
</div>
<button className="btn btn-success">Submit</button>
</form>
</div>
);
}
When I tried adding it in I also got a
A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
It linked me to the docs which I went over as well.
Can you please tell me how to make the original code I had fit in and work for this time or how it is supposed to be done so I can compare and understand the difference?
This error stems from the fact that you are stating that the input fields are updated via a this.onInputChangefunction and yet that function is not defined anywhere. This means there is no way to update the inputs and react does not like that. either define your this.onInputChange function or switch to using refs (when you use ref react call that an uncontrolled component).
I'm using React and Meteor to generate a table of information about items in a catalog. I also made a form so that users can add new items. Each item has a name, price and manufacturer. This works perfectly fine when I make all the inputs text areas, but I want to make the manufacturer input a with tags generated according to a Meteor collection called Manufacturers.
Form = React.createClass({
propTypes: {
manufacturer: React.PropTypes.object.isRequired
},
renderOptions(){
return(<option value={this.props.manufacturer.name}>{this.props.manufacturer.name}</option>);
},
submitItem(event){
event.preventDefault();
var name=React.findDOMNode(this.refs.nameInput).value.trim();
var price=React.findDOMNode(this.refs.priceInput).value.trim();
var manufacturer=React.findDOMNode(this.refs.manufacturerInput).value.trim();
Items.insert({
name: name,
price: price,
manufacturer: manufacturer,
});
React.findDOMNode(this.refs.nameInput).value="";
React.findDOMNode(this.refs.priceInput).value="";
},
render(){
return(
<footer>
<form className="new-item" onSubmit={this.submitItem}>
<input type="text" ref="nameInput" placeholder="Name" required />
<input type="text" ref="priceInput" placeholder="Price" required />
<button onClick={this.submitItem}>ADD</button>
</form>
<select ref="manufacturerInput" defaultValue="" required />
<option value="" disabled>Manufacturer</option>
{this.renderOptions()}
</select>
</footer>
);
}
});
How do I make this work? I've been pulling my hair out all day trying to figure it out, but Meteor just keeps telling me "Expected corresponding JSX closing tag for (38:8)". What am I doing wrong here? It works just fine when they're all but this destroys my entire app.
You accidentally closed your select element here:
<select ref="manufacturerInput" defaultValue="" required />
Remove the /.