Failed to Fetch using Knex JS in in React App - reactjs

I have a component with a checkbox that I'm keeping track of through a localhost db using postgres. When i check the checkboxes, a row is inserted into the table netowrkusers with the login user id in the id column, and the id of the user that was checked in the connections column. It removes the row when unchecked. It seems to work intermittently-but I keep getting a 'failed to fetch' error, and eventually the database doesn't keep proper track of the checked and unchecked boxes. Could someone that knows Knex see if there is a flaw in the code? Here is the Knex.js code I'm using.
app.post("/cb", (req, res) => {
const { loginuser, userid, ischecked } = req.body;
if (ischecked) {
console.log("flag is true");
db.transaction(trx => {
trx
.insert({
id: loginuser,
connections: userid
})
.into("networkusers")
.returning("id", "connections")
.then(() => {
console.log("committing");
trx.commit();
})
.catch(error => {
console.log("error", error);
trx.rollback();
});
}).catch(err => res.status(400).json(err));
} else {
console.log("flag is false");
db.transaction(trx => {
db("networkusers")
.where("id", "=", loginuser)
.andWhere("connections", "=", userid)
.del()
.returning("id", "connections")
.then(() => {
console.log("committing");
console.log(loginuser,userid)
trx.commit();
})
.catch(error => {
console.log("error", error);
trx.rollback();
});
}).catch(err => res.status(400).json(err));
}
});
And here is the components that have the checkbox logic:
import React, { useState } from "react";
const onUpdateCB = (ischecked, loginuser, userid, setisChecked,handleCheck) => {
console.log(ischecked, loginuser, userid);
fetch('http://localhost:3000/cb', {
method: 'post',
headers: {'Content-Type':'application/json'},
body:JSON.stringify({
loginuser,
userid,
ischecked: ischecked
})
}).then(setisChecked(ischecked));
return
};
const Card = props => {
const [isChecked, setisChecked] = useState(props.ischecked);
return (
<div
className="pointer bg-light-green dib br3 pa3 ma2 shadow-5"
onClick={() => props.handleClick(props.id)}
>
<div>
<h3>{props.name}</h3>
<p>{props.company}</p>
<p>{props.phone}</p>
<p>{props.email}</p>
<p>{props.city}</p>
</div>
<div>
My Network
<input
className="largeCheckbox"
type="checkbox"
checked={isChecked}
onChange={() =>
onUpdateCB(!isChecked, props.loginuser.id, props.id, setisChecked,props.handleCheck)
}
/>
</div>
</div>
);
};
export default Card;
NetworkArray component:
import React from "react";
import Card from "./Card";
const NetworkArray = ({
network,
networkusers,
handleChange,
handleClick,
loginuser
}) => {
console.log("in network array", networkusers);
const cardComponent = network.map((user, i) => {
const ischecked = networkusers.filter(n => {
var nw = n.id === loginuser.id && n.connections === network[i].id;
return nw;
});
console.log("is it checked", ischecked);
return (
<Card
key={network[i].id}
name={network[i].firstname + " " + network[i].lastname}
company={network[i].company}
phone={network[i].phone}
email={network[i].email}
city={network[i].city}
ischecked={ischecked.length}
handleChange={handleChange}
handleClick={handleClick}
id={network[i].id}
loginuser={loginuser}
/>
);
});
return <div>{cardComponent}</div>;
};
export default NetworkArray;

This doesn't look quite right:
db.transaction(trx => {
db("networkusers")
.where("id", "=", loginuser)
Normally you'd do:
db.transaction(trx => {
trx("networkusers")
.where("id", "=", loginuser)
or:
db.transaction(trx => {
db("networkusers")
.where("id", "=", loginuser)
.transacting(trx)
I suspect the issue is you're just not using the transaction object consistently. See overview.
You should also respond after your database operation succeeds (not just when it fails). So something like:
db
.transaction(trx => {
// ...query...
})
.then(() => res.json({ message: 'Success.' }))
.catch(err => res.status(400).json(err));

Related

How to switch from use state memory to my backend API in React

i am currently working on an application in react and i am trying to add an extra feature to my code. I currently have the ability to add and delete money from my account and i have it linked to mongo. Working from here i want to take the dates and titles from my calendar portion of the app and put them in another collection in my database instead of storing them in state. So i have now set up the backend API for GET ADD DELETE in my database and it all work in Postmans and the database accepts the same values as the calendar prints in state memory. Theoretically i can just change the state value with the backend API but i am having trouble figuring out how to do this, i used axios in my other section the app so i am wondering how i would do it here, it doesn't have the same setup as my transactions file so its hard to transfer the same principles from what i've already done. Here is my calendar app with the GET ADD DELETE functions.
app2.js
import React, { useState, useEffect } from 'react';
import { CalHeader } from './CalHead';
import { Day } from './Day';
import { AddEvent } from './AddModal';
import { DelModal } from './DelModal';
import { useDate } from './hooks';
import "./style.css";
export const App2 = () => {
const [nav, setNav] = useState(0);
// Const to determine if somethings been clicked
const [clicked, setClicked] = useState(); // clicked, setClicked
// Const for Bills to be saved in local storage
const [bills, setBills] = useState( // bills, setBills
localStorage.getItem('bills') ?
JSON.parse(localStorage.getItem('bills')) :
[]
);
const eventForDate = date => bills.find(e => e.date === date );
useEffect(() => {
localStorage.setItem('bills', JSON.stringify (bills));
}, [bills] );
const { days, dateDisplay} = useDate( bills, nav );
return(
<>
<div>
<br></br>
</div>
<div id="container">
<CalHeader
dateDisplay = {dateDisplay}
onNext={() =>setNav(nav + 1)}
onBack={() =>setNav(nav - 1)}
/>
<div id="weekdays">
<div>Sunday</div>
<div>Monday</div>
<div>Tuesday</div>
<div> Wednesday</div>
<div>Thursday</div>
<div>Friday</div>
<div>Saturday</div>
</div>
<div id="calendar">
{days.map((d, index) => (
<Day
key={index}
day={d}
onClick={() => {
if (d.value !== 'padding') {
setClicked(d.date);
}
}}
/>
))}
</div>
</div>
{
clicked && !eventForDate(clicked) &&
<AddEvent
onClose={() => setClicked(null)}
onSave={title => {
setBills([ ...bills, { title, date: clicked }]);
setClicked(null);
}}
/>
}
{
clicked && eventForDate(clicked) &&
<DelModal
eventText={ eventForDate(clicked).title }
onClose={() => setClicked( null )}
onDelete={() => {
setBills(bills.filter( e => e.date !== clicked ));
setClicked(null);
}}
/>
}
</>
);
};
export default App2;
here is some of my backend i am unsure what you would want to see in the backend but here is the routes and controllers files
calendarRoutes.js
const express = require('express');
const router = express.Router();
const { getCalendars, addCalendars, deleteCalendars} = require('../controllers/calendars');
router
.route('/')
.get(getCalendars)
.post(addCalendars);
router
.route('/:id')
.delete(deleteCalendars);
module.exports = router;
calendars.js
const { response } = require('express');
const Calendar = require('../models/Calendar');
// #desc Get all dates
// #route GET /api/v1/dates
// #access Public
exports.getCalendars = async (req, res, next) => {
try{
const dates = await Calendar.find();
return res.status(200).json({
success: true,
count: dates.length,
data: dates
});
} catch (err) {
return res.status(500).json({
success: false,
error: 'Server Error'
});
}
}
exports.addCalendars = async (req, res, next) => {
try {
const { title, date} = req.body;
const dates = await Calendar.create(req.body);
return res.status(201).json({
succes:true,
data: dates
});
} catch (err) {
if(err.name === 'ValidationError') {
const messages = Object.values(err.errors).map(val => val.message);
return res.status(400).json({
success: false,
error: messages
});
} else {
return res.status(500).json({
success: false,
error: 'Server Error'
});
}
}
}
// #desc Delete transaction
// #route DELETE /api/v1/transaction/:id
// #access Public
exports.deleteCalendars = async (req, res, next) => {
try {
const transaction = await Calendar.findById(req.params.id);
if(!transaction) {
return res.status(404).json({
success: false,
error: 'No transaction found'
});
}
await transaction.remove();
return res.status(200).json({
success: true,
data: {}
});
} catch (err) {
return res.status(500).json({
success: false,
error: 'Server Error'
});
}
}
Any help would be appreciated, Let me know if you need any more files uploaed

React POST request, how to reconcile a timing error?

I have a react file tied to an express/node backend and SQL database. My backend is functioning correctly, all routes are verified with postman and the app has the ability to get, post, update, and delete. However, I am running into an issue with my front end now, specifically regarding my POST request.
Every time I make a post request the server/database are being updated correctly with the new applicant, which I can see populate the back end, but I am receiving the following error on the front end "Cannot read properties of undefined (reading 'store')" pertaining to my function labeled "TableList"
Somehow the TableList function which has the role of extrapolating only the unique stores that applicants are assigned too is picking up an "undefined" value for the new store assignment whenever a POST request is made. What is most confusing to me is that if I then reload the page manually the front end displays correctly. Is this a timing issue related to async?
Below is my main file where all state is held - relevant functions are TableList and those containing the markup New Vehicle Form
import { useState, useEffect, useImperativeHandle } from "react";
import useFetch from "../Components/Fetch/fetch";
import List from "../Components/Sections/list";
import Intro from "../Components/Sections/intro";
import SearchForm from "../Components/Forms/searchForm";
import AddForm from "../Components/Forms/addForm";
import UpdateForm from "../Components/Forms/updateForm";
import { parseDate } from "../Components/Utils/index";
const Home = () => {
/* DATE & TIME FORMAT */
var today = new Date();
const displaytime = parseDate(today);
/*INITIAL STATE*/
const { data, setData, isPending } = useFetch(
`http://localhost:5000/api/applicants`
);
/*NEW VEHICLE FORM: TOGGLE FORM DISPLAY*/
const [showAddForm, setShowAddForm] = useState(false);
const handleAddForm = () => {
setShowAddForm(!showAddForm);
};
/*NEW VEHICLE FORM: POST REQUEST, DECLARING STATE*/
const initialFormState = {
store: "",
first_name: "",
last_name: "",
position: "",
recruiterscreen_status:"",
testing_status:"",
interview_status:"",
backgroundcheck_status:"",
drugscreen_status:"",
paperwork_status:"",
date_in: "",
currentdate: displaytime,
notes:"",
};
const [formData, setFormData] = useState({ ...initialFormState });
/*NEW VEHICLE FORM: POST REQUEST, UPDATING STATE*/
const handleFormChange = (event) => {
const { name, value } = event.target;
setFormData({
...formData,
[name]: value,
});
};
/*NEW VEHICLE FORM: POST REQUEST, TRIGGER RERENDER*/
const confirmpost = (applicant) => {
setData([...data, applicant])
console.log(data)
};
/*NEW VEHICLE FORM: POST REQUEST, SUBMIT TO SERVER*/
const handleFormSubmit = (event) => {
event.preventDefault();
const applicant = formData;
console.log(applicant);
fetch(`http://localhost:5000/api/applicants/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(applicant),
})
.then((response) => response.json())
.then((response) => console.log("Added Successfully"))
.then((applicant) => confirmpost(applicant))
.then(()=>handleAddForm())
.catch((error) => console.log("Form submit error", error));
setFormData({ ...initialFormState });
};
/*DELETE APPLICANT: FRONT END RERENDER*/
const deleteApplicant = (id) => {
const updatedTable = data.filter((item) => item.applicant_id != id);
setData(updatedTable);
};
/*DELETE APPLICANT: SERVER REQUEST*/
const handleDelete = (id, stock) => {
fetch(`http://localhost:5000/api/applicants/${id}`, {
method: "DELETE",
})
.then((response) => console.log("Deleted Applicant"))
.then(() => deleteApplicant(id));
};
/*UPDATE FORM: TOGGLE FORM DISPLAY, ALSO GRAB USER ID*/
const [showUpdateForm, setShowUpdateForm] = useState(false);
const [selectedApplicant, setSelectedApplicant] = useState(null)
const [selectedApplicantName, setSelectedApplicantName] = useState(null)
const handleUpdateForm = (applicant_id, first_name,last_name) => {
setSelectedApplicant(applicant_id)
setSelectedApplicantName(first_name + " "+ last_name)
setShowUpdateForm(!showUpdateForm);
console.log(`Show Form: ${showUpdateForm}`)
};
/*UPDATE FORM: DECLARE INITIAL STATE*/
const initialStatusState = {
recruiterscreen_status:null,
testing_status: null,
interview_status:null,
backgroundcheck_status: null,
drugscreen_status: null,
paperwork_status:null,
notes:null,
};
/*UPDATE FROM: CHANGE APPLICANT STATE*/
const [statusData, setStatusData] = useState({ ...initialStatusState });
const handleStatusChange = (event) => {
const { name, value } = event.target;
setStatusData({
...statusData,
[name]: value,
});
};
/*UPDATE FORM: SUMBIT TO SERVER*/
const handleUpdate = (id) => {
fetch(`http://localhost:5000/api/applicants/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(statusData),
})
.then((response) => response.json())
.then(() => confirmUpdate(id))
.then((response) => console.log(`Updated Successfully to
recruiterscreen_status: ${statusData.recruiterscreen_status},
testing_status: ${statusData.testing_status},
interview_status: ${statusData.interview_status},
backgroundcheck_status: ${statusData.backgroundcheck_status},
drugscreen_status: ${statusData.drugscreen_status},
paperwork_status: ${statusData.paperwork_status},
notes:${statusData.notes},`));
setStatusData({ ...initialStatusState });
};
/*UPDATE FORM: FRONT END RERENDER*/
const confirmUpdate = (id) => {
const updatedTable = data.map((item) =>
item.applicant_id != id
? item
: {
...item,
recruiterscreen_status: statusData.recruiterscreen_status,
testing_status: statusData.testing_status,
interview_status: statusData.interview_status,
backgroundcheck_status: statusData.backgroundcheck_status,
drugscreen_status: statusData.drugscreen_status,
paperwork_status: statusData.paperwork_status,
notes:statusData.notes,
}
);
setData(updatedTable);
handleUpdateForm(id)
};
/* NOTES POP UP */
const [notesIsOpen, setNotesIsOpen] = useState(false)
const togglePopup = () => {
setNotesIsOpen(!notesIsOpen)
}
/*LIST OF ACTIVE STORES*/
const unique = (value, index, self) => {
return self.indexOf(value) === index;
};
const TableList = (data) => {
const list = data.map((element) => element.store);
const uniquelist = list.filter(unique).sort();
console.log(uniquelist)
return uniquelist;
};
const stores = TableList(data)
/*RUN*/
return (
<div>
<Intro displaytime={displaytime} />
<div className="add-applicant">
<button className="add-applicant-btn" onClick={handleAddForm}>
Add Applicant
</button>
</div>
<SearchForm data={data} setData={setData} />
{showAddForm ? (
<AddForm
formData={formData}
setFormData={setFormData}
handleFormChange={handleFormChange}
handleFormSubmit={handleFormSubmit}
/>
) : null}
{showUpdateForm ? (
<UpdateForm data={data} selectedApplicant={selectedApplicant} selectedApplicantName={selectedApplicantName} handleUpdateForm={handleUpdateForm} statusData={statusData} handleStatusChange={handleStatusChange} handleUpdate={handleUpdate} />
) : null}
<hr></hr>
{!isPending ? (
stores.map((element) => (
<div>
{" "}
<List
cut={element}
data={data}
isPending={isPending}
handleDelete={handleDelete}
handleUpdate={handleUpdate}
handleStatusChange={handleStatusChange}
showUpdateForm={showUpdateForm}
handleUpdateForm={handleUpdateForm}
togglePopup={togglePopup}
notesIsOpen={notesIsOpen}
TableList={TableList}
/>
</div>
))
) : (
<div>Loading</div>
)}
</div>
);
};
export default Home;
In the fetch chain the logging step which injects an undefined into the chain.
The logging step needs to pass on its input data:
fetch(`http://localhost:5000/api/applicants/`, { }) // Elided
.then((response) => response.json())
.then((response) => {
console.log("Added Successfully")
return response
)
.then((applicant) => confirmpost(applicant))

General Advice with apollo hooks and too many re renders

background: I am trying to achieve a file upload with DropZone to s3 and graphql serving presigned url for puts and gets and while it might not be perfect it does work. The issue I am having now is when I add in useMutation to push the result to graphlql end which write to mongodb database I am getting too many re renders so looking for advice on how to really understand whats going on here. As ugly as my code may be the upload to s3 works once I don’t have addFileS3(file) the addFileS3(file) is call useMutation to grpahql to write the result to mongoDB so I can retrieve the file at later point so i assumed the best
place for it was the response from axios.
const DropZone = ({ folderId, folderProps }) => {
const [createS3File] = useMutation(ADD_FILE_S3);
const addFileS3 = (file) => {
createS3File({
variables: {
folderId: folderId,
fileName: file.name,
},
})
.then(({ data }) => {
console.log("data", data);
})
.catch((e) => {
console.log(e);
});
};
const {
acceptedFiles,
getRootProps,
getInputProps,
isDragActive,
isDragAccept,
isDragReject,
} = useDropzone({ accept: "image/*, application/pdf" });
const [
getPutURL,
{ loading: loading_url, error: error_url, data: data_url },
] = useLazyQuery(GET_S3_PUT_URL);
if (loading_url) {
console.log("loading");
} else if (error_url) {
console.log(error_url);
} else if (data_url) {
const results = data_url.PUTURL;
results.map((file) => {
const fileResult = acceptedFiles.filter(function(fileAcc) {
return fileAcc.name === file.name;
});
const options = {
params: {
Key: file.name,
ContentType: file.type,
},
headers: {
"Content-Type": file.type,
},
};
axios
.put(file.url, fileResult[0], options)
.then((res) => {
//once i add the below here or outside axios post it goes mental on uploads
addFileS3(file);
})
.catch((err) => {
});
});
}
const acceptedFilesItems = acceptedFiles.map((file) => {
return (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
);
});
const uploadDocs = () => {
let files = [];
acceptedFiles.map((file) => {
const fileObj = { name: file.name, type: file.type };
files.push(fileObj);
});
return getS3URLResult(files);
};
const getS3URLResult = async (files) => {
getPutURL({
variables: {
packet: files,
},
});
};
return (
<StyledDropZone>
<div className="container">
<Container
{...getRootProps({ isDragActive, isDragAccept, isDragReject })}
>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</Container>
{acceptedFilesItems}
</div>
<button onClick={() => uploadDocs(acceptedFiles)}>Upload</button>
</StyledDropZone>
);
};
You're making axios request during render 'flow', not in event handler/chain. It's called, changes state and causes next rerendering - infinite loop.
Both mutation and lazy query have possibility to use onCompleted handler. This is a place to chain/invoke next action (using data result parameter).
... alse hanlder should not return anything (return getS3URLResult(files);) - just call it (getS3URLResult(files);) or directly getPutURL.
update
Probably you're looking for something like this:
const DropZone = ({ folderId, folderProps }) => {
const {
acceptedFiles,
getRootProps,
getInputProps,
isDragActive,
isDragAccept,
isDragReject,
} = useDropzone({ accept: "image/*, application/pdf" });
const uploadDocs = () => {
let files = [];
acceptedFiles.map((file) => {
const fileObj = { name: file.name, type: file.type };
files.push(fileObj);
});
console.log("uploadDocs, from acceptedFiles", files);
// return getS3URLResult(files);
getPutURL({
variables: {
packet: files,
},
});
};
const [
getPutURL,
{ loading: loading_url, error: error_url, data: data_url },
] = useLazyQuery(GET_S3_PUT_URL, {
onCompleted: (data) => {
console.log("PUT_URL", data);
const results = data.PUTURL;
results.map((file) => {
const fileResult = acceptedFiles.filter(function(fileAcc) {
return fileAcc.name === file.name;
});
const options = {
params: {
Key: file.name,
ContentType: file.type,
},
headers: {
"Content-Type": file.type,
},
};
axios
.put(file.url, fileResult[0], options)
.then((res) => {
console.log("axios PUT", file.url);
// addFileS3(file);
createS3File({
variables: {
folderId: folderId,
fileName: file.name,
},
})
})
.catch((err) => {
});
});
}
});
const [createS3File] = useMutation(ADD_FILE_S3,{
onCompleted: (data) => {
console.log("ADD_FILE_S3", data);
//setUploadedFiles( uploadedFiles,concat(data.somefilename) );
}
});
const [uploadedFiles, setUploadedFiles] = useState( [] );
const acceptedFilesItems = acceptedFiles.map((file) => {
return (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
);
});
const renderUploadedFiles ...
return (
<StyledDropZone>
<div className="container">
<Container
{...getRootProps({ isDragActive, isDragAccept, isDragReject })}
>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</Container>
{acceptedFilesItems}
{uploadedFiles.length && <div class="success">
{renderUploadedFiles}
</div>}
</div>
<button onClick={() => uploadDocs(acceptedFiles)}>Upload</button>
</StyledDropZone>
);
};
Some optimalizations should be added (useCallback), not placed for clarity.
For more readability and optimization (limit rerenderings) ... I would move almost all (processing) into separate subcomponent - pass acceptedFiles as prop, render upload button inside.

React project- chatting app. Need advice on setting interval

below is my main code for my project, which is a simple open chatting room.
This is a chatting client basically, which can only get chatting logs and post chats.
To log in, I request for an authorization key to the server and use that key to start chatting.
The thing that I want to do is to get the chatting logs from the server every 3 seconds.
So, after it retrieves chats every 3 seconds, the page should refresh with new chats.
However, this seems not to be working well for me.
If there is a good React pro who can solve this problem for me, please do.
import React from 'react';
import { withRouter } from 'react-router-dom';
import './Chat.css';
import InfoBar from '../InfoBar/InfoBar';
import Input from '../Input/Input';
import Messages from '../Messages/Messages';
const API_ENDPOINT = 'https://snu-web-random-chat.herokuapp.com';
let INTERVAL_ID;
const Chat = ({ token, setToken }) => {
const [ name, setName ] = React.useState('');
const [ message, setMessage ] = React.useState('');
const [ messages, setMessages ] = React.useState([]);
const [ lastEl, setLastEl ] = React.useState({});
React.useEffect(() => {
if (localStorage.getItem('username')) {
setName(localStorage.getItem('username'));
}
window.addEventListener('scroll', listenToScroll, true);
fetchData();
INTERVAL_ID = setInterval(() => {
fetchData();
}, 3000);
return () => {
window.removeEventListener('scroll', listenToScroll);
clearInterval(INTERVAL_ID);
};
}, []);
const fetchData = () => {
fetch(`${API_ENDPOINT}/chats?createdAtFrom=${lastEl.createdAt || ''}`)
.then((res) => res.json())
.then((msgs) => {
const updatedMsgs = messages.concat(msgs);
setLastEl(msgs[msgs.length - 1]);
setMessages(updatedMsgs);
});
};
const listenToScroll = (e) => {
const div = document.getElementById('messagesContainer');
// console.log(window.scrollY);
// const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
// const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
// const scrolled = winScroll / height;
};
const onLogin = (e) => {
e.preventDefault();
fetch(`${API_ENDPOINT}/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `name=${name}`
})
.then((response) => response.json())
.then(({ key }) => {
if (key) {
setToken(true);
localStorage.setItem('__key', key);
localStorage.setItem('username', name);
}
})
.catch((err) => console.error(err));
};
const sendMessage = (e) => {
e.preventDefault();
if (message) {
fetch(`${API_ENDPOINT}/chats`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Key ${localStorage.getItem('__key')}`
},
body: `message=${message}`
})
.then((response) => response.json())
.then((res) => {
const obj = {
createdAt: res.createdAt,
message: res.message,
userName: res.user.name,
_id: res.user._id
};
setMessages([ ...messages, obj ]);
})
.catch((err) => console.error(err));
setMessage('');
}
};
const logout = () => {
localStorage.removeItem('__key');
localStorage.removeItem('username');
setToken(false);
};
const loadMessages = () => {
fetchData();
clearInterval(INTERVAL_ID);
};
return (
<div className="outerContainer">
<div className="innerContainer">
<InfoBar
name={name}
token={token}
handleInput={(e) => setName(e.target.value)}
handleLogin={onLogin}
handleLogout={logout}
/>
<Messages messages={messages} name={name} lastEl={lastEl} loadMore={loadMessages} />
{token && <Input message={message} setMessage={setMessage} sendMessage={sendMessage} />}
</div>
</div>
);
};
export default withRouter(Chat);
I can give u the advice to make a chat app with socket.io, not setInterval.
https://socket.io/get-started/chat

Setting up proper MongoDB implementation in React app

So, currently I'm working on internship React (MERN) app, which is a simple to-do list with ability to create, delete and edit todos. I will post some code from it, but you also can look at the full code on GitHub: https://github.com/Wonderio619/magisale-internship-todo
The next task is connecting my app to MongoDB. I have some "boilerplate" code - I alredy set up connection with MongoDB, also have Express router with routes like get all todos list, send todo to database, update todo with id, get todo with id:
const express = require("express");
const router = express.Router();
let Todo = require('../models/model')
// get all todo list with id
router.get('/', function (req, res) {
Todo.find()
.then((todos) => res.json(todos))
.catch((error) => res.send(error))
})
// send todo to database
router.post('/', function (req, res) {
let todo = new Todo();
todo.titleText = req.body.title;
todo.todoText = req.body.body;
todo.save(function (err) {
if (err)
res.send(err);
res.send('Todo successfully added!');
});
})
// get todo with id
router.get('/:todoId', function (req, res) {
Todo.findById(req.params.todoId)
.then(foundTodo => res.json(foundTodo))
.catch(error => res.send(error));
})
// updates todo with id
router.put('/:todoId', function (req, res) {
Todo.findOneAndUpdate({ _id: req.params.todoId }, req.body, { new: true })
.then((todo) => res.json(todo))
.catch((error) => res.send(error))
})
// deletes todo with id
router.delete('/:todoId', function (req, res) {
Todo.remove({ _id: req.params.todoId })
.then(() => res.json({ message: 'todo is deleted' }))
.catch((error) => res.send(error))
})
module.exports = router;
These routes used when corresponding methods from todo app are called:
import React, { Component } from 'react';
import './ToDo.css';
import Logo from './assets/logo.png';
import ToDoItem from './components/ToDoItem';
import AppBar from './components/AppBar';
import Popover from './components/Popover';
import { connect } from 'react-redux';
class ToDo extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
title: '',
todo: '',
};
};
componentDidMount = () => {
fetch("/api/todos")
.then(data => data.json())
.then(res => this.setState({ list: res.data }));
console.log(this.state.list)
};
createNewToDoItem = () => {
fetch("/api/todos", {
method: "post",
headers: new Headers({
"Content-Type": "application/json"
}),
body: JSON.stringify({
title: this.state.title,
body: this.state.todo
})
})
.catch(err => {
console.error(err);
});
if (this.state.title !== '' & this.state.todo !== '') {
this.props.createTodoItem(this.state.title, this.state.todo);
this.setState({ title: '', todo: '' });
}
};
handleTitleInput = e => {
this.setState({
title: e.target.value,
});
};
handleTodoInput = e => {
this.setState({
todo: e.target.value,
});
};
editItem = (i, updTitle, updToDo) => {
const modifyURL = "/api/todos/" + i;
fetch(modifyURL, {
method: "put",
headers: new Headers({
"Content-Type": "application/json"
}),
body: JSON.stringify({
title: updTitle,
todo: updToDo
})
})
.then(resp => {
if (!resp.ok) {
if (resp.status >= 400 && resp.status < 500) {
return resp.json().then(data => {
let error = { errorMessage: data.message };
throw error;
});
} else {
let error = {
errorMessage: "Please try again later. Server is not online"
};
throw error;
}
}
return resp.json();
})
.then(newTodo => {
let arr = this.props.list;
arr[i].title = updTitle;
arr[i].todo = updToDo;
this.setState({ updateList: true });
});
};
deleteItem = indexToDelete => {
const deleteURL = "/api/todos/" + indexToDelete;
fetch(deleteURL, {
method: "delete"
})
.then(resp => {
if (!resp.ok) {
if (resp.status >= 400 && resp.status < 500) {
return resp.json().then(data => {
let error = { errorMessage: data.message };
throw error;
});
} else {
let error = {
errorMessage: "Please try again later. Server is not online"
};
throw error;
}
}
return resp.json();
})
.then(() => {
this.props.deleteTodoItem(indexToDelete);
});
};
randId() {
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10);
}
eachToDo = (item, i) => {
return <ToDoItem
key={this.randId()}
title={item.title}
todo={item.todo}
deleteItem={this.deleteItem.bind(this, i)}
editItem={this.editItem.bind(this, i)}
/>
};
render() {
const { list } = this.props;
return (
<div className="ToDo">
<img className="Logo" src={Logo} alt="React logo" />
<AppBar />
<div className="ToDo-Container">
<div className="ToDo-Content">
{list.map(this.eachToDo)}
</div>
<div>
<Popover
toDoValue={this.state.todo}
titleValue={this.state.title}
titleOnChange={this.handleTitleInput}
toDoOnChange={this.handleTodoInput}
addHandler={this.createNewToDoItem}
/>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
list: state.list
}
}
const mapDispatchToProps = dispatch => {
return {
deleteTodoItem: id => {
dispatch({ type: "DELETE_TODO", id: id });
},
createTodoItem: (title, todo) => {
dispatch({ type: "CREATE_TODO", title: title, todo: todo });
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ToDo);
Note that "list" array from state is not really used, bacause I have initial list state in Redux state here( it may be implemented bad, but it is anyway):
const initState = {
list: [
{
title: 'Cup cleaning',
todo: "Wash and take away the Kurzhiy's cup from WC"
},
{
title: 'Smoking rollton',
todo: 'Do some rollton and cigarettes'
},
{
title: 'Curious dream',
todo: 'Build a time machine'
}
],
};
const rootReducer = (state = initState, action) => {
switch (action.type) {
case "DELETE_TODO":
let newList = state.list.filter((todo, index) => action.id !== index)
return {
...state,
list: newList
}
case "CREATE_TODO":
const title = action.title;
const todo = action.todo;
let createdList = [
...state.list,
{
title,
todo
}
]
return {
...state,
list: createdList
}
default:
return state;
}
}
export default rootReducer;
So, now I need some help - if I understand everything right, my list state should now be stored inside MongoDB database. But currently it is in Redux, how should I switch from current state implementation to MongoDB properly ?
Also I understand that my MongoDB implementation is far from perfection, I'm just newbie to this, but I need to solve following problems:
1) I tried to get all todos from database in ComponentDidMount method and save it in array, but console.log always show that array is empty smth definitely wrong there.
2) Also connection with database is not really set up, because in general I can only add todos to database, but delete or edit functions does not work, because I'm little stuck about how to implement this index stuff, should I use ObjectId property from MongoDB or should I pass indexes from my main component to database, and how ?
Also any global recommendations regarding proper mongodb implementaion and suggestions or fixes to my code will be greatly appreciated :)
It's not res.data but res that you should inject in your state. res.data is undefined so it won't update the state.list.
componentDidMount = () => {
fetch("/api/todos")
.then(data => data.json())
.then(jsonData => {
console.log('jsonData --', jsonData)
console.log('jsonData.data is empty!', jsonData.data)
this.setState({ list: jsonData })
});
};
1- To be able to update, you're sending an id. You may create id's in your db if that's the way you want to find your todos.
Please note that _id is different from id.
_id mongodb's ObjectId, it is not of type integer but of type ObjectId.
id is just a regular field that you created that is called id.
NB: Your req.params.todoId is an integer. While ObjectId is of type ObjectId! So you won't be able to query one with the wrong type.
var todoSchema = new Schema({
id: Number,
titleText: String,
todoText: String
});
2- Get your todo and update it thanks to the id. If it does not exist, it will be created thanks to the upsert option. Don't forget to cast in order to match your schema. title: req.body.title won't work because you defined it as titleText in your schema.
// updates todo with id
router.put('/:todoId', function (req, res) {
const data = {
titleText: req.body.title,
todoText: req.body.todo
}
Todo.findOneAndUpdate(
{ id: req.params.todoId }, // the query
{ $set: data }, // things to update
{ upsert: true } // upsert option
).then((todo) => res.json(todo))
.catch((error) => res.send(error))
})

Resources