I have a form with input fields 'title' and 'body', they are being added to MongoDB after submitting the form, but the new post item shows up only after I refresh the page (that of course is not how it should work), but I want to know what I am doing wrong and how to fix the handleSumbit function?
Here is my code:
import { useState, useEffect } from "react";
import axios from "axios";
import "./App.css";
function App() {
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
const [posts, setPosts] = useState([]);
const url = "http://localhost:8005/";
useEffect(() => {
const fetchPosts = async () => {
const response = await axios(`${url}posts`);
setPosts(response.data);
};
fetchPosts();
}, []);
const handleTitleChange = (event) => {
setTitle(event.target.value);
};
const handleBodyChange = (event) => {
setBody(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
axios.post(`${url}posts`, { title: title, body: body })
.then((res) => {
console.log(res.data);
});
alert("Post added!");
setTitle("");
setBody("");
};
console.log(posts);
return (
<div className="App">
{posts.map((post) => (
<div className="post" key={post.id}>
<h4>{post.title}</h4>
<p>{post.body}</p>
</div>
))}
<form onSubmit={handleSubmit}>
<label>
Mew post:
<input
type="text"
name="title"
placeholder="Add title"
onChange={handleTitleChange}
/>
<input
type="text"
name="body"
placeholder="Add body"
onChange={handleBodyChange}
/>
</label>
<button type="submit">Add</button>
</form>
</div>
);
}
export default App;
The problem is that you didn't update the posts state after you sent the axios post request. Edit the below code block:
const handleSubmit = (event) => {
event.preventDefault();
axios.post(`${url}posts`, { title: title, body: body })
.then((res) => {
console.log(res.data);
// save the new post to posts
setPosts([...posts, res.data])
});
alert("Post added!");
setTitle("");
setBody("");
};
Related
I have 2 sibling components namely <AddTask/> and <TaskList/> which are children of <Home/> component. Currently, when I add a new task in my ToDo App, it will be added but I need to refresh the page in order for it to display the new task. How can I refresh the <TaskList/> component immediately after I click the Add button in <AddTask/> component?
Here is my <AddTask/> Component
const AddTask = () => {
const [task, setTask] = useState("");
const [isPending, setIsPending] = useState(false);
const handleClick = (e)=> {
e.preventDefault();
setIsPending(true);
const todo = {task};
fetch('http://localhost:8000/tasks', {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify(todo)
})
.then(()=>{
setIsPending(false);
})
};
return (
<form className="new-task" onSubmit={handleClick}>
<input className="input"
type="text"
required
value={task}
onChange= { (e)=> setTask(e.target.value) }
/>
<button className="add-task">Add</button>
</form>
);
}
export default AddTask;
This is the <Home/> Component
import TaskList from "./TaskList";
import useFetch from "./useFetch";
const Home = () => {
const { data: task, isPending, error} = useFetch('http://localhost:8000/tasks');
return (
<div className="home">
<AddTask />
{ error && <div>Failed to fetch data.</div> }
{ isPending && <div>Loading...</div> }
{ task && <TaskList task={task} /> }
</div>
);
}
export default Home;
In Home component, you need a tasks state so you can update that state in AddTask component
Home
import TaskList from "./TaskList";
import useFetch from "./useFetch";
import { useState, useEffect } from 'react'
const Home = () => {
const [tasks, setTasks] = useState(null);
const { data: task, isPending, error} = useFetch('http://localhost:8000/tasks');
useEffect(() => {
if (task) setTasks(task)
}, [task])
return (
<div className="home">
<AddTask setTasks={setTasks} />
{ error && <div>Failed to fetch data.</div> }
{ isPending && <div>Loading...</div> }
{ tasks && <TaskList task={tasks} /> }
</div>
);
}
export default Home;
AddTask
const AddTask = ({ setTasks }) => {
const [task, setTask] = useState("");
const [isPending, setIsPending] = useState(false);
const handleClick = (e)=> {
e.preventDefault();
setIsPending(true);
const todo = {task};
fetch('http://localhost:8000/tasks', {
method: 'POST',
headers: { "Content-Type": "application/json" },
body: JSON.stringify(todo)
})
.then(()=>{
setIsPending(false);
setTasks(prev => ([...prev, task]))
})
};
return (
<form className="new-task" onSubmit={handleClick}>
<input className="input"
type="text"
required
value={task}
onChange= { (e)=> setTask(e.target.value) }
/>
<button className="add-task">Add</button>
</form>
);
}
export default AddTask;
I have one component with a form that creates posts and then I have another component that displays these posts in a feed. How do I get the feed to re-render when a new post is created? What's the best way to do that?
Re-render the parent component without changing props?
Update props?
Some other way?
Create.js
import React, { useState } from "react";
export default function Create () {
const [form, setForm] = useState({
post: "",
});
// These methods will update the state properties.
function updateForm(value) {
return setForm((prev) => {
return { ...prev, ...value };
});
}
async function onSubmit(e) {
e.preventDefault();
// When a post request is sent to the create url, we'll add a new post to the database.
const newPost = { ...form };
await fetch("http://localhost:3000/post/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newPost),
})
.catch(error => {
window.alert(error);
return;
});
setForm({ post: "" });
}
return (
<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label htmlFor="post">Post</label>
<input
type="text"
className="form-control"
id="post"
value={form.post}
onChange={(e) => updateForm( { post: e.target.value })}
/>
</div>
<div className="form-group">
<input
type="submit"
value="Create post"
className="btn btn-primary"
/>
</div>
</form>
</div>
);
}
Feed.js
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
const Post = (props) => (
<div>
{props.post.post}
<Link className="btn btn-link" to={`/edit/${props.post._id}`}>Edit</Link>
<button className="btn btn-link" onClick={() => { props.deletePost(props.post._id);
}}
>
Delete
</button>
</div>
);
export default function PostList() {
const [posts, setPosts] = useState([]);
// This method fetches the posts from the database.
useEffect(() => {
async function getPosts() {
const response = await fetch(`http://localhost:3000/post/`);
if (!response.ok) {
const message = `An error occured: ${response.statusText}`;
window.alert(message);
return;
}
const posts = await response.json();
setPosts(posts);
}
getPosts();
return;
}, [posts.length]);
// This method will delete a post
async function deletePost(id) {
await fetch(`http://localhost:3000/${id}`, {
method: "DELETE"
});
const newPosts = posts.filter((el) => el._id !== id);
setPosts(newPosts);
}
// This method will map out the posts on the table
function postList() {
return posts.map((post) => {
return (
<Post
post={post}
deletePost={() => deletePost(post._id)}
key={post._id}
/>
);
});
}
// This following section will display the the posts.
return (
<div>
<h3>Post List</h3>
<div>{postList()}</div>
</div>
);
}
Home.js
import React from "react";
import Feed from "./feed";
import Create from "./create";
const Home = () => {
return (
<div className="app">
<div>
<Create />
<Feed />
</div>
</div>
);
};
export default Home;
There are numerous ways to do so. The one you should start with is the most straightforward: pass your current posts and setPosts into both component from a parent component:
const Parent = () => {
const [posts, setPosts] = useState([]);
return <>
<Create posts={posts} setPosts={setPosts} />
<PostList posts={posts} setPosts={setPosts} />
</>
}
There are better ways to do so, but they are more involved. I suggest you to start with this one to understand how React handles data better.
Here I want to register user with image so I want to pass both image and name in my formdata.
I am able to upload the file using some guideline (I am not good with react) but I am not able to pass the input name with my formdata. which procedure to follow?
import axios from "axios";
import React, { useState, useEffect } from 'react'
import { LinkContainer } from 'react-router-bootstrap'
import { Table, Button, Row, Col } from 'react-bootstrap'
import { useDispatch, useSelector } from 'react-redux'
const UPLOAD_ENDPOINT = "http://127.0.0.1:8000/api/orders/vendor/register/";
function VendorRegistration() {
const [file, setFile] = useState(null);
const [name, setName] = useState("");
const { userInfo } = useSelector((state) => state.userLogin);
const handleSubmit = async (event) => {
event.preventDefault();
const formData = new FormData();
formData.append("avatar", file);
formData.append("name", name);
const resp = await axios.post(UPLOAD_ENDPOINT, formData, {
headers: {
"content-type": "multipart/form-data",
Authorization: `Bearer ${userInfo.token}`,
},
});
console.log(resp.status)
};
return (
<form onSubmit={handleSubmit}>
<h1>React File Upload</h1>
<input type="file" onChange={(e) => setFile(e.target.files[0])} />
<input type="text" onChange={(e) => setName(e.target.value)} value={name} />
<button type="submit" disabled={!(file && name)}>
Upload File
</button>
{resp.status == 200(
<h1>ok</h1>
)
}
</form>
);
}
export default VendorRegistration;
You'll just want to bind the other input to state as per usual, and then add that value to the form data.
I added rudimentary validation that prevents clicking the submit button unless both fields are filled in, too.
EDIT: I also added status responses, as per comments.
import React from "react";
import axios from "axios";
const UPLOAD_ENDPOINT = "http://127.0.0.1:8000/api/orders/vendor/register/";
function VendorRegistration() {
const [file, setFile] = useState(null);
const [name, setName] = useState("");
const [status, setStatus] = useState("");
const { userInfo } = useSelector((state) => state.userLogin);
const handleSubmit = async (event) => {
setStatus(""); // Reset status
event.preventDefault();
const formData = new FormData();
formData.append("avatar", file);
formData.append("name", name);
const resp = await axios.post(UPLOAD_ENDPOINT, formData, {
headers: {
"content-type": "multipart/form-data",
Authorization: `Bearer ${userInfo.token}`,
},
});
setStatus(resp.status === 200 ? "Thank you!" : "Error.");
};
return (
<form onSubmit={handleSubmit}>
<h1>React File Upload</h1>
<input type="file" onChange={(e) => setFile(e.target.files[0])} />
<input type="text" onChange={(e) => setName(e.target.value)} value={name} />
<button type="submit" disabled={!(file && name)}>
Upload File
</button>
{status ? <h1>{status}</h1> : null}
</form>
);
}
export default VendorRegistration;
I am trying to submit a POST form with a simple html form and I also use redux with a userAction which allows to store the user and the token returned by the express js API, but the submission does not work, when I submit the form, nothing happens I can't even get into the fetch function of the action.
import '../styles/ConnexionModal.css';
import { userLogin } from '../redux/userActions';
import { useState } from 'react';
// import { useSelector } from 'react-redux';
const ConnexionModal = ({ showModal, hideModal }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
// const message = useSelector(state => state.userReducer.message);
// console.log(message);
const handleChangeEmail = (event) => {
setEmail(event.target.value);
}
const handleChangePassword = (event) => {
setPassword(event.target.value);
}
const handleSubmit = (e) => {
e.preventDefault();
userLogin(email, password);
}
return (
showModal && (
<div className="modalBg">
<div className="modalContainer">
<div className="modalHeader">
<h1 className="modalTitle">Connexion</h1>
</div>
<div className="modalBody">
<form method="POST" onSubmit={handleSubmit}>
<div className="formGroup">
<label htmlFor="email" className="info">Email</label>
<input className="field" name="email" id="email" type="email" value={email} onChange={handleChangeEmail} autoFocus required />
</div>
<div className="formGroup">
<label htmlFor="password" className="info">Mot de passe</label>
<input className="field" name="password" id="password" type="password" value={password} onChange={handleChangePassword} required />
</div>
<div className="formGroup">
<label htmlFor="connexion" className="submitButton">Se connecter</label>
<input className="field submit" id="connexion" name="submit" type="submit" />
</div>
</form>
</div>
<div className="close">
<button onClick={() => hideModal()} className="closeButton">Fermer</button>
</div>
</div>
</div>
)
)
}
export default ConnexionModal;
export const LOGIN = "LOGIN";
export const ERROR = "ERROR";
export const userLogin = (email, password) => {
return async dispatch => {
console.log('test');
fetch('http://192.168.1.36:4000/api/users/login', {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
password: password
})
})
.then((res) => res.json())
.then(async (res) => {
if(!res.error) {
localStorage.setItem('auth', res.token);
dispatch({ type: LOGIN, user: res.user, token: res.token });
} else {
dispatch({ type: ERROR, message: res.error });
}
})
}
}
The console.log ('test') does not display anything which means I am not even accessing the userLogin function in my component.
I assume you are also using redux-thunk.
You should always pass the action inside your container to a redux dispatch function. E.g.
import '../styles/ConnexionModal.css';
import { userLogin } from '../redux/userActions';
import { useState } from 'react';
import { useDispatch } from 'react-redux';
const ConnexionModal = ({ showModal, hideModal }) => {
const dispatch = useDispatch();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleChangeEmail = (event) => {
setEmail(event.target.value);
}
const handleChangePassword = (event) => {
setPassword(event.target.value);
}
const handleSubmit = (e) => {
e.preventDefault();
dispatch(userLogin(email, password));
}
...
This is my Signupcomponent
const SignupComponent = () => {
const [values, setValues] = useState({
username: 'silvio1',
name: 'Silvioo',
email: 'berlusconi#gmail.com',
password: '123ooo007',
});
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
const { username, name, email, password } = values;
const user = {username, name, email, password};
await axios.post('${API)/signup', user);
};
const handleChange = name => e => {
setValues({ ...values, [name]: e.target.value });
};
const showLoading = () => (loading ? <div className="alert alert-info">Loading...</div> : '');
const signupForm = () => {
return (
<form onSubmit={handleSubmit}>
<div className="form-group">
<input
value={values.username}
onChange={handleChange('username')}
type="text"
className="form-control"
placeholder="Type your username"
/>
</div>
<div className="form-group">
<input
value={values.name}
onChange={handleChange('name')}
type="text"
className="form-control"
placeholder="Type your name"
/>
</div>
<div className="form-group">
<input
value={values.email}
onChange={handleChange('email')}
type="email"
className="form-control"
placeholder="Type your email"
/>
</div>
<div className="form-group">
<input
value={values.password}
onChange={handleChange('password')}
type="password"
className="form-control"
placeholder="Type your password"
/>
</div>
<div>
<button className="btn btn-primary">Signup</button>
</div>
</form>
);
};
return <React.Fragment>
{showLoading()}
{signupForm()}
</React.Fragment>;
};
export default SignupComponent;
EDIT
I changed my code(zhulien's accepted answer).
Signup page appears,I try to sign up user.
I got error
Unhandled Runtime Error
Error: Request failed with status code 404
Call Stack
createError
node_modules/axios/lib/core/createError.js (16:0)
settle
node_modules/axios/lib/core/settle.js (17:0)
XMLHttpRequest.handleLoad
node_modules/axios/lib/adapters/xhr.js (62:0)
Frontend folder
components
config.js
next.config.js
node_modules
package.json
package-lock.json
pages
My pages folder
_document.js
index.js
signin.js
signup.js
signup.js imports the code above
import Link from 'next/link';
import Layout from '../components/Layout';
import SignupComponent from '../components/frontauth/SignupComponent';
const Signup = () => {
return (
<Layout>
<h2>Signup page</h2>
<SignupComponent />
</Layout>
);
};
My next.config.js
{
APP_NAME: 'BLOG FRONTEND',
APP_DEVELOPMENT: 'http://localhost:3000',
PRODUCTION: false
}
And config.js
const { publicRuntimeConfig } = getConfig();
console.log(publicRuntimeConfig);
export const API = publicRuntimeConfig.PRODUCTION
? 'https://cryptoblog.com'
: 'http://localhost:3000';
export const APP_NAME = publicRuntimeConfig.APP_NAME;
I am new to React and React Hooks. How to solve this problem?
First of all, you're trying to access {username}(which doesn't exist) instead of the state property which is values.username. Furthermore, don't use hooks in event handlers, they should be used in the top level body of the component or in custom hooks only. Checkout this: React hooks rules.
So:
In your form you have to use the state(values) properties.
Extract useEffect hook in the main body flow of the component or BETTER remove it altogether as you're not using it properly currently. You're better of with just the simple event handler for form submit which should post the data somewhere without setting any state.
Your code could look something like:
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { API } from '../../config';
const SignupComponent = () => {
const [values, setValues] = useState({
username: 'silvio1',
name: 'Silvioo',
email: 'berlusconi#gmail.com',
password: '123ooo007',
});
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
const { username, name, email, password } = values;
const user = {username, name, email, password};
await axios.post('${API)/signup', user);
};
const handleChange = name => e => {
setValues({ ...values, [name]: e.target.value });
};
const showLoading = () => (loading ? <div className="alert alert-info">Loading...</div> : '');
const signupForm = () => {
return (
<form onSubmit={handleSubmit}>
<div className="form-group">
<input
value={values.username}
onChange={handleChange('username')}
type="text"
className="form-control"
placeholder="Type your username"
/>
</div>
this is how it should be:
useEffect(() => {
postUser();
}, []);
not inside the function the way you have done it:
const handleSubmit = e => {
e.preventDefault();
setValues({...values});
const { username, name, email, password } = values;
const user = {username, name, email, password};
async function postUser () {
const result = await axios.post('${API)/signup', user);
};
useEffect(() => {
postUser();
}, []);
};
UseEffects aren't meant to be placed inside your functions.Just place them inside your functional component,with some value(or no value) inside your dependency array of the useEffect.These values present inside the array will trigger the useEffect whenever they get changed.