React - passing ref attribute as prop - reactjs

I am trying to refactor the code by removing the Form into a different component.
import React, { Component } from "react";
const collections = [];
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: collections,
};
}
handleSubmit = (e) => {
e.preventDefault();
const { data } = this.state,
name = this.refs.name.value;
this.setState({ data: [...data, { name }] }, () => {
this.refs.name.value = "";
});
};
render() {
return (
<div>
...
</div>
<hr />
<Form onSubmit={this.handleSubmit} myRef={(ref) => (ref = "name")} />
</div>
);
}
}
Form component is taking a ref as an attribute.
const Form = ({ onSubmit, myRef }) => {
return (
<form onSubmit={onSubmit} className="mt-10 flex justify-between">
<input
className="bg-gray-300 rounded-lg px-4 py-3 outline-none"
ref={myRef}
type="text"
placeholder="Category name"
/>
<button type="submit" className="mt-5 hover:text-red-500 cursor-pointer">
Add
</button>
</form>
);
};
The ref attribute is passed into the App component using the example from a previous question React - passing refs as a prop
<Form onSubmit={this.handleSubmit} myRef={(ref) => (ref = "name")} />
However, I still get an error

You need to store the ref in a class instance variable
<Form onSubmit={this.handleSubmit} myRef={(ref) => (this.name = ref)} />
and use it like
handleSubmit = (e) => {
e.preventDefault();
const { data } = this.state,
const name = this.name.value;
this.setState({ data: [...data, { name }] }, () => {
this.name.value = "";
});
};
Working demo
Approach 2
you use React.createRef to create a ref like
constructor() {
super();
this.nameRef = React.createRef();
}
---
handleSubmit = (e) => {
e.preventDefault();
const { data } = this.state,
const name = this.nameRef.current.value;
this.setState({ data: [...data, { name }] }, () => {
this.nameRef.current.value = "";
});
};
---
<Form onSubmit={this.handleSubmit} myRef={this.nameRef} />

Related

Comment reply system with React Quill and Firebase Firestore

I'm making a comment system with React Quill as my editor and Firebase Firestore. Each comment post gets stored in firestore. Each stored comment has a reply button, and when clicked, the editor should be populated with the comment content I want to reply to. Basically I need to populate my editor with the content stored in firestore database. Here's a screenshot as to watch I want to achieve:
Comment reply
Here's some code from the comment editor component
class NewComment extends Component {
constructor(props) {
super(props);
this.state = {
comment: {
commentID: "",
content: "",
createDate: new Date(),
featureImage: "",
isPublish: "True",
createUserID: "",
},
};
}
...
onChangeCommentContent = (value) => {
this.setState({
comment: {
...this.state.comment,
content: value,
},
});
};
...
render() {
return (
<Container>
<Row>
<Col xl={9} lg={8} md={8} sn={12}>
<h2 className={classes.SectionTitle}>Comment</h2>
<FormGroup>
<ReactQuill
ref={(el) => (this.quill = el)}
value={this.state.comment.content}
onChange={(e) => this.onChangeCommentContent(e)}
theme="snow"
modules={this.modules}
formats={this.formats}
placeholder={"Enter your comment"}
/>
</FormGroup>
</Col>...
The reply button is in a different component where I render the stored comments. Tell me if you need the full code from the components.
Here is a simple example on how to pass on information between two components via the parent component using function components:
// Index.js
const MyComponent = () => {
const [replyValue, setReplyValue] = useState("");
const onClick = (value) => {
setReplyValue(value);
};
return (
<>
<Comment value="This is a reply" onClick={onClick} />
<Comment value="This is another reply" onClick={onClick} />
<CreateReply quoteValue={replyValue} />
</>
);
};
// Comment.js
export const Comment = ({ value, onClick }) => {
return (
<div className="comment" onClick={() => onClick(value)}>
{value}
</div>
);
};
// CreateReply.js
export const CreateReply = ({ quoteValue = "" }) => {
const [value, setValue] = useState("");
useEffect(() => {
setValue(quoteValue);
}, [quoteValue]);
const onValueUpdated = (newValue) => {
if (newValue !== value) {
setValue(newValue);
}
};
return (
<>
<ReactQuill value={value} onChange={onValueUpdated} />
</>
);
};
Here is the same example using class components:
// Index.js
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
replyValue: ""
};
}
onClick = (value) => {
this.setState({
replyValue: value
});
};
render() {
return (
<>
<Comment value="This is a reply" onClick={this.onClick} />
<Comment value="This is another reply" onClick={this.onClick} />
<CreateReply quoteValue={this.state.replyValue} />
</>
);
}
}
// Comment.js
export class Comment extends React.Component {
render() {
return (
<div
className="comment"
onClick={() => this.props.onClick(this.props.value)}
>
{this.props.value}
</div>
);
}
}
// CreateReply.js
export class CreateReply extends React.Component {
constructor(props) {
super(props);
this.onValueUpdated = this.onValueUpdated.bind(this);
this.state = {
value: props.quoteValue
};
}
componentDidUpdate(prevProps) {
if (this.props.quoteValue !== prevProps.quoteValue) {
this.setState({
value: this.props.quoteValue
});
}
}
onValueUpdated = (newValue) => {
if (newValue !== this.state.value) {
this.setState({
value: newValue
});
}
};
render() {
return (
<>
<ReactQuill value={this.state.value} onChange={this.onValueUpdated} />
</>
);
}
}

Deleting function not working and no errors appear

Good day so I have a question about firebase and perhaps my code as well I wrote some code in JSX and React linked to Firebase and the Button that I'm using to delete is not working properly.
I'm using Parent Child props to pass the function into the page that is needed to be deleted but there is no functionality. I need help thanks!
this is the parent where the function is located :
import React from 'react';
import fire from '../config/firebase';
import Modal from 'react-modal';
// import "firebase/database";
// import 'firebase/auth';
import NotesCard from './note-card';
Modal.setAppElement('#root');
export default class Notes extends React.Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
notes: [],
showModal: false,
loggedin: false
};
this.handleOpenModal = this.handleOpenModal.bind(this);
this.handleCloseModal = this.handleCloseModal.bind(this);
this.handleAddNote = this.handleAddNote.bind(this);
this.handleRemoveNote = this.handleRemoveNote.bind(this);
}
componentDidMount() {
this._isMounted = true;
fire.auth().onAuthStateChanged((user) => {
if(user){
// call firebase from import fire
// grab userData and push it to the dataArray
fire.database().ref(`users/${user.uid}/notes`).on('value', (res) => {
const userData = res.val()
const dataArray = []
for(let objKey in userData) {
userData[objKey].key = objKey
dataArray.push(userData[objKey])
}
// set in the state
if(this._isMounted){
this.setState({
notes: dataArray,
loggedin: true
})
}
});
}else {
this.setState({loggedin: false})
}
});
};
componentWillUnmount() {
this._isMounted = false;
}
handleAddNote (e) {
e.preventDefault()
const note = {
title: this.noteTitle.value,
text: this.noteText.value
}
// reference where we can push it
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes`);
dbRef.push(note)
this.noteTitle.value = ''
this.noteText.value = ''
this.handleCloseModal()
}
handleRemoveNote(key) {
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes/${key}`);
dbRef.remove();
}
handleOpenModal (e) {
e.preventDefault();
this.setState({
showModal: true
});
}
handleCloseModal () {
this.setState({
showModal: false
});
}
render() {
return (
<div>
<button onClick={this.handleOpenModal}>create Note</button>
<section className='notes'>
{
this.state.notes.map((note, indx) => {
return (
<NotesCard
note={note}
key={`note-${indx}`}
handleRemoveNote={this.handleRemoveNote}
/>
)
}).reverse()
}
</section>
<Modal
isOpen={this.state.showModal}
onRequestClose={this.handleCloseModal}
shouldCloseOnOverlayClick={false}
style={
{
overlay: {
backgroundColor: '#9494b8'
},
content: {
color: '#669999'
}
}
}
>
<form onSubmit={this.handleAddNote}>
<h3>Add New Note</h3>
<label htmlFor='note-title'>Title:</label>
<input type='text' name='note-title' ref={ref => this.noteTitle = ref} />
<label htmlFor='note-text'>Note</label>
<textarea name='note-text' ref={ref => this.noteText = ref} placeholder='type notes here...' />
<input type='submit' onClick={this.handleAddNote} />
<button onClick={this.handleCloseModal}>close</button>
</form>
</Modal>
</div>
)
}
}
and this is where the function is being called :
import React from 'react';
import fire from '../config/firebase';
export default class NotesCard extends React.Component {
constructor(props) {
super(props);
this.state = {
editing: false,
note: {}
}
this.handleEditNote = this.handleEditNote.bind(this);
this.handleSaveNote = this.handleSaveNote.bind(this);
}
handleEditNote() {
this.setState({
editing: true
})
}
handleSaveNote(e) {
e.preventDefault()
const userId = fire.auth().currentUser.uid;
const dbRef = fire.database().ref(`users/${userId}/notes/${this.props.note.key}`);
dbRef.update({
title: this.noteTitle.value,
text: this.noteText.value
})
this.setState({
editing: false
})
}
render() {
let editingTemp = (
<span>
<h4>{this.props.note.title}</h4>
<p>{this.props.note.text}</p>
</span>
)
if(this.state.editing) {
editingTemp = (
<form onSubmit={this.handleSaveNote}>
<div>
<input
type='text'
defaultValue={this.props.note.title}
name='title'
ref={ref => this.noteTitle = ref}
/>
</div>
<div>
<input
type='text'
defaultValue={this.props.note.text}
name='text'
ref ={ref => this.noteText = ref}
/>
</div>
<input type='submit' value='done editing' />
</form>
)
}
return (
<div>
<button onClick={this.handleEditNote}>edit</button>
<button onClick={this.props.handleRemoveNote(this.state.note.key)}>delete</button>
{editingTemp}
</div>
)
}
}
Thank you in advance for taking a look at this code.
Second iteration answer
Working sandbox
Problem
looking at https://codesandbox.io/s/trusting-knuth-2og8e?file=/src/components/note-card.js:1621-1708
I see that you have this line
<button onClick={()=> this.props.handleRemoveNote(this.state.note.key)}>delete
Yet your state.note declared as an empty map in the constructor:
this.state = {
editing: false,
note: {}
}
But never assigned a value using this.setState in the component
Solution
Change it to:
<button onClick={()=> this.props.handleRemoveNote(**this.props.note.key**)}>delete</button>
First iteration answer
NotesCard's buttons is firing the onClick callback on render instead on click event.
This is because you have executed the function instead of passing a callback to the onClick handler
Change
<button onClick={this.props.handleRemoveNote(this.state.note.key)}>delete</button>
To
<button onClick={()=> this.props.handleRemoveNote(this.state.note.key)}>delete</button>

Cannot update state with file uploader in reactJS

I created a simple image uploader component that is rendered in a new-image-form that extends a form. but when handling a change(an image upload), the state is updated with an empty opbject (and not with the file object). I tried to debug it but anything goes right until this.setState. Can anyone helps?
NewImagesForm :
class NewImagesForm extends Form {
state = {
data: {
_id: 0,
name: "",
file: null,
},
errors: {},
apiEndpoint: "",
};
render() {
return (
<div className="new-menu-item-form">
<form onSubmit={this.handleSubmit}>
{this.renderInput("name", "Name")}
{this.renderUploadFile("file", "Upload")}
{this.renderButton("Add")}
</form>
</div>
);
}
Form
class Form extends Component {
state = { data: {}, errors: {} };
handleFileUpload = (e) => {
const data = { ...this.state.data };
data[e.currentTarget.name] = e.target.files[0];
this.setState({ data });
};
renderUploadFile(name, label) {
const { data } = this.state;
return (
<UploadImage
name={name}
label={label}
value={data[name]}
onChange={this.handleFileUpload}
></UploadImage>
);
}
}
export default Form;
File Upload Component
const UploadImage = ({ name, label, error, onChange }) => {
return (
<div className="input-group">
<div className="custom-file">
<input
type="file"
onChange={onChange}
className="custom-file-input"
id={name}
name={name}
></input>
{label && (
<label className="custom-file-label" htmlFor={name}>
{label}
</label>
)}
</div>
<div className="input-group-append">
<button className="btn btn-outline-secondary" type="button">
Button
</button>
</div>
</div>
);
};
export default UploadImage;
First of all, react does not recommend the use of extends, because of the combination of extends.
In the Form component, the setState() trigger the render() , but there is no render(). setState does not allow renderUploadFile to be re-executed.
It would be a good idea to use the UploadImage component in the NewImagesForm component. Don't let NewImagesForm extends Form.
But the exactly same way works for me with custom input text and custom select boxes, see the differences:
handleChange = (e) => {
const data = { ...this.state.data };
data[e.currentTarget.name] = e.currentTarget.value;
this.setState({ data });
};
handleFileUpload = (e) => {
const data = { ...this.state.data };
data[e.currentTarget.name] = e.target.files[0];
this.setState({ data });
};

update values in react form input fields

I am new to react and I can fetch the result from form input fields. Now I need to update those values and submit to the backend. I am struggling to find a way to pass all the input field values at once.
constructor(props) {
super(props);
this.state = {
items: [],
isLoaded: false,
data: this.props.location.data
};
}
render() {
return (
<div>
<h2>Update Your Profile</h2>
{items.map(item => (
<Form key={item.uId} onSubmit={this.handleSubmit}>
<label>User Name</label>
<input type="text" defaultValue={item.userName}></input>
<label>Email address</label>
<input type="email" defaultValue={item.email}></input>
</div>
<button type="submit" >Update</button>
</Form>
))}
</div>
);
}
handleSubmit = (e) => {
e.preventDefault();
axios.put('http://localhost:3000/api/user/' + this.state.data, this.state.items).then(response => {
//
});
};
My API call looks like this:
app.put('/api/user/:userId', (req, res, err) => {
User.update(
{ userName: req.body.userName, email: req.body.email },
{
where: {
userId: req.params.userId
}
}
).then(function (rowsUpdated) {
res.json(rowsUpdated)
}).catch(err);
});
How can I modify this code to set a value for this.state.items with all the updated fields values and submit it?
I'd recommend to create a new component to wrap around the <Form /> and move the submit/change event handling to that component for each item. This would allow you to be able to extract individual email/userName for any given <Form /> to send as a PUT to your API endpoint as well as handle the respective input value changes.
Parent Component:
class Parent extends Component {
constructor() {
super();
this.state = {
name: 'React',
items: [
{ uId: 1, email: 'foo#test.com', userName: 'bar' },
{ uId: 2, email: 'baz#test.com', userName: 'foobar' }
]
};
}
render() {
return (
<div>
{this.state.items.map(item =>
<MyForm key={item.uId} item={item} data={this.props.location.data} />)}
</div>
);
}
}
Child/Form Component:
import React, { Component } from 'react';
class MyForm extends Component {
constructor(props) {
super(props);
this.state = {
email: this.props.item.email,
userName: this.props.item.userName
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// https://reactjs.org/docs/forms.html#handling-multiple-inputs
handleChange(e) {
const { target} = event;
const value = target.type === 'checkbox' ? target.checked : target.value;
const { name } = target;
this.setState({ [name]: value });
}
handleSubmit(e) {
e.preventDefault();
const { email, userName } = this.state;
const body = { email, userName };
const json = JSON.stringify(body);
console.log(json);
// axios.put('http://localhost:3000/api/user/' + this.props.data, json).then(response => {});
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>User Name</label>
<input type="text" defaultValue={this.state.userName}></input>
<label>Email address</label>
<input type="email" defaultValue={this.state.email}></input>
<button type="submit" >Update</button>
</form>
);
}
}
export default MyForm;
Here is an example in action.
Hopefully that helps!

How do I implement reusable form for add and update actions in react?

I'm trying to implement <RecipeForm /> in my <AddRecipe /> component. Later on I would like to reuse the same form for an update action.
The recipe is still not added to the list.
I'm defining handleAddRecipe in my App.js.
passing it to <AddRecipe /> component
from here passing it to <RecipeForm /> component
What do I need to fix in these components?
<AddRecipe /> component:
class AddRecipe extends Component {
render() {
return (
<div>
<h2>Add New Recipe:</h2>
<RecipeForm
handleAddRecipe={this.props.handleAddRecipe}
/>
</div>
)
}
}
export default AddRecipe;
repo: https://github.com/kstulgys/fcc-recipe-box/blob/master/src/components/AddRecipe.js
I guess the trickiest part is <RecipeForm /> component:
export default class RecipeForm extends React.Component {
constructor(props) {
super(props);
this.state = {
url: this.props.url || '',
title: this.props.title || '',
description: this.props.description || '',
error: ''
};
}
onUrlChange = (e) => {
const url = e.target.value;
this.setState(() => ({ url }));
};
onTitleChange = (e) => {
const title = e.target.value;
this.setState(() => ({ title }));
};
onDescriptionChange = (e) => {
const description = e.target.value;
this.setState(() => ({ description }));
};
onSubmit = (e) => {
e.preventDefault();
if (!this.state.url || !this.state.title || !this.state.description) {
this.setState(() => ({ error: 'Please provide description and amount.'}));
} else {
this.setState(() => ({ error: ''}));
this.props.onSubmit({
url: this.state.url,
title: this.state.title,
description: this.state.description
});
}
}
render () {
const submitText = this.state.title ? 'Update' : 'Create' ;
return (
<div>
<form onSubmit={this.onSubmit}>
{this.state.error && <p>{this.state.error}</p>}
<input
type='text'
placeholder='picture url'
autoFocus
value={this.state.url}
onChange={this.onUrlChange}
/>
<input
type='text'
placeholder='title'
autoFocus
value={this.state.title}
onChange={this.onTitleChange}
/>
<input
type='text'
placeholder='description'
autoFocus
value={this.state.description}
onChange={this.onDescriptionChange}
/>
<button>Add Expense</button>
</form>
</div>
)
}
}
In my opinion the onSubmit function is not being invoked.
The button on the form must be type="submit"
You should bind onSubmit function to current scope, in the constructor with this.onSubmit = this.onSubmit.bind(this)

Resources