React Hooks - How to pass props from child to parent component - hook

In the bellow example, how can I pass imageAsUrl.imgUrl from the Child component (ImageUpload) to the parent component (UpdateCard).
CHILD Component
import React, { useState, useEffect } from 'react';
import { storage } from '../firebase';
const ImageUpload = () => {
const allInputs = { imgUrl: '' };
const [imageAsUrl, setImageAsUrl] = useState(allInputs);
const [image, setImage] = useState(null);
const handleChange = (e) => {
if (e.target.files[0]) {
setImage(e.target.files[0]);
}
};
useEffect(() => {
if (image) {
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
'state_changed',
(snapshot) => {},
(error) => {
console.log(error);
},
() => {
storage
.ref('images')
.child(image.name)
.getDownloadURL()
.then((fireBaseUrl) => {
setImageAsUrl((prevObject) => ({
...prevObject,
imgUrl: fireBaseUrl,
}));
});
}
);
}
}, [image]);
return (
<>
<label className='custom-file-upload'>
<input type='file' onChange={handleChange} />
</label>
<img src={imageAsUrl.imgUrl} alt='sample' />
</>
);
};
export default ImageUpload;
PARENT Component
import React, { useState } from 'react';
import firebase from '../firebase';
import ImageUpload from './ImageUpload';
const UpdateCard = ({ card }) => {
const [originalText, setOriginalText] = useState(card.originalText);
const [translatedText, setTranslatedText] = useState(card.translatedText);
const onUpdate = () => {
const db = firebase.firestore();
db.collection('FlashCards')
.doc(card.id)
.set({ ...card, originalText, translatedText });
timeOutScroll();
};
return (
<>
<div>
{card.imageURL ? (
<img src={card.imageURL} alt='' className='img' />
) : (
<textarea
className='upload-textarea'
value={originalText}
onChange={(e) => {
setOriginalText(e.target.value);
}}
/>
)}
<ImageUpload />
</div>
<textarea
value={translatedText}
onChange={(e) => {
setTranslatedText(e.target.value);
}}
/>
<button onClick={onUpdate}>Update</button>
</>
);
};
export default UpdateCard;

Inside parent,You can define a callback function as prop ref to be called inside the child.
const ImageUpload = ({getURLtoParent}) =>{ <--------------------
const [imageAsUrl, setImageAsUrl] = useState(allInputs);
useEffect(() => {
uploadTask.on(
..............
...
);
if(imageAsUrl.imgUrl !== '')
getURLtoParent(imageAsUrl.imgUrl) <-----------------------
},[image])
}
const UpdateCart = () => {
const[imgURL,setimgURL] = useState(null)
return (
......
<ImageUpload getURLtoParent={ (url) => setimgURL(url) } /> <----------------
.........
)
}

Related

[Unhandled promise rejection: FirebaseError: Function addDoc() called with invalid data. Unsupported field value: a custom Class object

I want to post data in firebase firestore database but I don't understand why that does'nt work
I Can post prewrite title in database but not with custom title from an input
traceback :
[Unhandled promise rejection: FirebaseError: Function addDoc() called with invalid data. Unsupported field value: a custom Class object (found in field date in document events/T58NdobxrvVchtaQjYx0)]
My addEvent function to add eventDate and a title to an firebase events collection
firebase.js
export async function addEvent(date, title) {
const eventsRef = collection(db, EVENTS_COLLECTION);
const newEventRef = await addDoc(eventsRef, {
date,
title,
})
.then(() => {
// Data saved successfully!
console.log("data submitted");
})
.catch((error) => {
// The write failed...
console.log(error);
});
const newEvent = await getDoc(newEventRef);
return newEvent;
}
My calendar to add data on firebase.
I have a button that open picker date time modal and next modal with an input to add data with submit and cancel button.
CalendarScreen.js
import React, { useState } from "react";
import { View, Button, StyleSheet, Modal, TextInput } from "react-native";
import { addEvent } from "../../firebase/firebase";
import CalendarPicker from "react-native-calendar-picker";
import DateTimePickerModal from "react-native-modal-datetime-picker";
export default function CalendarScreen({ selectedDate, onDateChange }) {
const [isDatePickerVisible, setIsDatePickerVisible] = useState(false);
const [isTitleModalVisible, setIsTitleModalVisible] = useState(false);
const [eventTitle, setEventTitle] = useState("");
const showDatePicker = () => {
setIsDatePickerVisible(true);
};
const hideDatePicker = () => {
setIsDatePickerVisible(false);
};
const showTitleModal = () => {
setIsTitleModalVisible(true);
};
const hideTitleModal = () => {
setIsTitleModalVisible(false);
};
const handleAddEvent = (eventDate, eventTitle) => {
hideDatePicker(eventDate, eventTitle);
showTitleModal();
};
const handleSubmitTitle = (eventDate, eventTitle) => {
addEvent(eventDate, eventTitle);
hideTitleModal();
setEventTitle("");
};
return (
<View>
<CalendarPicker
style={styles.calendar}
selectedDate={selectedDate}
onDateChange={onDateChange}
/>
<Button title="Add Event" onPress={showDatePicker} />
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode="datetime"
onConfirm={handleAddEvent}
onCancel={hideDatePicker}
/>
<Modal style={styles.modal} visible={isTitleModalVisible}>
<View>
<TextInput
placeholder="Event Title"
value={eventTitle}
onChangeText={setEventTitle}
style={styles.input}
/>
<Button title="Submit" onPress={handleSubmitTitle} />
<Button title="Cancel" onPress={hideTitleModal} />
</View>
</Modal>
</View>
);
}
EDIT When string is typed
export async function getEvents() {
const eventsRef = collection(db, EVENTS_COLLECTION);
const docSnap = await getDocs(eventsRef);
const events = [];
docSnap.forEach((doc) => {
events.push({ id: doc.id, ...doc.data() });
});
return events;
}
CalendarScreen.js
export default function CalendarScreen({ selectedDate, onDateChange }) {
const [isDatePickerVisible, setIsDatePickerVisible] = useState(false);
const showDatePicker = () => {
setIsDatePickerVisible(true);
};
const hideDatePicker = () => {
setIsDatePickerVisible(false);
};
const handleAddEvent = (eventDate) => {
addEvent(eventDate);
hideDatePicker();
};
return (
<View>
<CalendarPicker selectedDate={selectedDate} onDateChange={onDateChange} />
<Button title="Add Event" onPress={showDatePicker} />
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode="datetime"
onConfirm={handleAddEvent}
onCancel={hideDatePicker}
/>
</View>
);
temporary SOLUTION : See you database even with the error
CalendarScreen.js
import React, { useState } from "react";
import { View, Button, StyleSheet, Modal, TextInput } from "react-native";
import { addEvent } from "../../firebase/firebase";
import CalendarPicker from "react-native-calendar-picker";
import DateTimePickerModal from "react-native-modal-datetime-picker";
import moment from "moment-timezone";
moment.tz.setDefault("Europe/Paris");
export default function CalendarScreen({ selectedDate, onDateChange }) {
const [isDatePickerVisible, setIsDatePickerVisible] = useState(false);
const [isModalVisible, setIsModalVisible] = useState(false);
const [eventTitle, setEventTitle] = useState("");
const showDatePicker = () => {
setIsDatePickerVisible(true);
};
const hideDatePicker = () => {
setIsDatePickerVisible(false);
};
const showModal = () => {
setIsModalVisible(true);
};
const hideModal = () => {
setIsModalVisible(false);
};
const handleAddEvent = (eventDate) => {
addEvent(eventDate, eventTitle);
showModal();
};
const handleSubmit = (eventDate) => {
addEvent(eventDate, eventTitle);
hideModal();
hideDatePicker();
};
return (
<View>
<CalendarPicker selectedDate={selectedDate} onDateChange={onDateChange} />
<Button title="Add Event" onPress={showDatePicker} />
<DateTimePickerModal
isVisible={isDatePickerVisible}
mode="datetime"
onConfirm={handleAddEvent}
onCancel={hideDatePicker}
/>
<Modal animationType="slide" transparent={false} visible={isModalVisible}>
<View>
<TextInput
placeholder="Event Title"
value={eventTitle}
onChangeText={setEventTitle}
/>
<Button
title="Submit"
onPress={(eventDate) => handleSubmit(eventDate)}
/>
<Button title="Cancel" onPress={hideModal} />
</View>
</Modal>
</View>
);
export async function addEvent(date, title) {
const eventsRef = collection(db, EVENTS_COLLECTION);
const newEventRef = await addDoc(eventsRef, {
date: date,
title: title,
})
.then(() => {
// Data saved successfully!
console.log("data submitted");
})
.catch((error) => {
// The write failed...
console.log(error);
});
const newEvent = await getDoc(newEventRef);
return newEvent;
}
SOLUTION updated from scra:
export async function addEvent(event) {
try {
const eventsRef = collection(db, EVENTS_COLLECTION);
const docRef = await addDoc(eventsRef, event);
console.log("Event is added, id :", docRef.id);
} catch (error) {
console.error(error);
}
}
const handleDatePicked = (date) => {
const formattedDate = date.toLocaleDateString();
const formattedTime = date.toLocaleTimeString();
setDate(`${formattedDate} ${formattedTime}`);
setDateTimePickerVisible(false);
};
const handleSubmit = async () => {
try {
await addEvent({ ...event, date });
} catch (error) {
console.error(error);
}
};
return (
<View>
<TextInput
onChangeText={(text) => setEvent({ ...event, name: text })}
placeholder="Event name"
/>
<Text>{date}</Text>
<Button
Color="red"
title="Pick date and time"
onPress={showDateTimePicker}
/>
<DateTimePicker
mode="datetime"
isVisible={isDateTimePickerVisible}
onConfirm={handleDatePicked}
onCancel={() => setDateTimePickerVisible(false)}
/>
<Button title="Submit" onPress={handleSubmit} />
</View>
);
The error indicates that you try to write a document with an unsupported field value for the field date.
You will find here the list of the data types supported by Firestore. You need to transform the Object returned by your date picker to an Object that has a type supported by Firestore.

Firefox: React app not populating root element with weird error

I have a react application built for production and being served by a node/express server.
I have started the server locally and also deployed it to heroku and render.com
On all these systems, Chrome and Edge have no problems and no errors when accessing the site. But: Firefox won't populate the root element and shows a weird error. Only after refreshing, Firefox shows the site perfectly.
This is the error and the corresponding code ... it doesn't make sense
TypeError: wi.get(...) is undefined
ts Header.jsx:36
React 7
C scheduler.production.min.js:13
T scheduler.production.min.js:14
813 scheduler.production.min.js:14
Webpack 12
react-dom.production.min.js:189:29
React 9
C scheduler.production.min.js:13
T scheduler.production.min.js:14
(Async: EventHandlerNonNull)
813 scheduler.production.min.js:14
Webpack 12
const availableLanguages = [
{
code: "he",
name: "עברית",
country_code: "il",
dir: "rtl",
},
{
code: "en",
name: "English",
country_code: "gb",
},
{
code: "de",
name: "Deutsch",
^---- The error is showing for this code position! There is no code!
country_code: "de",
},
];
This happens on all three environments, tested from three different systems.
Does anyone know what is happening?
EDIT: Full Header.jsx
import { useState, useRef, useEffect } from "react";
import i18next from "i18next";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import useOutsideClick from "../hooks/useOutsideClick";
import useAuth from "../hooks/useAuth";
import Anchor from "./Anchor";
import "./Header.css";
import claryNextLogo from "../images/ClaryNext2.png";
import cookies from "js-cookie";
import { config } from "../Environment";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { solid } from "#fortawesome/fontawesome-svg-core/import.macro";
import { useContext } from "react";
import { WSContext } from "../App";
import { useEffectOnce } from "../hooks/useEffectOnce";
import { distributionService } from "../services/distributionService";
import MessageBox from "./MessageBox";
import useRetryFetch from "../hooks/useRetryFetch";
const availableLanguages = [
{
code: "he",
name: "עברית",
country_code: "il",
dir: "rtl",
},
{
code: "en",
name: "English",
country_code: "gb",
},
{
code: "de",
name: "Deutsch",
country_code: "de",
},
];
function Header() {
const currentLanguageCode = cookies.get("i18next").split("-")[0] || "en";
const currentLanguage = availableLanguages.find(
(l) => l.code === currentLanguageCode
);
const { auth, setAuth } = useAuth();
const navigate = useNavigate();
const dropdownRef = useRef(null);
const retryFetch = useRetryFetch();
const [showLanguageDropdown, setShowLanguageDropdown] = useState(false);
const [showMessageBox, setShowMessageBox] = useState(false);
const [msgBoxTitle, setMsgBoxTitle] = useState("");
const [msgBoxButtons, setMsgBoxButtons] = useState({});
const [msgBoxInputs, setMsgBoxInputs] = useState([]);
const [msgBoxId, setMsgBoxId] = useState("");
const [msgBoxMoreJSX, setMsgBoxMoreJSX] = useState(null);
const [chatRequestUser, setChatRequestUser] = useState("");
const [chatRequestProcessId, setChatRequestProcessId] = useState("");
const [chatRequestRoomId, setChatRequestRoomId] = useState(0);
const { t } = useTranslation();
const { socket } = useContext(WSContext);
const openMessageBox = (title, id, buttons, inputs, moreJSX) => {
setMsgBoxTitle(title);
setMsgBoxId(id);
setMsgBoxButtons(buttons);
setMsgBoxInputs(inputs);
setMsgBoxMoreJSX(moreJSX);
setShowMessageBox(true);
};
const onButton = async (result) => {
console.log("MessageBox button was clicked");
console.dir(result);
if (result.btnId === "yes") {
// chat was accepted, change to ShowFullLog, join room and notify user
socket.send(
JSON.stringify({
command: "acceptchat",
payload: { email: chatRequestUser, roomId: chatRequestRoomId },
})
);
// collect necessary process log information
let response = await retryFetch(
`${config.API_BASE}/api/processes/${chatRequestProcessId}`
);
let process = await response.json();
let stateToPass = {
processId: chatRequestProcessId,
presName: process.prescriptionName,
presHistoryId: process.fiPrescriptionHistory._id,
autochat: true,
};
if (process.fiExpert?.email === auth.email) {
stateToPass.userEmail = process.fiInitiator?.email;
navigate("/fulllog", { state: stateToPass });
} else {
stateToPass.expertEmail = process.fiExpert?.email;
navigate("/userlog", { state: stateToPass });
}
} else {
// chat was refused, send message to requesting user
socket.send(
JSON.stringify({
command: "refusechat",
payload: {
email: chatRequestUser,
roomId: chatRequestRoomId,
},
})
);
}
};
useEffect(() => {
document.body.dir = currentLanguage?.dir || "ltr";
}, [currentLanguage]);
useEffectOnce(() => {
let messageUnsubscribe = distributionService
.getMessage()
.subscribe((msg) => {
console.log("Header incoming message");
switch (msg.command) {
case "requestchat":
setChatRequestUser(msg.payload.email);
setChatRequestProcessId(msg.payload.processId);
setChatRequestRoomId(msg.payload.roomId);
openMessageBox(
t("msg_chat_requested", {
user: msg.payload.email,
processId: msg.payload.processId,
}),
"requestchat",
[
{ id: "yes", text: t("yes") },
{ id: "no", text: t("no") },
],
[],
""
);
break;
}
});
return () => {
// cleanup subscription
messageUnsubscribe.unsubscribe();
};
}, []);
const login = () => {
navigate("/login");
};
const logout = async () => {
let response = await fetch(config.API_BASE + "/logout", {
credentials: "include",
});
if (!response.ok) alert(t("msg_error_logout"));
let result = await response.json();
console.log(result);
setAuth({});
socket.send(
JSON.stringify({
command: "logout",
payload: undefined,
})
);
navigate("/");
};
// const register = () => {
// navigate("/register");
// };
const showPrescriptions = () => {
//navigate("/design");
navigate("/prescriptionlist");
};
const goHome = () => {
navigate("/");
};
const openMenu = () => {
setShowLanguageDropdown(true);
};
const closeMenu = () => {
setShowLanguageDropdown(false);
};
const selectLanguage = (code) => {
i18next.changeLanguage(code);
setShowLanguageDropdown(false);
};
useOutsideClick(dropdownRef, closeMenu);
return (
<>
<div className="header-menu">
<div className="title" onClick={goHome}>
<img src={claryNextLogo} alt="ClaryNext logo" width="90" />
</div>
{!auth?.email ? (
<>
<div className="notonmobile">
<div>
<Anchor
onClick={showPrescriptions}
text={t("example_prescriptions")}
/>
</div>
</div>
<div className="rightflex notonmobile">
<div className="rightMargin">
<span> </span>
</div>
<button onClick={login}>{t("login")}</button>
<span className="leftMargin bigger">
<FontAwesomeIcon
onClick={() => openMenu()}
icon={solid("globe")}
/>
</span>
</div>
</>
) : (
<>
<div className="rightMargin notonmobile">
<Anchor
onClick={showPrescriptions}
text={t("prescriptions_and_processes")}
/>
</div>
<div className="rightflex notonmobile">
<div className="rightMargin">
<Anchor onClick={logout} text={t("logout")} />
</div>
<span className="smaller">{auth.email}</span>
<span className="leftMargin bigger">
<FontAwesomeIcon
onClick={() => openMenu()}
icon={solid("globe")}
/>
</span>
</div>
</>
)}
</div>
{showMessageBox ? (
<MessageBox
onButton={onButton}
buttons={msgBoxButtons}
text={msgBoxTitle}
inputs={msgBoxInputs}
id={msgBoxId}
moreJSX={msgBoxMoreJSX}
onClose={() => setShowMessageBox(false)}
/>
) : (
""
)}
{showLanguageDropdown ? (
<div className="language_dropdown" ref={dropdownRef}>
{availableLanguages.map((lang, idx) => (
<p key={idx} onClick={() => selectLanguage(lang.code)}>
<span
className={`flag-icon flag-icon-${lang.country_code}`}
></span>
{lang.name}
</p>
))}
</div>
) : (
""
)}
</>
);
}
export default Header;
Originally, I had the code to access the cookie in the main function scope. After I moved it into the useEffect[] callback, it started working also in Firefox.

Can't resolve 'react-transition-group/Transition' in 'C:\...;

I have an app which displays blogposts.
The app works perfectly without styles.
However, when I import a component from EITHER React Bootstrap OR material-ui. I get the following error message:
Can't resolve 'react-transition-group/Transition' in 'C:\Users\xxxxx\Documents\Web Development\bloglist-frontend\node_modules\react-bootstrap\esm'
How do you work around this message?
The App:
import { useState, useEffect } from "react";
import Blog from "./components/Blog";
import blogService from "./services/blogs";
import loginService from "./services/login";
import Notification from "./components/Notification";
import ErrorNotification from "./components/ErrorNotification";
import BlogForm from "./components/BlogForm";
import Togglable from "./components/Togglable";
import { useDispatch, useSelector } from 'react-redux'
import { setBlogs } from './reducers/blogReducer'
import User from './components/User'
import SingleBlog from './components/SingleBlog'
import styled from 'styled-components'
import LoginForm from './components/LoginForm'
import Blogs from './components/Blogs'
import Logout from './components/Logout'
import { Table } from 'react-bootstrap'
import {
BrowserRouter as Router,
Routes, Route, Link
} from "react-router-dom"
const Page = styled.div`
padding: 1em;
background: papayawhip;
`
const Navigation = styled.div`
background: BurlyWood;
padding: 1em;
`
const Footer = styled.div`
background: Chocolate;
padding: 1em;
margin-top: 1em;
`
const App = () => {
/* const [blogs, setBlogs] = useState([]); */
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [user, setUser] = useState(null);
const [errorMessage, setErrorMessage] = useState(null);
const [newAuthor, setNewAuthor] = useState("");
const [newTitle, setNewTitle] = useState("");
const [newUrl, setNewUrl] = useState("");
const [failureMessage, setFailureMessage] = useState(null);
const dispatch = useDispatch()
useEffect(() => {
blogService
.getAll().then(blogs => dispatch(setBlogs(blogs)))
}, [dispatch])
const blogs = useSelector(state => state)
/*
useEffect(() => {
blogService.getAll().then((blogs) => setBlogs(blogs));
}, []); */
useEffect(() => {
const loggedUserJSON = window.localStorage.getItem("loggedBlogappUser");
if (loggedUserJSON) {
const user = JSON.parse(loggedUserJSON);
setUser(user);
blogService.setToken(user.token);
}
}, []);
const handleAuthorChange = (event) => {
setNewAuthor(event.target.value);
};
const handleTitleChange = (event) => {
setNewTitle(event.target.value);
};
const handleUrlChange = (event) => {
setNewUrl(event.target.value);
};
const handleLogin = async (event) => {
event.preventDefault();
try {
const user = await loginService.login({
username,
password,
});
window.localStorage.setItem("loggedBlogappUser", JSON.stringify(user));
blogService.setToken(user.token);
setUser(user);
setUsername("");
setPassword("");
setErrorMessage(`${user.name} is now logged in!`);
setTimeout(() => {
setErrorMessage(null);
}, 5000);
} catch (exception) {
setFailureMessage("Wrong username or password");
setTimeout(() => {
setFailureMessage(null);
}, 5000);
setErrorMessage("Wrong credentials");
setTimeout(() => {
setErrorMessage(null);
}, 5000);
}
};
const handleLogout = () => {
window.localStorage.removeItem("loggedBlogappUser");
};
const padding = {
padding: 5
}
/* const createBlog = async () => {
try {
const blogObject = {
author: newAuthor,
title: newTitle,
url: newUrl,
likes: 0,
};
await blogService.create(blogObject);
setBlogs(blogs.concat(blogObject));
setNewTitle("");
setNewAuthor("");
setNewUrl("");
setErrorMessage(`${blogObject.title} has been added!`);
setTimeout(() => {
setErrorMessage(null);
}, 5000);
} catch (exception) {
setErrorMessage(`blog couldn't be added`);
}
};
*/
/* const handleLike = (id) => {
const blog = blogs.find((b) => b.id === id);
const changedBlog = { ...blog, likes: blog.likes + 1 };
blogService
.update(id, changedBlog)
.then((returnedBlog) => {
setBlogs(blogs.map((blog) => (blog.id !== id ? blog : returnedBlog)));
})
.catch((error) => {
alert(`the blog '${blog.content} was already deleted from the server`);
setBlogs(blogs.filter((b) => b.id !== id));
});
}; */
/* const handleDelete = (id) => {
const blog = blogs.find((b) => b.id === id);
if (window.confirm(`are you sure you want to remove ${blog.title}`)) {
blogService
.remove(id)
.then(setBlogs(blogs.filter((b) => b.id !== id)))
.catch((error) => {
alert(`${error}`);
});
} else {
alert("deletion cancelled");
}
};
*/
/* const arrayforSort = [...blogs] */
if (!blogs) {
return null
}
if (blogs.length > 1)
{
return (
<Router>
<Page>
<Navigation>
<div>
<Link style={padding} to="/">home</Link>
<Link style={padding} to="/blogform">blogform</Link>
<Link style={padding} to="/users">Users</Link>
<Link style={padding} to="/login">Login</Link>
</div>
</Navigation>
<Routes>
<Route path="/blogform" element={<BlogForm newAuthor={newAuthor}
newTitle={newTitle}
newUrl={newUrl}
handleAuthorChange={handleAuthorChange}
handleUrlChange={handleUrlChange}
handleTitleChange={handleTitleChange}
setErrorMessage={setErrorMessage}
blogs={blogs}/>} />
<Route path="/login" element={user === null ? (
<LoginForm handleLogin={handleLogin} setUsername={setUsername} setPassword={setPassword} username={username} password={password} />
) : (
<div>
<p>{user.name} logged-in</p>
<Logout handleLogout={handleLogout} />
</div>
)} />
<Route path="/" element={<Blogs blogs={blogs} />} />
<Route path="/users" element={<User/>} />
</Routes>
<div>
<Notification message={errorMessage} />
<ErrorNotification message={failureMessage} />
<Togglable buttonLabel="new blog">
<BlogForm
newAuthor={newAuthor}
newTitle={newTitle}
newUrl={newUrl}
handleAuthorChange={handleAuthorChange}
handleUrlChange={handleUrlChange}
handleTitleChange={handleTitleChange}
setErrorMessage={setErrorMessage}
blogs={blogs}
/>
</Togglable>
<div>
</div>
</div>
</Page>
</Router>
);
} else {
return (
<SingleBlog blogs={blogs} setBlogs={setBlogs}/>
)
}
};
export default App;

Why do I have to refresh the page when I delete a post? MERN stack

I am a beginner in the MERN stack and I am interested in why I have to refresh the page after deleting the document (post)?
This is my Action.js
export const deletePost = id => async (dispatch, getState) => {
try {
dispatch({ type: DELETE_POST_BEGIN });
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.delete(`/api/v1/post/${id}`, config);
dispatch({ type: DELETE_POST_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: DELETE_POST_FAIL,
payload: { msg: error.response.data.msg },
});
}
};
This is my Reducer.js
export const deletePostReducer = (state = {}, action) => {
switch (action.type) {
case DELETE_POST_BEGIN:
return { loading: true };
case DELETE_POST_SUCCESS:
return { loading: false };
case DELETE_POST_FAIL:
return { loading: false, error: action.payload.msg };
default:
return state;
}
};
And this is my Home page where i list all posts:
import { useEffect } from 'react';
import { Col, Container, Row } from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { getPosts } from '../actions/postActions';
import Loader from '../components/Loader';
import Message from '../components/Message';
import Post from '../components/Post';
const HomePage = () => {
const dispatch = useDispatch();
const allPosts = useSelector(state => state.getPosts);
const { loading, error, posts } = allPosts;
const deletePost = useSelector(state => state.deletePost);
const { loading: loadingDelete } = deletePost;
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
return (
<Container>
{loading || loadingDelete ? (
<Loader />
) : error ? (
<Message variant='danger'>{error}</Message>
) : (
<>
<Row>
{posts.map(post => (
<Col lg={4} key={post._id} className='mb-3'>
<Post post={post} />
</Col>
))}
</Row>
</>
)}
</Container>
);
};
export default HomePage;
And this is my single Post component:
const Post = ({ post }) => {
const dispatch = useDispatch();
const allPosts = useSelector(state => state.getPosts);
const { loading, error, posts } = allPosts;
const userLogin = useSelector(state => state.userLogin);
const { userInfo } = userLogin;
const handleDelete = id => {
dispatch(deletePost(id));
};
return (
<>
<div>{post.author.username}</div>
<Card>
<Card.Img variant='top' />
<Card.Body>
<Card.Title>{post.title}</Card.Title>
<Card.Text>{post.content}</Card.Text>
<Button variant='primary'>Read more</Button>
{userInfo?.user._id == post.author._id && (
<Button variant='danger' onClick={() => handleDelete(post._id)}>
Delete
</Button>
)}
</Card.Body>
</Card>
</>
);
};
And my controller:
const deletePost = async (req, res) => {
const postId = req.params.id;
const post = await Post.findOne({ _id: postId });
if (!post.author.equals(req.user.userId)) {
throw new BadRequestError('You have no permission to do that');
}
await Post.deleteOne(post);
res.status(StatusCodes.NO_CONTENT).json({
post,
});
};
I wish someone could help me solve this problem, it is certainly something simple but I am a beginner and I am trying to understand.
I believe the issue is that you are not fetching the posts after delete is successful.
Try this inside the HomePage component:
...
const [isDeleting, setIsDeleting] = useState(false);
const { loading: loadingDelete, error: deleteError } = deletePost;
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
useEffect(() => {
if (!deleteError && isDeleting && !loadingDelete) {
dispatch(getPosts());
}
setIsDeleting(loadingDelete);
}, [dispatch, deleteError, isDeleting, loadingDelete]);
...
Another method is to use "filtering", but you have to update your reducer as such:
export const deletePostReducer = (state = {}, action) => {
switch (action.type) {
case DELETE_POST_BEGIN:
return { loading: true };
case DELETE_POST_SUCCESS:
return { loading: false, data: action.payload}; // <-- this was changed
case DELETE_POST_FAIL:
return { loading: false, error: action.payload.msg };
default:
return state;
}
};
Now in your HomePage component, you will do something like this when rendering:
...
const { loading: loadingDelete, data: deletedPost } = deletePost;
...
useEffect(() => {
dispatch(getPosts());
if (deletedPost) {
console.log(deletedPost);
}
}, [dispatch, deletedPost]);
return (
...
<Row>
{posts.filter(post => post._id !== deletedPost?._id).map(post => (
<Col lg={4} key={post._id} className='mb-3'>
<Post post={post} />
</Col>
))}
</Row>
)

_id is missing after doing actions

i'm currently creating my first MERN App, and everything is going well, until something happened, and i'm going my try to explain because i need help !
What i'm doing is a facebook clone, where you can post something, you can delete your post and you can update your post, the logic is simple, i call dispatch to pass the data to the actions, the actions pass the data to the backend, and the backend return something to me and it saves in my store, because i'm using redux
The problem is that, when i have 2 post, and i want to delete a post, or maybe i want to edit it, the other post dissapears, it's like it loses its id and then loses the information, then i can't do anything but reaload the page, and it happens always
this is how it looks like, everything fine
Then, after trying to edit a post, the second one lost its information, and in the console, it says that Warning: Each child in a list should have a unique "key" prop, and i already gave each post the key={_id}, but the post lost it and i don't know how
Here's the code
Posts.js
import React, { useState } from "react";
import "./Posts.css";
import moment from "moment";
// Icons
import { BiDotsVertical, BiLike } from "react-icons/bi";
import { MdDeleteSweep } from "react-icons/md";
import { AiFillLike } from "react-icons/ai";
import { GrClose } from "react-icons/gr";
// Calling actions
import { deletePost, } from "../actions/posts.js";
// Gettin The Data From Redux
import { useSelector, useDispatch } from "react-redux";
const Posts = ({ setCurrentId }) => {
const [animation, setAnimation] = useState(false);
const [modal, setModal] = useState(false);
const [modalPost, setModalPost] = useState({});
// Getting The Posts
const posts = useSelector(state => state.posts);
const dispatch = useDispatch();
// Showing And Hiding Modal Window
const ModalWindow = post => {
setModalPost(post);
setModal(true);
};
// Liking the post
// const Like = id => {
// dispatch(giveLike(id));
// setAnimation(!animation);
// };
if (!posts.length) {
return <div>Loading</div>;
} else {
return (
<div className="Posts">
{/* // Modal window for better look to the post */}
{/* {modal && (
<div className="modalWindow">
<div className="container">
<div className="container-image">
<img src={modalPost.image} alt="" />
</div>
<div className="information">
<div className="container-information">
<div className="data-header">
<h2>
User <br />{" "}
<span style={{ fontWeight: "400" }}>
{moment(modalPost.createdAt).fromNow()}
</span>
</h2>
<span className="data-icon" onClick={() => setModal(false)}>
<GrClose />
</span>
</div>
<div className="message">
<h2>{modalPost.title}</h2>
<p>{modalPost.message}</p>
</div>
</div>
</div>
</div>
</div>
)} */}
{/* */}
{posts.map(post => {
const { _id, title, message, image, createdAt, likes } = post;
return (
<div className="Posts-container" key={_id}>
<div className="Fit">
<div className="Fit-stuff">
<h2 className="Fit-stuff_title">
User <br />{" "}
<span style={{ fontWeight: "400" }}>
{moment(createdAt).fromNow()}
</span>
</h2>
<a
className="Fit-stuff_edit"
href="#form"
onClick={() => setCurrentId(_id)}
>
<BiDotsVertical />
</a>
</div>
<div className="Fit-data">
<h2 className="Fit-data_title">{title}</h2>
<p className="Fit-data_message">{message}</p>
{image ? (
<div className="Fit-img">
<img
onClick={() => ModalWindow(post)}
src={image}
alt=""
/>
</div>
) : (
<div></div>
)}
</div>
<div className="Fit-shit">
<span>
{animation ? (
<AiFillLike className="fullLightBlue" />
) : (
<BiLike />
)}
{likes}
</span>
<span onClick={() => dispatch(deletePost(_id))}>
<MdDeleteSweep />
</span>
</div>
</div>
</div>
);
})}
</div>
);
}
};
export default Posts;
The form where i call update and create Post
import React, { useState, useEffect } from "react";
import Filebase from "react-file-base64";
// For the actions
import { useDispatch, useSelector } from "react-redux";
import { createPost, updatePost } from "../actions/posts.js";
import {
Wrapper,
FormContainer,
Data,
DataInput,
SecondDataInput,
FormContainerImg,
FormContainerButtons,
Buttons
} from "./FormStyled.js";
const Form = ({ currentId, setCurrentId }) => {
const [formData, setFormData] = useState({
title: "",
message: "",
image: ""
});
const specificPost = useSelector(state =>
currentId ? state.posts.find(p => p._id === currentId) : null
);
// Sending The Data And Editing The data
const dispatch = useDispatch();
useEffect(() => {
if (specificPost) setFormData(specificPost);
}, [specificPost]);
// Clear Inputs
const clear = () => {
setCurrentId(0);
setFormData({ title: "", message: "", image: "" });
};
const handleSubmit = async e => {
e.preventDefault();
if (currentId === 0) {
dispatch(createPost(formData));
clear();
} else {
dispatch(updatePost(currentId, formData));
clear();
}
};
return (
<Wrapper>
<FormContainer onSubmit={handleSubmit}>
<Data>
<DataInput
name="title"
maxLength="50"
placeholder="Title"
type="text"
value={formData.title}
onChange={e => setFormData({ ...formData, title: e.target.value })}
/>
<SecondDataInput
name="message"
placeholder="Message"
maxLength="300"
value={formData.message}
required
onChange={e =>
setFormData({ ...formData, message: e.target.value })
}
/>
<FormContainerImg>
<Filebase
required
type="file"
multiple={false}
onDone={({ base64 }) =>
setFormData({ ...formData, image: base64 })
}
/>
</FormContainerImg>
<FormContainerButtons>
<Buttons type="submit" create>
{specificPost ? "Edit" : "Create"}
</Buttons>
<Buttons onClick={clear} clear>
Clear
</Buttons>
</FormContainerButtons>
</Data>
</FormContainer>
</Wrapper>
);
};
export default Form;
My actions
import {
GETPOSTS,
CREATEPOST,
DELETEPOST,
UPDATEPOST,
LIKEPOST
} from "../actionTypes/posts.js";
import * as api from "../api/posts.js";
export const getPosts = () => async dispatch => {
try {
const { data } = await api.getPosts();
dispatch({ type: GETPOSTS, payload: data });
} catch (error) {
console.log(error);
}
};
export const createPost = newPost => async dispatch => {
try {
const { data } = await api.createPost(newPost);
dispatch({ type: CREATEPOST, payload: data });
} catch (error) {
console.log(error);
}
};
export const updatePost = (id, updatePost) => async dispatch => {
try {
const { data } = await api.updatePost(id, updatePost);
dispatch({ type: UPDATEPOST, payload: data });
} catch (error) {
console.log(error);
}
};
export const deletePost = id => async dispatch => {
try {
await api.deletePost(id);
dispatch({ type: DELETEPOST, payload: id });
} catch (error) {
console.log(error);
}
};
Redux Part
import {
GETPOSTS,
CREATEPOST,
DELETEPOST,
UPDATEPOST,
LIKEPOST
} from "../actionTypes/posts.js";
const postData = (posts = [], action) => {
switch (action.type) {
case GETPOSTS:
return action.payload;
case CREATEPOST:
return [...posts, action.payload];
case UPDATEPOST:
return posts.map(post =>
action.payload._id === post._id ? action.payload : posts
);
case DELETEPOST:
return posts.filter(post => post._id !== action.payload);
default:
return posts;
}
};
export default postData;
My controllers in the backend
import mongoose from "mongoose";
import infoPost from "../models/posts.js";
// Getting All The Posts
export const getPosts = async (req, res) => {
try {
const Posts = await infoPost.find();
res.status(200).json(Posts);
} catch (error) {
res.status(404).json({ message: error.message });
console.log(error);
}
};
// Creating A Post
export const createPost = async (req, res) => {
const { title, message, image } = req.body;
const newPost = new infoPost({ title, message, image });
try {
await newPost.save();
res.status(201).json(newPost);
} catch (error) {
res.status(409).json({ message: error.message });
console.log(error);
}
};
// Update A Post
export const updatePost = async (req, res) => {
const { id } = req.params;
const { title, message, image } = req.body;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`No Post With Id Of ${id}`);
const updatedPost = { title, message, image, _id: id };
await infoPost.findByIdAndUpdate(id, updatedPost, { new: true });
res.json(updatedPost);
};
// Deleting A Post
export const deletePost = async (req, res) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id))
return res
.status(404)
.send(`We Couldnt Found The Post With Id Of ${id} To Delete`);
await infoPost.findByIdAndRemove(id);
res.json(`Post With Id Of ${id} Deleted Succesfully`);
};
// Liking A Post
export const likePost = async (req, res) => {
const { id } = req.params;
if (!mongoose.Types.ObjectId.isValid(id))
return res.status(404).send(`No post with id: ${id}`);
const post = await infoPost.findById(id);
const updatedPost = await infoPost.findByIdAndUpdate(
id,
{ likeCount: post.likeCount + 1 },
{ new: true }
);
res.json(updatedPost);
};
Even though i've been trying to solve this problem for nearly 3.5 hours, i think that the problem might be in my Posts.js part, if you can help me, you're the greatest !

Resources