I'm using react and trying to figure out how to trigger useEffect (on parent) from a child component.
I have a child component that contains a modal with input fields that are passed to my DB.
import React, { useState } from 'react';
import { Modal, Button, Form } from 'react-bootstrap';
const AddContact = () => {
// Modal Setup
const [ showModal, setShowModal ] = useState(false);
const handleClose = () => setShowModal(false);
const handleShow = () => setShowModal(true);
// Using state to hold the values for each field in the form
const [first, setFirst] = useState('');
const [last, setLast] = useState('');
const handleSubmit = (e) => {
e.preventDefault(); // prevents form default action
const addContact = async () => {
const result = await fetch('/api/crm/add', {
method: 'POST',
body: JSON.stringify({ first_name: first, last_name: last }),
headers: {
'Content-Type': 'application/json',
}
});
const body = await result.json();
}
addContact();
handleClose();
};
return (
<>
<Button onClick={handleShow} variant="primary" size="sm">Add Contact</Button>
<Modal show={showModal} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Add Contact</Modal.Title>
</Modal.Header>
<Modal.Body>
<label for="formfirst">First Name: </label><br />
<input
id="formfirst"
name="formfirst"
type="text"
value={first}
onChange={e => setFirst(e.target.value)}
/>
<br/>
<label for="formlast">Last Name: </label><br />
<input
id="last"
name="last"
type="text"
value={last}
onChange={e => setLast(e.target.value)}
/> <br/>
<Form.Group>
</Form.Group>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>Cancel</Button>
<Button variant="primary" onClick={handleSubmit}>Submit</Button>
</Modal.Footer>
</Modal>
</>
);
}
export default AddContact;
The parent component has a table of data which I would like to refresh so that it shows the current data:
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { Table } from 'react-bootstrap';
import AddContactForm from '../components/AddContactForm';
// React-Table
import {
useTable,
} from 'react-table'
const ContactsPage = () => {
const [ tableData, setTableData ] = useState([]);
useEffect( () => {
const fetchData = async () => {
const result = await fetch(`api/crm/get`);
const body = await result.json();
setTableData(body);
}
fetchData();
},[]);
const columns = React.useMemo(
() => [
{
Header: 'First Name',
accessor: 'first_name',
},
{
Header: 'Last Name',
accessor: 'last_name',
}
... SIMPLIFIED FOR CONCISENESS
],
[]
)
function ReactTable({ columns, data }) {
const {
getTableProps,
... REMOVED SOME REACT-TABLE FOR CONCISENESS
} = useTable({
columns,
data,
})
return (
<>
<h2>Contacts</h2>
<hr></hr>
<div>
<AddContactForm />
</div>
<Table striped bordered hover size="sm" {...getTableProps()}>
... REMOVED TABLE TO CONCISENESS
</Table>
</>
);
}
const data = React.useMemo(() => tableData)
return (
<Styles>
<ReactTable columns={columns} data={data} />
</Styles>
)
}
export default ContactsPage;
How can I achieve this? I tried making my useEffect hook into a function which I could pass to my child component, but I got an error saying that that new function was not a function???
Not sure it makes sense to pass useEffect as props, instead pass the function used inside useEffect as props. Here
useEffect( () => {
const fetchData = async () => {
const result = await fetch(`api/crm/get`);
const body = await result.json();
setTableData(body);
}
fetchData();
},[]);
Refactor it like this;
// Wrapped in useCallback otherwise it would be recreated each time
// this component rerenders, hence triggering useEffect below
let fetchData = React.useCallback(async () => {
const result = await fetch(`api/crm/get`);
const body = await result.json();
setTableData(body);
},[])
useEffect(() => {
fetchData();
},[fetchData]);
and pass fetchData as props to the modal which you can invoke after submit.
Related
I have a User page component which has a link that is meant to open a FollowingModal modal component to show the followers of a user.
The User component calls an async function in my followers context to retieve the user's details of the user page I am on and uses the data to update a userDetails state hook.
I'm then attempting to pass the data in the state object to mt FollowingModal as props but the modal always shows these props as undefined.
I presume this is something like the modal is being rendered before the state is updated, but I'm not sure how to go about this in order to get the desired result of these props being properly initialized and passed to the modal component
Here's my current code (minus all the irrelevant functionality I've stripped out)
User.jsx:
import { useParams } from 'react-router-dom';
import { Container, Row, Col, Button, Tabs, Tab } from 'react-bootstrap';
import { useAuth } from '../contexts/FollowersContext';
import { FollowingModal } from './partials/user/FollowingModal';
import { ProfileTabAddedPages } from './partials/ProfileTabAddedPages';
import { ProfileTabLikedPages } from './partials/ProfileTabLikedPages';
export function User() {
const { username } = useParams();
const {
checkIsFollowing,
getUserImageById,
getUserProfileByUsername,
} = useAuth();
const handleCloseFollowingDialog = () => setShowFollowingDialog(false);
const handleSetShowFollowingDialog = () => setShowFollowingDialog(true);
const [showFollowingDialog, setShowFollowingDialog] = useState(false);
const [userDetails, setUserDetails] = useState([]);
const [loadingUserDetails, setLoadingUserDetails] = useState(true);
const getUserDetails = async () => {
try {
const data = await getUserProfileByUsername(username);
setUserDetails(data);
setLoadingUserDetails(false);
} catch (error) {
console.log('Error retrieving user profile' + error);
setLoadingUserDetails(false);
}
};
useEffect(() => {
getUserDetails();
// eslint-disable-next-line
}, [loadingUserDetails]);
return (
<div className="container-md clear-header-footer">
<Container flex>
<FollowingModal
showFollowingDialog={showFollowingDialog}
onHideFollowingDialog={handleCloseFollowingDialog}
userid={userDetails?.id}
username={userDetails?.username}
></FollowingModal>
<Row>
<Col>
<h1 className="page-heading">profile</h1>
</Col>
</Row>
<Row>
<Col>
<div className="mx-auto image-placeholder-profile">
</div>
</Col>
<Col>
<h2>{userDetails ? userDetails.displayName : 'display name'}</h2>
<h5>#{userDetails ? userDetails.username : 'username'}</h5>
<p>{userDetails ? userDetails.bio : 'bio'}</p>
<div
onClick={() => handleSetShowFollowingDialog()}
className="clickable-text fit-content"
>
<p>following</p>
</div>
</Col>
</Row>
</Container>
</div>
);
}
FollowingModal.jsx:
import React, { useRef, useState, useEffect, Fragment } from 'react';
import { useAuth as useFollowerContext } from '../../../contexts/FollowersContext';
import { Modal, Card, Col} from 'react-bootstrap';
export function FollowingModal(props) {
const {
getUserFollowing,
} = useFollowerContext();
const [following, setFollowing] = useState([]);
const [loadingFollowers, setLoadingFollowing] = useState(true);
const getFollowing = async () => {
try {
// <----- props?.userid is always undefined at this point ----->
const data = await getUserFollowing(props?.userid);
setFollowing(data);
setLoadingFollowing(false);
} catch (error) {
console.log('getFollowing() error: ' + error);
}
};
useEffect(() => {
getFollowing();
// eslint-disable-next-line
}, [loadingFollowers]);
return (
<Fragment>
<Modal
show={props.showFollowingDialog}
onHide={props.onHideFollowingDialog}
userid={props.userid}
centered
>
<Modal.Header closeButton>
<Modal.Title>Following</Modal.Title>
</Modal.Header>
<Modal.Body>
{following?.map((follower, index) => (
<Col key={index}>
<Card>
<Card.Body>
<span>{follower?.username}</span>
</Card.Body>
</Card>
</Col>
))}
</Modal.Body>
</Modal>
</Fragment>
);
}
getUserFollowing() (in FollowersContext.js):
const getUserFollowing = async (id) => {
try {
const usersFollowingRef = query(
collection(db, 'users', id, 'following')
);
const usersFollowingSnapshot = await getDocs(usersFollowingRef);
if (!usersFollowingSnapshot.empty) {
return usersFollowingSnapshot.docs.map((doc) => doc.data());
}
} catch (error) {
console.error(error);
}
};
Managed to fix it in the end. I changed the user.jsx component so that it check for the userDetails.id value before rendering the modal component:
{userDetails?.id && (
<FollowingModal
showFollowingDialog={showFollowingDialog}
onHideFollowingDialog={handleCloseFollowingDialog}
userid={userDetails?.id}
username={userDetails?.username}
></FollowingModal>
)}
I am creating a blog website in which I am embedding react-draft-wysiwyg editor. I am facing problem when the user has to update the blog. When I click the update button the content is gone. I looked into many solutions but I couldn't make it work.
This is my code
import axios from "axios";
import React, { useContext, useEffect, useState } from "react";
import { useLocation } from "react-router";
import { Link } from "react-router-dom";
import { Context } from "../../context/Context";
import "./singlePost.css";
import { EditorState, ContentState, convertFromHTML } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import { convertToHTML } from 'draft-convert';
import DOMPurify from 'dompurify';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import Parser from 'html-react-parser';
export default function SinglePost() {
const location = useLocation();
const path = location.pathname.split("/")[2];
const [post, setPost] = useState({});
const PF = "http://localhost:5000/images/";
const { user } = useContext(Context);
const [title, setTitle] = useState("");
const [desc, setDesc] = useState("");
const [updateMode, setUpdateMode] = useState(false);
useEffect(() => {
const getPost = async () => {
const res = await axios.get("/posts/" + path);
setPost(res.data);
setTitle(res.data.title);
setDesc(res.data.desc);
};
getPost();
}, [path]);
const handleDelete = async () => {
try {
await axios.delete(`/posts/${post._id}`, {
data: { username: user.username },
});
window.location.replace("/");
} catch (err) {}
};
// updating post
const handleUpdate = async () => {
try {
await axios.put(`/posts/${post._id}`, {
username: user.username,
title,
desc,
});
setUpdateMode(false)
} catch (err) {}
};
const [editorState, setEditorState] = useState(
() => EditorState.createWithContent(
ContentState.createFromBlockArray(
convertFromHTML(desc)
)
),
);
const [convertedContent, setConvertedContent] = useState(null);
const handleEditorChange = (state) => {
setEditorState(state);
convertContentToHTML();
}
const convertContentToHTML = () => {
let currentContentAsHTML = convertToHTML(editorState.getCurrentContent());
setConvertedContent(currentContentAsHTML);
setDesc(currentContentAsHTML);
}
const createMarkup = (html) => {
return {
__html: DOMPurify.sanitize(html)
}
}
return (
<div className="singlePost">
<div className="singlePostWrapper">
{post.photo && (
<img src={PF + post.photo} alt="" className="singlePostImg" />
)}
{updateMode ? (
<input
type="text"
value={title}
className="singlePostTitleInput"
autoFocus
onChange={(e) => setTitle(e.target.value)}
/>
) : (
<h1 className="singlePostTitle">
{title}
{post.username === user?.username && (
<div className="singlePostEdit">
<i
className="singlePostIcon far fa-edit"
onClick={() => setUpdateMode(true)}
></i>
<i
className="singlePostIcon far fa-trash-alt"
onClick={handleDelete}
></i>
</div>
)}
</h1>
)}
<div className="singlePostInfo">
<span className="singlePostAuthor">
Author:
<Link to={`/?user=${post.username}`} className="link">
<b> {post.username}</b>
</Link>
</span>
<span className="singlePostDate">
{new Date(post.createdAt).toDateString()}
</span>
</div>
{updateMode ? (
// <textarea
// className="singlePostDescInput"
// value={desc}
// onChange={(e) => setDesc(e.target.value)}
// />
<Editor
contentState={desc}
editorState={editorState}
onEditorStateChange={handleEditorChange}
wrapperClassName="wrapper-class"
editorClassName="editor-class"
toolbarClassName="toolbar-class"
/>
) : (
<p className="singlePostDesc">{Parser(desc)}</p>
)}
{updateMode && (
<button className="singlePostButton" onClick={handleUpdate}>
Update
</button>
)}
</div>
</div>
);
}
I want to display desc which is saved in MongoDB database when the user clicks on update button.
The following part is what I tried to do but didn't work.
const [editorState, setEditorState] = useState(
() => EditorState.createWithContent(
ContentState.createFromBlockArray(
convertFromHTML(desc)
)
),
);
I am getting warning in this:
react.development.js:220 Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the r component.
Please help
I'm trying to build a calendar function where you can save the data you introduce to the database so it gets added as an event. The calendar is build with the fullcalendar module for React.
Here is the error I'm getting:
POST localhost:4000/events/create-event net::ERR_FAILED
Here is my models code:
const mongoose = require("mongoose")
const EventSchema = mongoose.Schema({
start: { type : Date, required: false },
end: { type : Date, required: false },
title: { type: String, required: false }
})
const Events = mongoose.model("events", EventSchema)
Here is where the event gets added:
const express = require('express');
const router = express.Router();
const moment = require("moment")
const Events = require('../models/events.js')
router.route('/create-event').post((req, res) => {
const events = {
events:req.body.event
}
const newEvent = new Events(events)
newEvent.save()
.then (() => res.json(newEvent))
.catch(err => res.status(400).json('error' + err))
})
router.route('/get-events').get((req, res) => {
Events.find({start: {$gte: moment(req.query.start).toDate()}, end: {$lte: moment(req.query.end).toDate()}})
.then(event => res.json(event))
.catch(err => res.status(400).json('Error:' + err))
})
module.exports = router
Here is the full code for client side:
import React, {useState, useRef} from 'react';
import FullCalendar from '#fullcalendar/react' // must go before plugins
import dayGridPlugin from '#fullcalendar/daygrid' // a plugin!
import AddEvent from './AddEvent.js'
import axios from "axios"
import moment from "moment"
import "react-datetime/css/react-datetime.css";
function CalendarSection() {
const [modalOpen, setModalOpen] = useState(false)
const [events, setEvents] = useState([])
const calendarRef = useRef(null)
console.log(events)
const onEventAdded = (event) => {
let calendarApi = calendarRef.current.getApi()
calendarApi.addEvent({
start: moment(event.start).toDate(),
end: moment(event.end).toDate(),
title: event.title
})
}
const handleEventAdd = (data) => {
console.log(data.event)
axios.post("localhost:4000/events/create-event", data.event)
}
const handleDateSet = async (data) => {
const response = await axios.get("localhost:4000/events/get-events?start=" +moment(data.start).toISOString() +"&end="+moment(data.end).toISOString())
setEvents(response.data)
}
return (
<section>
<button onClick={() => setModalOpen(true)}>Add Event</button>
<div style={{position: "relative", zIndex: 0}}>
<FullCalendar
ref={calendarRef}
events={events}
plugins={[ dayGridPlugin ]}
initialView="dayGridMonth"
eventAdd={(event) => handleEventAdd(event)}
dateSet={(date)=> handleDateSet(date)}
/>
</div>
<AddEvent isOpen={modalOpen} onClose={() => setModalOpen(false)} onEventAdded={event => onEventAdded(event)} />
</section>
)
}
export default CalendarSection
Here is the Add Event component:
import React, {useState} from 'react'
import Modal from "react-modal"
import Datetime from 'react-datetime';
function AddEvent({isOpen, onClose, onEventAdded}) {
const [title, setTitle] = useState("")
const [start, setStart] = useState(new Date())
const [end, setEnd] = useState(new Date())
const onSubmit = (e) => {
e.preventDefault()
onEventAdded({
title,
start,
end
})
onClose()
}
return (
<Modal isOpen={isOpen} onRequestClose={onClose}>
<form onSubmit={onSubmit}>
<input placeholder="Title" value={title} onChange={e => setTitle(e.target.value)} />
<div>
<label>Start Date</label>
<Datetime value={start} onChange={date => setStart(date)} />
</div>
<div>
<label>End Date</label>
<Datetime value={end} onChange={date => setEnd(date)} />
</div>
<button>Add Event</button>
</form>
</Modal>
)
}
export default AddEvent
I need to fix a memory leak in my app but Im not sure how to. I have a component that uses a modal and I get the error when I am adding an item. The modal is reusable and I use it in other components as well. This is the main component:
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Card, Select, Form, Button } from 'antd';
import Table from 'components/Table';
import Modal from '../Modal';
import styles from '../index.module.scss';
const { Item } = Form;
const { Option } = Select;
const PersonForm = ({ details, form }) => {
const [modalVisible, setModalVisible] = useState(false);
const [name, setName] = useState(
details?.name ? [...details?.name] : []
);
useEffect(() => {
form.setFieldsValue({
name: name || [],
});
}, [form, details, name]);
const addName = values => {
setName([...name, values]);
setModalVisible(false);
};
const removeName = obj => {
setName([...name.filter(i => i !== obj)]);
};
const cancelModal = () => {
setModalVisible(false);
};
return (
<div>
<Card
title="Names
extra={
<Button type="solid" onClick={() => setModalVisible(true)}>
Add Name
</Button>
}
>
<Table
tableData={name}
dataIndex="name"
removeName={removeName}
/>
</Card>
<Item name="name">
<Modal
title="Add Name"
fieldName="name"
onSubmit={addName}
visible={modalVisible}
closeModal={cancelModal}
/>
</Item>
</div>
);
};
PersonForm.propTypes = {
details: PropTypes.instanceOf(Object),
form: PropTypes.instanceOf(Object),
};
PersonForm.defaultProps = {
form: null,
details: {},
};
export default PersonForm;
And this is the modal component:
import React from 'react';
import PropTypes from 'prop-types';
import { Input, Form } from 'antd';
import Modal from 'components/Modal';
import LocaleItem from 'components/LocaleItem';
const { Item } = Form;
const FormModal = ({ visible, closeModal, onSubmit, fieldName, title }) => {
const [form] = Form.useForm();
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 15 },
};
const addItem = () => {
form
.validateFields()
.then(values => {
onSubmit(values, fieldName);
form.resetFields();
closeModal(fieldName);
})
.catch(() => {});
};
const canceledModal = () => {
form.resetFields();
closeModal(fieldName);
};
return (
<Modal
onSuccess={addItem}
onCancel={canceledModal}
visible={visible}
title={title}
content={
<Form {...layout} form={form}>
<Item
name="dupleName"
label="Name:"
rules={[
{
required: true,
message: 'Name field cannot be empty',
},
]}
>
<Input placeholder="Enter a name" />
</Item>
</Form>
}
/>
);
};
FormModal.propTypes = {
visible: PropTypes.bool.isRequired,
closeModal: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
fieldName: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
};
FormModal.defaultProps = {};
export default FormModal;
I get a memory leak when I am in the test file when adding items in the modal. Can someone point out why this is happening and how to fix this? Thanks
Remove closeModal and form.resetFields from addItem function.
const addItem = () => {
form
.validateFields()
.then(values => {
onSubmit(values, fieldName); // when this onSubmit resolves it closes the modal, therefor these two lines below will be executed when component is unmounted, causing the memory leak warning
form.resetFields();
closeModal(fieldName);
})
.catch(() => {});
};
// instead maybe just:
const [form] = Form.useForm();
<Modal onOk={form.submit}>
<Form form={form}>
<Form.Item name="foo" rules={[{ required: true }]}>
<Input />
</Form.Item>
</Form>
</Modal>
Also, as far as I know you don't need to call form.validateFields as Ant Design's Form would do that automatically if rules are set in the Form.Item's.
The parent component connects to a Google Cloud FireStore and saves all data in to cards using setCards hooks.
Next we import two children components in to our parent component:
<UpdateCard card={card} />
<AddCard totalDoclNumbers={totalDoclNumbers} />
PARENT Component - DockList
import React, { useState, useEffect } from 'react';
import { db } from '../firebase';
import UpdateCard from './UpdateCard';
import AddCard from './AddCard';
const DocList = () => {
const [cards, setCards] = useState([]);
const [beginAfter, setBeginAfter] = useState(0);
const [totalDoclNumbers, setTotalDoclNumbers] = useState(0);
useEffect(() => {
const fetchData = async () => {
const data = await db
.collection('FlashCards')
.orderBy('customId')
.startAfter(beginAfter)
.get();
setCards(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
fetchData();
}, [beginAfter]);
return (
<ul className='list'>
{cards.map((card) => (
<li key={card.id} className='list__item' data-id={card.id}>
<UpdateCard card={card} />
</li>
))}
<AddCard totalDoclNumbers={totalDoclNumbers} />
</ul>
);
};
export default DocList;
Inside UpdateCard, we list all data stored in cards using an unordered list:
import React, { useState } from 'react';
import { db } from '../firebase';
const UpdateCard = ({ card }) => {
const [translatedText, setTranslatedText] = useState(card.translatedText);
const [customId, setCustomId] = useState(card.customId);
const onUpdate = async () => {
await db
.collection('FlashCards')
.doc(card.id)
.update({ ...card, customId, originalText, translatedText, imgURL });
};
return (
<>
<input
type='text'
value={customId}
onChange={(e) => {
setCustomId(Number(e.target.value));
}}
/>
<textarea
value={translatedText}
onChange={(e) => {
setTranslatedText(e.target.value);
}}
/>
<button onClick={onUpdate}>
Update
</button>
</>
);
};
export default UpdateCard;
Finally in the second child component, called AddCard, we have a button, which triggers the function onAdd to add new data in to our FireStore collection.
import React, { useState } from 'react';
import { db } from '../firebase';
const AddCard = ({ totalDoclNumbers }) => {
const [newTranslatedText, setNewTranslatedText] = useState([]);
const nextNumber = totalDoclNumbers + 1;
const onAdd = async () => {
await db.collection('FlashCards').add({
translatedText: newTranslatedText,
customId: Number(nextNumber),
});
};
return (
<ul className='list'>
<li key={nextNumber}>
<input
type='text'
className='list__input'
defaultValue={nextNumber}
/>
<textarea
onChange={(e) => setNewTranslatedText(e.target.value)}
/>
<button onClick={onAdd}>
Add
</button>
</li>
</ul>
);
};
export default AddCard;
It all works. When you click the button inside the second child component AddCard component, the new data get stored in to the collection.
But to be able to see new added data, I need to render UpdateCard and that's exactly, what I'm struggling with.
How can I achieve that click on the button inside the AddCard component, triggers rendering in UpdateCard component.
Ok, so first on DocList add a callback function:
const DocList = () => {
...
const [addButtonClickCount, setAddButtonClickCount] = useState(0);
...
return (
<ul className='list'>
{cards.map((card) => (
<li key={card.id} className='list__item' data-id={card.id}>
<UpdateCard card={card} addButtonClickCount={addButtonClickCount}/>
</li>
))}
<AddCard totalDoclNumbers={totalDoclNumbers} onAddButtonClick={(card) => {
setAddButtonClickCount(c => c + 1)
setCards(cards => [...cards, {...card.data(), id: card.idcard}])
}} />
</ul>
);
};
then call onAddButtonClick which is passed to AddCard as props when needed:
const AddCard = ({ totalDoclNumbers, onAddButtonClick }) => {
...
const onAdd = async () => {
// Somehow you gotta get value of newly created card:
let card = await db.collection('FlashCards').add({
translatedText: newTranslatedText,
customId: Number(nextNumber),
});
// pass the newly created card here so you could use it in `UpdateCard`
onAddButtonClick(card) // this looks likes where it belongs.
};
this will result in rerendering of UpdateCard component since it's getting addButtonClickCount as props, if you want to do something in UpdateCard after add button is clicked, you could use useEffect with [addButtonClickCount] dependency array.