I want to make text editor and save the written data to a server (Mongodb) with its styling, then show the saved written data in the styled way, I did the coding for the editor but the problem now is how to send the data to the server. POST
Here is the code for TextEditor.js
import React, { Component } from "react";
import { Editor } from "react-draft-wysiwyg";
import { EditorState, convertToRaw } from "draft-js";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import draftToHtml from "draftjs-to-html";
export default class TextEditor extends Component {
state = {
editorState: EditorState.createEmpty(),
};
onEditorStateChange = (editorState) => {
this.setState({
editorState,
});
};
render() {
const { editorState } = this.state;
console.log(draftToHtml(convertToRaw(editorState.getCurrentContent())));
return (
<div>
<Editor
editorState={editorState}
toolbarClassName="toolbarClassName"
wrapperClassName="wrapperClassName"
editorClassName="editorClassName"
onEditorStateChange={this.onEditorStateChange}
/>
<textarea
disabled
value={draftToHtml(convertToRaw(editorState.getCurrentContent()))}
></textarea>
</div>
);
}
}
Write Page Write.jsx
import axios from 'axios';
import TextEditor from '../../components/TextEditor/TextEditor';
export default function Write() {
const [desc, setDesc] = useState('');
const { user } = useContext(Context);
const handleSubmit = async (e) => {
e.preventDefault();
const newPost = {
username: user.username,
desc,
};
try {
const res = await axios.post('/posts', newPost);
window.location.replace('/post/' + res.data._id);
} catch (err) {}
};
return (
<div className='write'>
<form className='writeForm' onSubmit={handleSubmit}>
<TextEditor
placeholder='Write your story'
type='text'
className='writeInput writeText'
onChange={(e) => setDesc(e.target.value)}
/>
<button className='writeSubmit' type='submit'>
نشر
</button>
</form>
</div>
);
}
Post.js
const mongoose = require('mongoose');
const PostSchema = new mongoose.Schema({
desc: {
type: String,
required: true,
},
});
module.exports = mongoose.model('Post', PostSchema);
I know that I have to use JSON.stringify(convertToRaw()) and convertFromRaw() but I didn't know where to use them, it's been now week and I'm still trying to solve this problem.
It should be like something this.
import { EditorState } from "draft-js";
const data = convertFromRaw(response);
const state = EditorState.createWithContent(data);
<Editor editorState={state} readOnly={true} />
Related
I am executing search operation from Navbar component for the data that is present in separate Context API, and the results for the search operation will be presented in another component call Blog, which is using same Context API, but the problem here is search operation is not executing in real time, like when I clear the search bar then It's difficult to set search term in use state hook which is present in context API. So in this case how to solve the problem.
Below is my code from context API
import { BlogContext } from "./BlogContext";
import React from "react";
import { useState } from "react";
export const BlogState = (props) => {
const host = "http://localhost:5000";
const blogInitial = [];
const [blog, setBlog] = useState(blogInitial);
let fetchAllNotes = async () => {
//API call
const response = await fetch(`${host}/api/notes/blog/`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const json = await response.json();
setBlog(json);
};
const searchFilter = (searchWord) => {
const searchTerm =
blog.filter((note) =>
note.description.toLowerCase().includes(searchWord)
) || ((note) => note.title.toLowerCase().includes(searchWord));
setBlog(searchTerm);
};
return (
<BlogContext.Provider value={{ blog, fetchAllNotes, fil, searchFilter }}>
{props.children}
</BlogContext.Provider>
);
};
Code from Navbar component
import React, { useContext, useState } from "react";
import { Link, useNavigate, useLocation } from "react-router-dom";
import { ThemeContext } from "../context/notes/ThemeContext";
import { BlogContext } from "../context/notes/BlogContext";
export const Navbar = () => {
const { searchFilter, blog } = useContext(BlogContext);
const [searchTerm, setSearchTerm] = useState(blog);
const onChange = (e) => {
if (e.target.value === "") {
window.location.reload(true);
} else {
const search = e.target.value.toLowerCase();
setSearchTerm(search);
searchFilter(searchTerm);
}
};
return (
<div>
<nav
<form className="d-flex mx-2">
<input
onChange={onChange}
className="form-control me-2"
type="search"
placeholder="Search"
aria-label="Search"
/>
<button className="btn btn-success mx-2" type="submit">Clear</button>
</form>
</nav>
</div>
);
};
Code from Blog component
import React, { useContext, useEffect } from "react";
import { ThemeContext } from "../context/notes/ThemeContext";
import { BlogContext } from "../context/notes/BlogContext";
import BlogItem from "./BlogItem";
import { FlexNavbar } from "./FlexNavbar";
const Blog = () => {
const { theme } = useContext(ThemeContext);
const { blog } = useContext(BlogContext);
return (
<>
<div
className={`container bg-${theme} text-${
theme === "dark" ? "light" : "dark"
}`}
>
<FlexNavbar className="" />
<div className="row">
{blog.map((notes) => {
return <BlogItem key={notes._id} note={notes} />;
})}
</div>
</div>
</>
);
};
export default Blog;
I am building a chat application using firebase and react. The problem facing is that the input data is not getting added to the firebase firestore collection. The collection is named messages.
The output is like the input is getting displayed and disappeared as it is not saved in the database.
The code is given below:
import React, { useRef, useState } from 'react';
import './App.css';
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
import 'firebase/analytics';
import { useAuthState } from 'react-firebase-hooks/auth';
import { useCollectionData } from 'react-firebase-hooks/firestore';
firebase.initializeApp({
//firebase_credentials
})
const auth = firebase.auth();
const firestore = firebase.firestore();
function App() {
const [user] = useAuthState(auth);
return (
<div className="App">
<header>
<h1>SuperChat 💬</h1>
<SignOut />
</header>
<section>
{user ? <ChatRoom /> : <SignIn />}
</section>
</div>
);
}
function SignIn() {
const signInWithGoogle = () => {
const provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider);
}
return (
<>
<button className="sign-in" onClick={signInWithGoogle}>Sign in with Google</button>
<p>Don't wait, messages are gonna disappear in a while</p>
</>
)
}
function SignOut() {
return auth.currentUser && (
<button className="sign-out" onClick={() => auth.signOut()}>Sign Out</button>
)
}
function ChatRoom() {
const dummy = useRef();
const messagesRef = firestore.collection('messages');
const query = messagesRef.orderBy('createdAt').limit(25);
const [messages] = useCollectionData(query, { idField: 'id' });
const [formValue, setFormValue] = useState('');
const sendMessage = async (e) => {
e.preventDefault();
const { uid, photoURL } = auth.currentUser;
await messagesRef.add({
text: formValue,
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
uid,
photoURL
})
setFormValue('');
dummy.current.scrollIntoView({ behavior: 'smooth' });
}
return (<>
<main>
{messages && messages.map(msg => <ChatMessage key={msg.id} message={msg} />)}
<span ref={dummy}></span>
</main>
<form onSubmit={sendMessage}>
<input value={formValue} onChange={(e) => setFormValue(e.target.value)} placeholder="say something nice" />
<button type="submit" disabled={!formValue}>send</button>
</form>
</>)
}
function ChatMessage(props) {
const { text, uid, photoURL } = props.message;
const messageClass = uid === auth.currentUser.uid ? 'sent' : 'received';
return (<>
<div className={`message ${messageClass}`}>
<img src={photoURL || 'https://feedback.seekingalpha.com/s/cache/ff/f2/fff2493e0063ac43f7161be10e0d7fff.png'} />
<p>{text}</p>
</div>
</>)
}
export default App
The issue is that you are not properly setting the object to be added as a document in Firestore in the messagesRef.add() method. If you check this documentation you can see that the add() method should have a structure like:
db.collection("collectionName").add({
field1: "someValue1",
field2: "someValue2",
...
fieldN: "someValueN"
})
So what you should do in your code is the following:
await messagesRef.add({
text: formValue,
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
photoURL : "someURLvalue"
})
NOTE: I took out the uid from the example above because using add() the ID is automatically generated.
I want to add validation in my react form I am using SimpleReactValidator library for validation, but once I setup the code, the error is not displaying. But when i add
{validator.showMessages('fullName', fullName, 'required|alpha')}
before the return statement it's showing me without click on submit button.
Here's my code
import React, { useState } from 'react';
import SimpleReactValidator from 'simple-react-validator';
const UserDetails = ({ setForm, formData, navigation }) => {
const {
fullName
}= formData;
const useForceUpdate = () => useState()[1];
const validator = new SimpleReactValidator();
const forceUpdate = useForceUpdate();
const submitForm = (e) =>{
e.preventDefault()
if (validator.allValid()) {
alert('You submitted the form and stuff!');
} else {
validator.showMessages();
forceUpdate();
}
}
return(
<>
<input
type="text"
name="fullName"
placeholder='Name'
onChange={setForm}
defaultValue={fullName}
/>
{validator.message('fullName', fullName, 'required|alpha')}
</>
);
}
export default UserDetails;
Hope this will help you
import React, { useState } from 'react';
import SimpleReactValidator from 'simple-react-validator';
const UserDetails = () => {
const validator = new SimpleReactValidator();
const [state, setState] = useState({
fullName:""
})
const handleChnage = (e) => {
setState({
fullName:e.target.value
})
}
const submitForm = () => {
if (validator.allValid()) {
alert('You submitted the form and stuff!');
} else {
validator.showMessages();
}
}
return (
<>
<input
type="text"
name="fullName"
placeholder='Name'
onChange={(e) => handleChnage(e)}
defaultValue={state.fullName}
/>
{validator.message('fullName', state.fullName, 'required|alpha')}
<button onClick={() => submitForm()}>submit</button>
</>
);
}
export default UserDetails;
This code example would solve your problem.
class App extends React.Component {
constructor(props){
super(props)
this.validator = new SimpleReactValidator({autoForceUpdate: this});
this.state = {
fullName: ''
};
}
handleFullNameChange(e) {
this.setState({fullName: e.target.value});
}
handleFullNameBlur() {
if(this.validator.allValid()) {
this.validator.hideMessages();
} else {
this.validator.showMessages();
}
}
render(){
return (
<div>
<input type="text" name="fullName" placeholder='Name' onChange={this.handleFullNameChange.bind(this)} onBlur={this.handleFullNameBlur.bind(this)} value={this.state.fullName} />
{this.validator.message('fullName', this.state.fullName, 'required|alpha')}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'))
You can also take a look on Codepen here: https://codepen.io/aptarmy/pen/oNXRezg
I crafted a reactjs crud app with help of a tutorial and it works great now. Now i am trying to merge two form together so that same form should be used for both add and update operation.
This is my allpost.js file
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Post from '../components/Post';
import EditComponent from '../components/editComponent';
class AllPost extends Component {
render() {
return (
<div>
<h1>All Posts</h1>
{this.props.posts.map((post) => (
<div key={post.id}>
{post.editing ? <EditComponent post={post} key={post.id} /> :
<Post key={post.id} post={post} />}
</div>
))}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
posts: state
}
}
export default connect(mapStateToProps)(AllPost);
and this is my postForm.js file:
import React, { Component } from 'react';
import { connect } from 'react-redux'
class PostForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
const title = this.getTitle.value;
const message = this.getMessage.value;
const data = {
id: new Date(),
title,
message,
editing: false
}
console.log(data)
this.props.dispatch({
type: 'ADD_POST',
data,
});
this.getTitle.value = '';
this.getMessage.value = '';
}
render() {
return (
<div>
<h1>Create Post</h1>
<form onSubmit={this.handleSubmit}>
<input required type="text" ref={(input)=>this.getTitle = input}
placeholder="Enter Post Title"/>
<br /><br />
<textarea required rows="5" ref={(input)=>this.getMessage = input} cols="28"
placeholder="Enter Post" />
<br /><br />
<button>Post</button>
</form>
</div>
);
}
}
export default connect()(PostForm);
and this is my editComponent.js file
import React, { Component } from 'react';
import { connect } from 'react-redux';
class EditComponent extends Component {
handleEdit = (e) => {
e.preventDefault();
const newTitle = this.getTitle.value;
const newMessage = this.getMessage.value;
const data = {
newTitle,
newMessage
}
this.props.dispatch({ type: 'UPDATE', id: this.props.post.id, data: data })
}
render() {
return (
<div>
<form onSubmit={this.handleEdit}>
<input required type="text" ref={(input) => this.getTitle = input}
defaultValue={this.props.post.title} placeholder="Enter Post Title" /><br /><br />
<textarea required rows="5" ref={(input) => this.getMessage = input}
defaultValue={this.props.post.message} cols="28" placeholder="Enter Post" /><br /><br />
<button>Update</button>
</form>
</div>
);
}
}
export default connect()(EditComponent);
and this is my post.js file:
import React, { Component } from 'react';
import { connect } from 'react-redux'
class Post extends Component {
render() {
return (
<div>
<h2>{this.props.post.title}</h2>
<p>{this.props.post.message}</p>
<button onClick={() => this.props.dispatch({type: 'EDIT_POST', id: this.props.post.id})}>EDIT
</button>
<button onClick={ () => this.props.dispatch({type: 'DELETE_POST', id: this.props.post.id}) }>DELETE
</button>
</div>
);
}
}
export default connect()(Post);
and this is my postReducer.js file:
const postReducer = (state = [], action) => {
switch(action.type) {
case 'ADD_POST':
return state.concat([action.data]);
case 'DELETE_POST':
return state.filter((post)=>post.id !== action.id);
case 'EDIT_POST':
return state.map((post)=>post.id === action.id ? {...post,editing:!post.editing}:post)
case 'UPDATE':
return state.map((post)=>{
if(post.id === action.id) {
return {
...post,
title:action.data.newTitle,
message:action.data.newMessage,
editing: !post.editing
}
} else return post;
})
default:
return state;
}
}
export default postReducer;
Can anyone please help me to achieve this? I tried a lot to use same form form for both add and update and i failed to achieve this.
I think it's better you create separate component for rendering form data(FormComponent) and separate components for edit(EditComponent) and add(AddComponent).
This way there will not be clutter in one component and no if/else conditions for different modes like edit or add, or in future copy mode.
This approach will add flexibility and enhances compositional pattern of react.
1) AddComponent
import React, { Component } from 'react';
import { connect } from 'react-redux'
class AddComponent extends Component {
handleSubmit = (title, message) => {
const data = {
id: new Date(),
title,
message,
editing: false
}
this.props.dispatch({
type: 'ADD_POST',
data,
});
}
render() {
return (
<div>
<h1>Create Post</h1>
<FormComponent
buttonLabel='Post'
handleSubmit={this.handleSubmit}
/>
</div>
);
}
}
export default connect()(AddComponent);
2) EditComponent
import React, { Component } from 'react';
import { connect } from 'react-redux';
class EditComponent extends Component {
handleSubmit = (newTitle, newMessage) => {
const data = {
newTitle,
newMessage
}
this.props.dispatch({ type: 'UPDATE', id: this.props.post.id, data: data })
}
render() {
return (
<div>
<FormComponent
buttonLabel='Update'
handleSubmit={this.handleSubmit}
/>
</div>
);
}
}
export default connect()(EditComponent);
3) FormComponent
import React, { Component } from 'react';
class FormComponent extends Component {
handleSubmit = (e) => {
e.preventDefault();
const title = this.getTitle.value;
const message = this.getMessage.value;
this.props.handleSubmit(title, message);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input required type="text" ref={(input) => this.getTitle = input}
defaultValue={this.props.post.title} placeholder="Enter Post Title" /><br /><br />
<textarea required rows="5" ref={(input) => this.getMessage = input}
defaultValue={this.props.post.message} cols="28" placeholder="Enter Post" /><br /><br />
<button>{this.props.buttonLabel}</button>
</form>
);
}
}
export default FormComponent;
Hope that helps!!!
You can create your own Form component with a prop of editMode to control whether it's Create or Update.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
class Form extends Component {
handleSubmit = e => {
e.preventDefault();
const {editMode, post} = this.props;
const title = this.titleRef.value;
const body = this.bodyRef.value;
if (editMode){
const data = {
title,
body
}
this.props.dispatch({type: 'UPDATE', id: post.id, data})
}
else {
const data = {
id: new Date(),
title,
message,
editing: false
}
this.props.dispatch({type: 'ADD_POST', data});
}
}
render() {
const {editMode, post} = this.props;
const pageTitle = editMode ? 'Edit Post' : 'Create Post';
const buttonTitle = editMode ? 'Update' : 'Post';
return (
<div>
<h1>{pageTitle}</h1>
<form onSubmit={this.handleSubmit}>
<input
required
type="text"
ref={input => this.titleRef = input}
placeholder="Enter Post Title"
defaultValue={post.title}
/>
<textarea
required
rows="5"
ref={input => this.bodyRef = input}
cols="28"
placeholder="Enter Post"
defaultValue={post.body}
/>
<button>{buttonTitle}</button>
</form>
</div>
);
}
}
Form.propTypes = {
editMode: PropTypes.bool,
post: PropTypes.object
}
Form.defaultProps = {
editMode: false, // false: Create mode, true: Edit mode
post: {
title: "",
body: ""
} // Pass defined Post object in create mode in order not to get undefined objects in 'defaultValue's of inputs.
}
export default Form;
It would be on create mode by default but if you wanna update the post you should pass editMode={true} to your form component.
I am looking to fire a submit handler for a LoginForm. However, for some reason, instead of my mock function being called, the actual handler for the component gets fired (calling an external api). How can I ensure that my mock handler gets called instead?
The three components of interest are below (The presentational, container and the test suite)
LoginForm.js
import { Formik, Form, Field } from 'formik';
import { CustomInput } from '..';
const LoginForm = ({ initialValues, handleSubmit, validate }) => {
return (
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={handleSubmit}
>
{({ isSubmitting, handleSubmit }) => {
return (
<Form onSubmit={handleSubmit}>
<div className="d-flex flex-column justify-content-center align-items-center">
<Field
data-testid="usernameOrEmail"
type="text"
name="identifier"
placeholder="Username/Email"
component={CustomInput}
inputClass="mb-4 mt-2 text-monospace"
/>
<Field
data-testid="login-password"
type="password"
name="password"
placeholder="Password"
component={CustomInput}
inputClass="mb-4 mt-4 text-monospace"
/>
<button
data-testid="login-button"
className="btn btn-primary btn-lg mt-3 text-monospace"
type="submit"
disabled={isSubmitting}
style={{ textTransform: 'uppercase', minWidth: '12rem' }}
>
Submit
</button>
</div>
</Form>
)}}
</Formik>
);
};
export default LoginForm;
LoginPage.js
import React, { useContext } from 'react';
import { loginUser } from '../../services';
import { userContext } from '../../contexts';
import { loginValidator } from '../../helpers';
import { setAuthorizationToken, renderAlert } from '../../utils';
import LoginForm from './login-form';
const INITIAL_VALUES = { identifier: '', password: '' };
const LoginPage = props => {
const { handleUserData, handleAuthStatus } = useContext(userContext);
const handleSubmit = async (values, { setSubmitting }) => {
try {
const result = await loginUser(values);
handleAuthStatus(true);
handleUserData(result.data);
setAuthorizationToken(result.data.token);
props.history.push('/habits');
renderAlert('success', 'Login Successful');
} catch (err) {
renderAlert('error', err.message);
}
setSubmitting(false);
};
return (
<LoginForm
initialValues={INITIAL_VALUES}
validate={values => loginValidator(values)}
handleSubmit={handleSubmit}
/>
);
};
export default LoginPage;
LoginPage.spec.js
import React from 'react';
import { cleanup, getByTestId, fireEvent, wait } from 'react-testing-library';
import { renderWithRouter } from '../../../helpers';
import LoginPage from '../login-page';
afterEach(cleanup);
const handleSubmit = jest.fn();
test('<LoginPage /> renders with blank fields', () => {
const { container } = renderWithRouter(<LoginPage />);
const usernameOrEmailNode = getByTestId(container, 'usernameOrEmail');
const passwordNode = getByTestId(container, 'login-password');
const submitButtonNode = getByTestId(container, 'login-button');
expect(usernameOrEmailNode.tagName).toBe('INPUT');
expect(passwordNode.tagName).toBe('INPUT');
expect(submitButtonNode.tagName).toBe('BUTTON');
expect(usernameOrEmailNode.getAttribute('value')).toBe('');
expect(passwordNode.getAttribute('value')).toBe('');
});
test('Clicking the submit button after entering values', async () => {
const { container } = renderWithRouter(<LoginPage handleSubmit={handleSubmit} />);
const usernameOrEmailNode = getByTestId(container, 'usernameOrEmail');
const passwordNode = getByTestId(container, 'login-password');
const submitButtonNode = getByTestId(container, 'login-button');
fireEvent.change(usernameOrEmailNode, { target: { value: fakeUser.username }});
fireEvent.change(passwordNode, { target: { value: fakeUser.password }});
fireEvent.click(submitButtonNode);
await wait(() => {
expect(handleSubmit).toHaveBeenCalledTimes(1);
});
expect(usernameOrEmailNode.tagName).toBe('INPUT');
expect(passwordNode.tagName).toBe('INPUT');
expect(submitButtonNode.tagName).toBe('BUTTON');
expect(usernameOrEmailNode.getAttribute('value')).toBe('');
expect(passwordNode.getAttribute('value')).toBe('');
});```
To answer your question, you will need to first make the handleSubmit constant accessible outside LoginPage.js so that it may be mocked and then tested. For example,
LoginPage.js
export const handleSubmit = async (values, { setSubmitting }) => {
... code to handle submission
})
And in your tests - LoginPage.spec.js
jest.unmock('./login-page');
import LoginPage, otherFunctions from '../login-page'
otherFunctions.handleSubmit = jest.fn();
...
test('Clicking the submit button after entering values', () => {
...
fireEvent.click(submitButtonNode);
expect(handleSubmit).toHaveBeenCalledTimes(1);
})
I hope the above fixes your problem.
But, going by the philosophy of unit testing, the above components
must not be tested the way you are doing it. Instead your test setup
should be like this -
Add a new test file called LoginForm.spec.js that tests your LoginForm component. You would test the following in this -
Check if all input fields have been rendered.
Check if the correct handler is called on submit and with the correct parameters.
The existing test file called LoginPage.spec.js would then only test if the particular form was rendered and then you could also test
what the handleSubmit method does individually.
I believe the above would make your tests more clearer and readable
too, because of the separation of concerns and would also allow you to
test more edge cases.