I've got a React App, that scans products via a Scanner(using serialport.io + socket.io) and adds each scanned product into my frontend cart component.
Right now I got it working, but my solution creates a new row in my cart per product scanned as you can see here and I need it to display a new row only the first time a prod is scanned and then if the same product is detected it only updates the quantity and total per product and also the cart total, something like this...
From what I've searched the best way to do this would be by using react useContext and useReducer but I can't get it working.
This is my code on server side index.js:
io.on("connection", (socket) => {
console.log("Socket Connected");
const port = new SerialPort({
path: "COM6",
baudRate: 9600,
autoOpen: false,
});
socket.on("start_scanner", () => {
port.open(function (err) {
if (err) {
console.log("Error opening port: ", err.message);
}
else{
console.log("Scanner Connected");
}
});
port.on("open", function () {
setInterval(function () {
const portReader = port.read();
if (portReader != null) {
const sensorVal = Buffer.from(portReader).toString();
const soap = require("soap");
const url = "http://example.com?wsdl";
soap.createClient(url, function (err, client) {
client.GetProductById(
{
UserId: "1",
ProductId: sensorVal,
},
function (err, result) {
if (err) return console.log(err);
let productScanned = result.GetProductByIdResult;
socket.broadcast.emit("add_product_to_list", productScanned);
}
);
});
}
}, 700);
});
});
This is my Cart component code:
import { useState, useEffect } from "react";
import io from "socket.io-client";
import ProductRow from "./ProductRow";
import "./ProductsList.css";
const socket = io.connect("http://localhost:5000");
const ProductsList = (props) => {
const [scannedData, setScannedData] = useState([]);
useEffect(() => {
socket.on("add_product_to_list", (productScanned) => {
setScannedData((prevProducts) => [...prevProducts, productScanned]);
});
}, [socket]);
return (
<div className="w-9/12 h-full px-20 py-20 flex flex-col ">
<div className="w-full h-auto my-2 px-3 py-3 font-semibold grid grid-cols-3 bg-blue-600 rounded-xl">
<div className="w-[60%] text-left">Product</div>
<div className="w-[20%] text-left">Quant.</div>
<div className="w-[20%] text-left">Price</div>
</div>
<div
id="products-wrapper"
className="w-full h-[95%] flex flex-col overflow-x-hidden overflow-y-scroll"
>
{scannedData.map((productScanned) => (
<ProductRow data={productScanned} />
))}
</div>
<div className="w-full h-[15%] flex flex-row justify-end">
<div className="w-[20%] h-auto px-3 py-3 font-semibold flex flex-col justify-center bg-blue-600 rounded-xl ">
<div className="w-full text-left">
Total: {/* Total price amount */}{" "}
</div>
<div className="w-full text-left">
Qty: {0}
</div>
</div>
</div>
</div>
);
};
export default ProductsList;
This is my Cart Row component:
import "./ProductsList.css";
const ProductRow = ({ data }) => {
return (
<div
className="product-row w-full h-auto my-2 px-3 py-3 text-black text-center grid grid-cols-3 rounded-xl "
key={data._x003C_Id_x003E_k__BackingField}
>
<div className="w-[60%] text-left">
{data._x003C_Name_x003E_k__BackingField}
</div>
<div className="w-[20%] text-left">{1}</div>
<div className="w-[20%] text-left">
{parseFloat(data._x003C_Price_x003E_k__BackingField).toFixed(2)}€
</div>
</div>
);
};
export default ProductRow;
I've also got a cart-context.js and a CartProvider.js files which I was using to achieve my goal but can't get it to work.
/*cart-context.js*/
import React from "react";
const CartContext = React.createContext({
items: [],
totalAmount: 0,
addItem: (item) => {},
removeItem: (id) => {},
});
export default CartContext;
/*CartProvider.js*/
import { useContext, useReducer } from "react";
import CartContext from "./cart-context";
const defaultCartState = {
items: [],
totalAmount: 0,
};
const cartReducer = (state, action) => {
if (action.type === "ADD_ITEM") {
const updatedTotalAmount = state.totalAmount + action.item.price * action.item.amount;
const existingCartItemIndex = state.items.findIndex(
(item) => item.id === action.item.id
)
const existingCartItem = state.items[existingCartItemIndex];
let updatedItems;
if(existingCartItem){
const updatedItem = {
...existingCartItem,
amount: existingCartItem.amount + action.item.amount
}
updatedItems = [...state.items];
updatedItems[existingCartItemIndex] = updatedItem;
} else{
updatedItems = state.items.concat(action.item);
}
return {
items: updatedItems,
totalAmonut: updatedTotalAmount,
};
}
return defaultCartState;
};
const CartProvider = (props) => {
const [cartState, dispatchCartAction] = useReducer(
cartReducer,
defaultCartState
);
const addItemToCartHandler = (item) => {
dispatchCartAction({ type: "ADD_ITEM", item: item });
};
const removeItemFromCartHandler = (id) => {
dispatchCartAction({ type: "REMOVE_ITEM", id: id });
};
const cartContext = {
items: cartState.items,
totalAmonut: cartState.totalAmonut,
addItem: addItemToCartHandler,
removeItem: removeItemFromCartHandler,
};
return (
<CartContext.Provider value={cartContext}>
{props.children}
</CartContext.Provider>
);
};
export default CartProvider;
Could anyone help me out and help me understand my mistakes?
Thanks in advance.
1.After I came to profile page that show my profile details my console log shows POST http://localhost:3000/profileEdit 500 (Internal Server error)
2.I change my activity drinks about topics and then refresh page it shows nothing like it didn't save at all except image
I debug by myself then tried to find solution in reddit, quora, stackoverflow 5 day but can't find it So please could you help me a bit
this is frontend
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { useCookies } from "react-cookie";
import { Link } from "react-router-dom";
import axios from "axios";
import {
ref, uploadBytesResumable, getDownloadURL, getStorage
} from "firebase/storage";
import { profileInit } from "../redux/action";
import "./profileEdit.css";
import styled from "styled-components";
const Avatar = styled.div`
width: 250px;
height: 250px;
border-radius: 50%;
background-size: cover;
background-position: center;
cursor: pointer;
`;
function ProfileEdit(props) {
const [cookies, removeCookies] = useCookies([
"userName",
"userNickname",
]);
const [activity, setActivity] = useState("");
const [drinks, setDrinks] = useState("");
const [topics, setTopics] = useState("");
const [about, setAbout] = useState("");
const [url, setUrl] = useState("./images/UploadPic.svg");
const [save, setSave] = useState("");
const id = cookies.userName;
const { profileInit, user } = props;
const [image, setImage] = useState(null);
function patchData(event) {
event.preventDefault();
axios
.patch("/users/profile", {
activity,
drinks,
topics,
about,
id,
})
.then(({ data }) => {
if (data.success) {
setSave("Changes were saved");
} else {
setSave(data.err);
}
});
const storage = getStorage();
const storageRef = ref(storage, `images/${cookies.userName || "./images/infoUser.svg"}`);
const uploadTask = uploadBytesResumable(storageRef, image);
uploadTask.on("state_changed", undefined, undefined, () => {
getDownloadURL(storageRef).then((url) => {
setUrl(url);
});
});
if (setUrl !== null || setUrl == null) {
axios
.patch("/users/profile", {
activity,
drinks,
topics,
about,
id,
avatar: url,
})
.then(({ data }) => {
if (data.success) {
setSave("Saved");
} else {
setSave(data.err);
}
});
}
}
function handleChangeAbout(event) {
event.preventDefault();
setAbout(event.target.value);
}
function handleChangeDrinks(event) {
event.preventDefault();
setDrinks(event.target.value);
}
function handleChangeTopics(event) {
event.preventDefault();
setTopics(event.target.value);
}
function handleChangeActivity(event) {
event.preventDefault();
setActivity(event.target.value);
}
function LogOut() {
user.id = null;
removeCookies("userName");
removeCookies("userNickname");
}
useEffect(() => {
const storage = getStorage();
getDownloadURL(ref(storage, `images/${cookies.userName}`))
.then((url) => {
setUrl(url);
});
axios
.post('/users/profileEdit', {
id,
})
.then(({ data }) => {
setActivity(data.profileId.activity);
setDrinks(data.profileId.drinks);
setAbout(data.profileId.about);
setTopics(data.profileId.topics);
profileInit(data.profileId);
});
}, [profileInit, id]);
function photoDownload(e) {
if (e.target.files[0]) {
setImage(e.target.files[0]);
const storage = getStorage();
const storageRef = ref(storage, `images/${cookies.userName}`);
const uploadTask = uploadBytesResumable(storageRef, image);
uploadTask.on(
"state_changed",
() => {
setUrl("./loading.gif");
},
(error) => {
console.log(error);
},
() => {
getDownloadURL(storageRef)
.then((url) => {
setUrl(url);
});
},
);
}
}
return (
<>
<div className="profile-container">
<div style={{ alignSelf: "center" }}>
<label htmlFor="file-input">
<Avatar style={{ backgroundImage: `url(${url})` }} />
</label>
<input id="file-input" type="file" onChange={photoDownload} />
</div>
<form onSubmit={patchData} className="edit">
<span
style={{ textShadow: "none", marginBottom: "8px", color: "#fff" }}
>
Activity:
</span>
<label>
<input
value={activity}
onChange={handleChangeActivity}
type="text"
name="activity"
className="profileInput"
required
/>
</label>
<span
style={{ textShadow: "none", marginBottom: "8px", color: "#fff" }}
>
Topics:
</span>
<label>
<input
value={topics}
onChange={handleChangeTopics}
type="text"
name="topics"
className="profileInput"
required
/>
</label>
<span
style={{ textShadow: "none", marginBottom: "8px", color: "#fff" }}
>
About:
</span>
<label>
<input
value={about}
onChange={handleChangeAbout}
type="text"
name="about"
className="profileInput"
required
/>
</label>
<span
style={{ textShadow: "none", marginBottom: "8px", color: "#fff" }}
>
Drinks:
</span>
<label>
<input
value={drinks}
onChange={handleChangeDrinks}
type="text"
name="drinks"
className="profileInput"
required
/>
</label>
<button
style={{ margin: "0 auto" }}
className="chatButton"
>
{" "}
Save changes
{" "}
</button>
<div style={{ marginTop: "15px", color: "#fff" }}>
{" "}
{save}
</div>
</form>
<div className="quitEdit" style={{ margin: "0 auto" }}>
<Link to="/listUsers" style={{ position: "relative" }}>
<img src="./images/back.svg" width="100" height="100" alt="BackToListPage" title="BackToListPage" />
</Link>
</div>
<div className="exit" style={{ margin: "0 auto" }}>
<Link to="/login" onClick={LogOut} style={{ position: "relative" }}>
<img src="./images/exit.svg" width="100" height="100" alt="Logout" title="Logout" />
</Link>
</div>
</div>
</>
);
}
const mapStateToProps = (state) => ({
profileId: state.user.profileId,
user: state.user,
err: state.error,
});
const mapDispatchToProps = (dispatch) => ({
profileInit: (profileId) => dispatch(profileInit(profileId)),
});
export default connect(mapStateToProps, mapDispatchToProps)(ProfileEdit);
my backend code for /users/profile and /users/profileEdit
router.patch('/profile', async (req, res) => {
const {
activity,
topics,
about,
drinks,
avatar,
id,
} = req.body;
const response = await Profile.updateOne({ person: id }, {
activity, topics, about, drinks, avatar
});
if (response) {
res.send({ success: true });
} else {
res.send({ success: false, err: 'Try again' });
}
});
router.post('/profileEdit', async (req, res) => {
const { id } = req.body;
const response = await Profile.findOne({ person: id });
if (response) {
res.send({ profileId: response });
} else {
res.status(500).send({ err: 'Something went wrong' });
}
});
Edit post here -> modelProfile.js
const mongoose = require('mongoose');
const profileSchema = new mongoose.Schema({
person: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Person',
},
name: {
type: String,
required: true,
minlength: 1,
},
DoB: {
type: Date,
required: true,
},
activity: {
type: String,
required: true,
minlength: 1,
},
about: {
type: String,
minlength: 1,
},
topics: String,
drinks: String,
avatar: String,
latitude: Number,
longitude: Number,
},
{
versionKey: false,
});
module.exports = mongoose.model('Profile', profileSchema);
here is process page
import React, { useState } from "react";
import axios from "axios";
import { LogIn } from "../redux/action";
import { connect } from "react-redux";
import ImageUpload from "./PhotoUpload";
import { useNavigate } from "react-router-dom";
import { FromProcess, FromProcessContainer, ButtonCreate } from "./CreatingElements";
function CreatingAccount (props) {
const navigate = useNavigate();
const [state,setState] = useState({
currentStep: 1,
name: "",
DoB: "",
activity: "",
topics: "",
drinks: "",
about: ""
});
const handleChange = e => {
const { name, value } = e.target;
setState(state => ({
...state, // <-- copy previous state
[name]: value, // <-- update property
}));
};
const handleSubmit = async e => {
e.preventDefault();
const { user } = props;
let { name, DoB, activity, topics, drinks, about } = state;
await axios.post("/users/profile", {
name,
DoB,
activity,
topics,
drinks,
about,
id: user.id
});
const profileId = {
person: user.id,
name,
DoB,
activity,
about,
topics,
drinks
};
props.LogIn(user.id, user.nickname, profileId);
navigate("/listUsers");
};
const _next = () => {
let currentStep = state.currentStep;
currentStep = currentStep >= 2 ? 3 : currentStep + 1;
setState(state => ({
...state, // <-- copy previous state
currentStep: currentStep// <-- update property
}));
};
const _prev = () => {
let currentStep = state.currentStep;
currentStep = currentStep <= 1 ? 1 : currentStep - 1;
setState(state => ({
...state, // <-- copy previous state
currentStep: currentStep// <-- update property
}));
};
function previousButton() {
let currentStep = state.currentStep;
if (currentStep !== 1) {
return (
<>
<ButtonCreate
style={{ color: "#3103ff" }}
className="btn"
type="button"
onClick={_prev}
>
Previous
</ButtonCreate>
<br />
</>
);
}
return null;
}
function nextButton() {
let currentStep = state.currentStep;
if (currentStep < 3) {
return (
<ButtonCreate
className="btn"
type="button"
onClick={_next}
data-cy="next-process"
style={{
marginBottom: "25px",
color: "#FFF",
backgroundColor: "#3103ff"
}}
>
Next
</ButtonCreate>
);
}
return null;
}
return (
<>
<FromProcessContainer>
<FromProcess onSubmit={handleSubmit} >
<p>Step {state.currentStep}</p>
<br/>
<Step1
currentStep={state.currentStep}
handleChange={handleChange}
name={state.name}
DoB={state.DoB}
activity={state.activity}
/>
<Step2
currentStep={state.currentStep}
handleChange={handleChange}
topics={state.topics}
drinks={state.drinks}
/>
<Step3
currentStep={state.currentStep}
handleChange={handleChange}
about={state.about}
/>
{previousButton()}
{nextButton()}
</FromProcess>
</FromProcessContainer>
</>
);
}
function Step1(props) {
if (props.currentStep !== 1) {
return null;
}
return (
<div className="form-group">
<label>
<input
value={props.name}
onChange={props.handleChange}
type="text"
name="name"
placeholder="Your name"
required
data-cy="input-name-process"
/>
</label>
<label>
<input
value={props.DoB}
onChange={props.handleChange}
type="date"
name="DoB"
placeholder="Date of Birth"
max="2010-01-01"
min="1930-12-31"
required
data-cy="input-Dob-process"
/>
</label>
<label>
<input
value={props.activity}
onChange={props.handleChange}
type="text"
name="activity"
required
placeholder="Place of work or study (required)"
data-cy="input-activity-process"
/>
</label>
</div>
);
}
function Step2(props) {
if (props.currentStep !== 2) {
return null;
}
return (
<div className="form-group">
<label>
<input
value={props.topics}
onChange={props.handleChange}
type="text"
name="topics"
placeholder="Favorite topics: (Optional)"
/>
</label>
<label>
<input
type="text"
value={props.drinks}
onChange={props.handleChange}
name="drinks"
placeholder="Favorite drink: (Optional)"
/>
</label>
</div>
);
}
function Step3(props) {
if (props.currentStep !== 3) {
return null;
}
return (
<>
<ImageUpload/>
<div className="form-group">
<label>
<input
value={props.about}
onChange={props.handleChange}
className="form-control"
type="text"
name="about"
placeholder="Caption (Optional)"
/>
</label>
</div>
<button
type="submit"
className="btn"
data-cy="submit-process"
style={{
backgroundColor: "#3103ff",
marginBottom: "25px",
color: "#FFF"
}}
>
Save it
</button>
</>
);
}
const mapStateToProps = state => ({
user: state.user
});
const mapDispatchToProps = dispatch => ({
LogIn: (id, nickname, profileId) => dispatch(LogIn(id, nickname, profileId))
});
export default connect(mapStateToProps, mapDispatchToProps)(CreatingAccount);
backend for /process
router.post('/profile', async (req, res) => {
const {
name,
DoB,
activity,
topics,
about,
drinks,
avatar,
id,
} = req.body;
const user = await Person.findOne({ _id: id }).exec();
if (!user.profileId) {
const newProfile = await Profile.create({
person: id,
name,
DoB,
activity,
topics,
about,
drinks,
avatar,
});
await Person.updateOne(user, { $set: { profileId: newProfile._id } });
return res.send({
success: true,
});
}
await Person.updateOne({ _id: user.profileId }, {
$set: {
activity,
topics,
about,
drinks,
avatar,
},
});
});
In your axios post, add a .then and a .catch, console.log() the result of the post whether its a success or a failure, also, not sure if u have the favourite variable defined globally but that might also be a source of the error
Issue
In the case of backend errors the API is returning a valid response that is missing a profileId property.
router.post('/profileEdit', async (req, res) => {
const { id } = req.body;
const response = await Profile.findOne({ person: id });
if (response) {
res.send({ success: true, profileId: response });
} else {
res.send({ success: false, err: 'Something went wrong' }); // <-- here, no profileId
}
});
This doesn't cause axios to return a Promise rejection and the frontend code assumes the response object has certain properties.
The error object returned is { success: false, err: 'Something went wrong' } but the code is attempting to access into an undefined profileId property.
axios
.post("/users/profileEdit", { id })
.then(({ data }) => {
setPlace(data.profileId.place); // <-- here, data.profileId undefined
setAge(data.profileId.age);
setFavorite(data.profileId.favorite);
setCaption(data.profileId.cation);
profileInit(data.profileId);
});
data.profileId is undefined, and alone isn't an issue, it's later when trying to access the place property of an undefined object that an error is thrown.
Solution
You can keep the backend code and do a check in the frontend for a successful POST request.
axios
.post("/users/profileEdit", { id })
.then(({ data }) => {
if (data.success) {
// POST success
setPlace(data.profileId.place);
setAge(data.profileId.age);
setFavorite(data.profileId.favorite);
setCaption(data.profileId.cation);
profileInit(data.profileId);
} else {
// POST failure, check error
// access data.err
}
});
It would be better to return a non-200 status code so axios can throw/handle a rejected Promise. You can read more about HTTP status codes here. Below I'm returning a super general Status 500 error, but you will want to be a specific as you need to be, i.e. a 404 if the specific person object isn't found.
Example:
router.post('/profileEdit', async (req, res) => {
const { id } = req.body;
const response = await Profile.findOne({ person: id });
if (response) {
res.send({ profileId: response });
} else {
res.status(500).send({ err: 'Something went wrong' });
}
});
...
axios
.post("/users/profileEdit", { id })
.then(({ data }) => {
// 200 responses
const {
profileId,
profileId: { age, cation, favorite, place },
} = data;
setPlace(place);
setAge(age);
setFavorite(favorite);
setCaption(cation);
profileInit(profileId);
})
.catch(error => {
// non-200 responses
});
I am having a problem testing the button click event. In test cases, toHaveBeenCalled returns 0.
Here below is my component file:
index.js (component file)
import { Button, ButtonThemes } from 'stitcht-ui';
import useReels from './hooks/useReels';
import { ReelCard } from '../../components/ReelCard';
import styles from './dashboard.module.scss';
import CreateReelButton from '../../components/CreateReelButton';
const Dashboard = () => {
const {
reels,
fetchMoreReels,
loading,
loadMoreRef,
allReelsLoaded,
threadDeleted,
} = useReels();
const navigate = useNavigate();
const reelOnClick = (reelId) => navigate(`/reel/${reelId}`);
if (!reels) {
return (
<div className={styles.reels}>
<ReelCard loading />
<ReelCard loading />
<ReelCard loading />
</div>
);
}
if (reels && reels.length === 0) {
return (
<div className={styles['empty-message-container']}>
<img src="/reel.svg" alt="No Reels" height="120" width="120" />
<p className={styles['empty-message-title']}>
You don't have any threads
</p>
<p className={styles['empty-message']}>
Create a thread to start a conversation with your communities.
</p>
<CreateReelButton label="CREATE A THREAD" variant="outlined" />
</div>
);
}
return (
<div id="dashboard">
<h2 className="title">Your Threads</h2>
<div className={styles.reels}>
{reels.map((reel) => (
<ReelCard
key={reel.id}
loading={false}
reel={reel}
onClick={() => reelOnClick(reel.id)}
onThreadDeleted={() => threadDeleted(reel)}
/>
))}
</div>
<div className={styles['load-more']} ref={loadMoreRef}>
<Button
theme={ButtonThemes.PRIMARY}
onClick={fetchMoreReels}
loading={loading}
disabled={allReelsLoaded || reels.length < 10}
>
{allReelsLoaded || reels.length < 10
? 'No more threads'
: 'Load More'}
</Button>
</div>
</div>
);
};
export default Dashboard;
index.test.tsx (test file)
import { when, resetAllWhenMocks, verifyAllWhenMocksCalled } from 'jest-when';
import { render, screen, waitFor, userEvent } from '../../../test-utils';
import Dashboard from '.';
import { UserContextValue } from '../../providers/auth';
import useReels from './hooks/useReels';
const userData = {
// dummy user data
};
const reelsData = {
reels: //reels array ,
loading: false,
loadMoreRef: { current: null },
allReelsLoaded: false,
fetchMoreReels: jest.fn(),
threadDeleted: jest.fn(),
};
jest.mock('../../../src/service/user');
jest.mock('../../../src/service/reel');
jest.mock('./hooks/useReels');
jest.mock('../../../src/providers/auth', () => ({
useCurrentUser: () =>
({
user: userData,
} as unknown as UserContextValue),
}));
afterEach(() => {
verifyAllWhenMocksCalled();
resetAllWhenMocks();
});
beforeEach(() => {
when(useReels).expectCalledWith().mockReturnValue(reelsData);
});
describe('Dashboard testing', () => {
test(`"Load more" button should be performed on click`, async () => {
render(<Dashboard />);
const loadMoreButton = screen.getByRole('button', {
name: 'Load More',
});
expect(loadMoreButton).toBeVisible();
await userEvent.click(loadMoreButton);
await waitFor(() => {
expect(jest.fn()).toHaveBeenCalled();
});
});
});
When toHaveBeenCalled always returning 0 but it should return 1.
Can someone tell me why this is return always 0 instead of 1?
My Goal for this one is to Add ObjectId inside the array
In my backend Im declare schema on this code:
tchStudents: [{
type: Schema.Types.ObjectId,
ref: "Student"
}]
THen Im do adding an ObjectId to insert to the array of ObjectID:
My BackEnd is very fine
router.put('/assignAddStudents/:tchID', async (req,res) => {
try {
const searchTch = await Teacher.findOne({ tchID: req.params.tchID })
if(!searchTch){
return res.status(404).send({
success: false,
error: 'Teacher ID not found'
});
} else {
let query = { tchID: req.params.tchID }
let assignedStudentObjID = {$push:{tchStudents: req.body.tchStudents }}
Teacher.updateOne(query, assignedStudentObjID ,() => {
try{
return res.status(200).send({
success: true,
msg: 'Student ID has been assigned'
});
} catch(err) {
console.log(err);
return res.status(404).send({
success: false,
error: 'Teacher ID not found'
})
}
})
}
} catch (err) {
console.log(err)
}
})
But my Front End Not working
err: BAD REQUEST(400) Unexpected token " in JSON at position 0
import React, {useState} from 'react'
import axios from 'axios'
import { URL } from '../../utils/utils'
import { Modal, Button } from 'react-materialize';
import ListTchStudents from '../lists/ListTchStudents';
const trigger =
<Button
style={{marginLeft:'2rem'}}
tooltip="Add More..."
tooltipOptions={{
position: 'top'
}}
className="btn-small red darken-4">
<i className="material-icons center ">add_box</i>
</Button>;
const MdlAddStudents =({teacher}) => {
const [data, setData] = useState('');
const { tchStudents} = data;
const {
tchID,
} = teacher; // IF WE RENDER THIS IT TURNS INTO OBJECT
const assignedStudent = () => {
// BUT WE SENT IT TO THE DATABASE CONVERT TO JSON.STRINGIFY to make ObjectId
const requestOpt = {
method: 'PUT',
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify(data)
}
axios.put(`${URL}teachers/assignAddStudents/${tchID}`, data,requestOpt)
.then(res => {
setData(res.data.data)
})
}
return (
<Modal header="Add Students" trigger={trigger}>
Please ADD and REMOVE Student ID No. for {tchID}
<div>
<ul
style={{marginBottom:'2rem'}}
className="collection">
{
Object.values(teacher.tchStudents).map(tchStudent => {
return(
<ListTchStudents
tchStudent={tchStudent}
/>
);
})
}
</ul>
<div className="row">
<div className="col s6 offset-s3"></div>
<div className="input-field">
<label
htmlFor=""
className="active black-text"
style={{fontSize:'1.3rem'}}>
Add Students here:
</label>
<input
type="text"
name="tchStudents"
value={(tchStudents)}
className="validate"
onChange={(e) => setData(e.target.value)}
/>
</div>
</div>
</div>
{/* BUT WE SENT IT TO THE DATABASE CONVERT TO JSON.STRINGIFY to send ObjectId to the database
*/}
<div className="row">
<div className="col s2 offset-s3" ></div>
<Button
onClick={assignedStudent}
tooltip="Add Students"
tooltipOptions={{
position: 'right'
}}
className="btn green darken-3">
<i className="material-icons ">add_circle</i>
</Button>
</div>
<p>There are {Object.values(teacher.tchStudents).length} student/s
assigned</p>
</Modal>
)
}
// MdlAddStudents.propTypes = {
// assignedStudents: PropTypes.func.isRequired,
// }
export default MdlAddStudents;
// export default connect(null, (assignedStudents))(MdlAddStudents);
Thank you for helping out
The problem stems from you attempting to wrap your tchStudents state property in an object named data.
My advice is to keep it very simple
// it's just a string
const [tchStudents, setTchStudents] = useState("")
const assignedStudent = () => {
// create your request payload
const data = { tchStudents }
// no config object required
axios.put(`${URL}teachers/assignAddStudents/${tchID}`, data)
.then(res => {
// not sure what you want to do here exactly but
// `res.data` should look like
// { success: true, msg: 'Student ID has been assigned' }
setTchStudents("") // clear the input ¯\_(ツ)_/¯
})
}
The only other change is to use the new setter name in your <input>...
<input
type="text"
name="tchStudents"
value={tchStudents}
className="validate"
onChange={(e) => setTchStudents(e.target.value)}
/>
Simply all i'm trying to do is re render the notes list when i add another note to the database. i tried several methods even redux dispatch method. but none worked and it kinda make sense because when i add a note i don't add anything so it can get the updated notes through /budget. maybe i have a big misunderstanding.
here's how i add a new note
export function saveOneNote() {
// saving a note
const _id = $('input[name="_id"]').val(),
firstItem = $('input[name="firstItem"]').val(),
firstPrice = $('input[name="firstPrice"]').val(),
secondItem = $('input[name="secondItem"]').val(),
secondPrice = $('input[name="secondPrice"]').val(),
thirdItem = $('input[name="thirdItem"]').val(),
thirdPrice = $('input[name="thirdPrice"]').val(),
tBudget = $('input[name="tBudget"]').val();
let currency = $("#currency").val();
console.log(currency);
$.ajax({
url: "/newNote",
type: "post",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({
currency,
_id,
firstItem,
firstPrice,
secondItem,
secondPrice,
thirdItem,
thirdPrice,
tBudget
}),
success: function(Data) {
console.log("note was saved!", Data);
},
error: function(err, status, xhr) {
console.log("err", err);
}
});
}
here's how i fetch notes
class ShowAll extends Component {
constructor(props){
super(props);
this.state = {
Data: [],
length:[],
searchbyid:[],
isLoggedIn:[]
}
}
componentDidMount(){
// fetch notes
Rquest.get('/budget').then((res)=>{
let DataString = Array.from(res.body);
this.setState((prevState,props)=>{
return {
Data: DataString,
length: res.body.length
}
})
}).catch((err)=> {
console.log(err);
})
// check if user is logged in
Request.get('/auth').then((user)=>{
if(user){
this.setState({
isLoggedIn: true
})
}
}).catch((err)=> {
this.setState({
isLoggedIn: false
})
});
}
render(){
const count = this.state.length;
const myNotes = this.state.Data;
const isLoggedIn = this.state.isLoggedIn;
const listItems = myNotes.map((dynamicData)=>{
return(
<Fragment key={dynamicData.id}>
<div className='jumbotron'>
<div className='row'>
<button className='btn btn-danger delete-note-btn' onClick={DeleteOneNote}>Delete</button>
<input className='col-12 title form-control' id='deleteById' value={dynamicData._id} readOnly/>
<div className="dropdown-divider"></div> {/*line divider*/}
<div className='col-6' >
<ul className='list-unstyled'>
<li className='items'>items</li>
<li >{dynamicData.firstItem}</li>
<li >{dynamicData.secondItem}</li>
<li >{dynamicData.thirdItem}</li>
{/* <li>Total Budget :</li> */}
</ul>
</div>
<div className='dynamicData col-6'>
<ul className ='list-unstyled'>
<li className='prices'>Prices</li>
<li>{dynamicData.firstPrice} {dynamicData.currency}</li>
<li>{dynamicData.secondPrice} {dynamicData.currency}</li>
<li>{dynamicData.thirdPrice} {dynamicData.currency}</li>
</ul>
</div>
</div>
<h3 className='col-12 totalprice'>{dynamicData.tBudget} {dynamicData.currency}</h3>
</div>
</Fragment>
)
})
return (
<Fragment>
{isLoggedIn ===true?(
<div className='myNotesList '>
number of notes : {count}
{listItems}
</div>
):(
<Fragment>
</Fragment>
)
}
</Fragment>
)
}
}
React components are re-rendering only on state or props change. In your code - you're not mutating state nor props of your component.
What you should do in your case probably is to re-fetch the items after save or add the new items to the state or pass through props.
Example:
class Notes extends React.Component {
state = { note: '', notes: [] }
changeNote = ({ target: { value } }) => {
this.setState({ note: value });
}
addNote = () => {
this.setState((state) => ({ notes: [...state.notes, state.note] }));
}
render() {
return (
<div>
<input type="text" onChange={this.changeNote} />
<button onClick={this.addNote}>Add</button>
<ul>
{this.state.notes.map(note =>
<li>{note}</li>
)}
</ul>
</div>
)
}
}
ReactDOM.render(
<Notes />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script>
<div id="app">
</div>