React-paypal advanced integration errors - reactjs

I have been trying to implement React-Paypal advanced integration for almost a week now but I keep getting an error. I did not use api from paypal sandbox, instead I'm calling our own api built with dotnet.
When I proceed with payment, order Id was created but the payment declined.
Working flow that I want to achieve is;
onClick Prepare for payment will generate all data that I need to process in the back end. Then, on render or onClick Pay, it will create an order including orderId and capture the payment.
Can anyone spot where I went wrong on my code below?
Error:
Checkout.jsx
import { useState } from "react"
import {
PayPalScriptProvider
} from "#paypal/react-paypal-js";
import CheckoutButton from "./CheckoutButton";
import "../styles/Checkout.css";
import NewCheckoutBtn from "./Checkout2";
const PrepareForPayment = () => {
const [clientId, setClientId] = useState(null)
const [clientToken, setClientToken] = useState(null)
const [merchantRef, setMerchantRef] = useState(null)
const [sessionHash, setSessionHash] = useState(null)
const [showData, setShowData] = useState(false);
const baseUrl = "http://local.payment.web"
const onClick = async () => {
console.log("onClick ran");
return await fetch(`${baseUrl}/api/Ppal/ReactApi/PrepareForPayment`, {
mode: "cors",
method: "post",
headers: {
'content-type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
"curCode": "USD",
"orderAmount": 500
})
}).then(res => {
console.log("fetch Data : ", res);
return res.json()
}).then(data => {
console.log("fetch Data : ", data);
setShowData(true)
setClientId(data.ClientId)
setClientToken(data.ClientToken)
if (data.prepareForPayment) {
setMerchantRef(data.merchantRef)
setSessionHash(data.sessionHash)
}
}).catch(err => console.log(err))
}
return (
<>
<button onClick={onClick} disabled={showData ? true : false}>Prepare for payment</button>
{
clientToken && (
<PayPalScriptProvider
options={{
"client-id": clientId,
components: "buttons,hosted-fields",
"data-client-token": clientToken,
intent: "capture",
vault: false,
}}
>
<CheckoutButton merchantRef={merchantRef} sessionHash={sessionHash} />
</PayPalScriptProvider>
)
}
</>
)
}
export default PrepareForPayment
CheckoutButton.jsx
import { useState, useRef, useEffect } from "react";
import { usePayPalHostedFields, usePayPalScriptReducer } from "#paypal/react-paypal-js";
import "../styles/CheckoutBtn.css";
import { useCallback } from "react";
const CheckoutButton = ({ merchantRef, sessionHash }) => {
const [paid, hasPaid] = useState(false)
const [orderId, setOrderId] = useState(null)
const [supportsHostedFields, setSupportsHostedFields] = useState(null)
const [renderInstance, setRenderInstance] = useState()
const cardHolderName = useRef(null);
const hostedField = usePayPalHostedFields();
const [{ isResolved, options }] = usePayPalScriptReducer()
const paypal = window.paypal
const baseUrl = "http://local.payment.web"
const initPaypal = useCallback(async () => {
if (typeof supportsHostedFields === "boolean" && !!supportsHostedFields) {
const instance = await paypal.HostedFields.render({
createOrder: function() {
return fetch(`${baseUrl}/api/Ppal/ReactApi/CreateOrder2`, {
method: 'post',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
merchantRef: merchantRef,
sessionHash: sessionHash,
orderId: orderId,
intent: "capture",
})
}).then(res => {
console.log("res from createOrder", res);
return res.json()
}).then(data => {
console.log("orderId from initialize : ", data?.orderId);
if (data?.createOrder) return setOrderId(data?.orderId)
}).catch(err => console.log(err))
},
styles: {
input: {
"font-size": "16pt",
color: "#3A3A3A"
},
".number": {
"font-family": "monospace"
},
".valid": {
color: "green"
}
},
fields: {
number: {
selector: "#card-number",
placeholder: "Credit Card Number"
},
cvv: {
selector: "#cvv",
placeholder: "CVV"
},
expirationDate: {
selector: "#expiration-date",
placeholder: "MM/YYYY"
}
}
})
setRenderInstance(instance)
}
}, [merchantRef, sessionHash, orderId, paypal, supportsHostedFields])
useEffect(() => {
if (isResolved) {
setSupportsHostedFields(paypal.HostedFields.isEligible());
}
}, [setSupportsHostedFields, options, isResolved, paypal]);
useEffect(() => {
initPaypal()
}, [initPaypal])
const handleClick = (e) => {
e.preventDefault()
console.log("Order id onclick : ", orderId);
renderInstance.submit().then((data) => {
console.log("Reach here?");
const captureOrder = async () => {
return await fetch(`${baseUrl}/api/Ppal/ReactApi/CaptureOrder`, {
mode: "cors",
method: "post",
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
merchantRef: merchantRef,
sessionHash: sessionHash
})
}).then((res) => {
console.log("res from pay button : ", res);
return res.json()
}).then((data) => {
console.log("data from pay button : ", data);
if(data.isApproved) {
alert("Payment successful")
hasPaid(true)
}
if(!data.isApproved) alert("Payment declined")
if(data.isExpired) alert("Payment expired")
})
}
captureOrder().catch((err) => {
console.log(err)
})
return data
})
console.log("Card holder : ", cardHolderName.current.value);
console.log("merchant ref : ", merchantRef);
console.log("Order id onclick after : ", orderId);
};
return (
<form onSubmit={handleClick}>
<label htmlFor="card-number">Card Number</label>
<div id="card-number"></div>
<label htmlFor="expiration-date">Expiration Date</label>
<div id="expiration-date"></div>
<label htmlFor="cvv">CVV</label>
<div id="cvv"></div>
<button value="submit" id="submit" className="btn">
Pay with Card
</button>
</form>
)
};
export default CheckoutButton;

As others have pointed out, the error in your screenshot comes from this line inside of handleClick
console.log("Card holder : ", cardHolderName.current.value);
You've initialized cardHolderName with null.
const cardHolderName = useRef(null);
But then your code appears to do nothing with that ref. So that current value of the ref will always be null.
To fix the error, remove the reverence to cardHolderName.current.value, or set the value of the ref to an object with a value property.

Related

Value of props is undefined when accessed inside a function

I have a function which update and add new record to my database. I have passed props called matterId from parent to child component but if I do console log inside the functions, it shows undefined instead.
import React, { useState, useEffect } from 'react';
import { Table, Button, Modal, Form } from 'react-bootstrap';
export default function TimeEntry(props){
const { matterId, timeEntriesData } = props;
console.log(`matterId: ${matterId}`)
const [timeEntries, setTimeEntries] = useState([]);
const addTimeEntry = (e, matterId) => {
console.log(`matterId: ${matterId}`)
e.preventDefault();
fetch(`http://localhost:4000/matters/628607f1c8a4009f2fd4801e/timeEntry`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${ localStorage.getItem('token') }`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
description: entryDescription
})
})
.then(res => res.json())
.then(data => {
if (data === true) {
// fetchData();
alert("New time entry successfully aded.");
closeEdit();
} else {
alert("Something went wrong.");
closeEdit();
}
});
};
};
console.log shows this: matterId: undefined
You are declaring the variable again in the function signature.
Change it something like this
const addTimeEntry = (e) => {
console.log(`matterId: ${matterId}`)
....
}
const { matterId, timeEntriesData } = props;
// This matterId is from props.
console.log(`matterId: ${matterId}`);
const [timeEntries, setTimeEntries] = useState([]);
const addTimeEntry = (e, matterId) => {
// This matterId is from function addTimeEntry
console.log(`functionMatterId: ${matterId}`);
};
// Call function like this
addTimeEntry(e, "id"); // expected console >> functionMatterId: id

Error: {"data": "foreach() argument must be of type array|object, string given", "message": "" in React Native

i have to send this Array object to the API when i post the API data
it shows me this error: Error: {"data": "foreach() argument must be of
type array|object, string given", "message": ""
this is my code where i select postcodes
<Picker style={GlobalSS.picker}
selectedValue={postcodes}
mode='dropdown'
dropdownIconRippleColor='orange'
dropdownIconColor='#FF8025'
onValueChange={(itemValue, itemIndex) =>
setPostcodess(itemValue)
}>
<Picker.Item color='grey'
label="Select Postal Code " value="" />
{data3.map(item => (
<Picker.Item label={item.text} value={item.id} />
))}
</Picker>
this is the code where i fetch the postal codes from API
useEffect(() => {
const fetchstateData = async () => {
const resMr = await axios.post
(`${urldemo}getskills`)
setData1(resMr.data.result)
const Org = await axios.post
(`${urldemo}job/job-organization/index?token=${user_token}`)
setJob_organization_id(Org.data.result.job_organization_id)
const responsestate = await axios.post
(`${urldemo}states`)
setData2(responsestate.data.result)
const responsepostal = await axios.post
(`${urldemo}postal-codes`)
setData3(responsepostal.data.result)
}
fetchstateData();
}, []);
const [data1, setData1] = useState([])
const [data2, setData2] = useState([])
const [data3, setData3] = useState([])
const [job_organization_id, setJob_organization_id] = useState("")
const [state_id, setStateId] = useState([]);
const [postcodes, setPostcodess] = useState([]);
const [skills, setSkills] = useState([]);
i want to send post codes in this form
"postcodes": {
"postcode_0": {
"postcode_id": "postcode-0001"
},
"postcode_1": {
"postcode_id": "postcode-0002"
}
},
send Post code and other data here
let data = {
address: address,
job_organization_id: job_organization_id,
customer_id: customer_id,
job_title: job_title,
job_description: job_description,
max_salary: max_salary,
min_salary: min_salary,
job_experience_id: experiance,
job_salary_type_id: salary,
skills: skills,
employment_type_id: jobMode,
job_contact_number: job_contact_number,
job_contact_email: job_contact_email,
job_type_id: jobType,
state_id: state_id,
postcodes: postcodes,
position_no: position_no,
last_apply_date: last_apply_date,
};
fetch(`${urldemo}job/store?token=${user_token}`, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': "application/json",
"Accept": "application/json",
},
})
.then((response) => response.json())
.then((responseJson) => {
console.log("response.JSON ==>", responseJson);
showSuccess('Posted')
if (responseJson.status === 'success') {
console.log(
'Job Posted'
);
showSuccess('Successfully Posted')
setModalVisible(true)
} else {
showError("Not Posted ! something went wrong")
}
})
.catch((error) => {
console.error("Error Received ", error);
});
}

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

Resources