Redux state not resetting on history.push - node.js

I have 2 React components, MyPolls and NewPoll.
MyPolls renders the last 4 polls created by a certain user and each time you press a 'Load more button', it renders 4 more. This is done by making a mongoose call to my mongoDB by sorting in desending order by date and using skip and limit.
My problem right now is that whenever I create a new poll, it redirects me to the MyPolls component via history.push('/mypolls') in my action creator and the order of my polls list gets messed up.
Let's say there are currently 8 polls in my mongoDB:
1 2 3 4 5 6 7 8 (1 being oldest, and 8 being newest).
When I view MyPolls, it will show 8 7 6 5. And if you click 'Load More', you will see the other 4: 8 7 6 5 4 3 2 1.
But after you create a new poll, 9, you will get redirected to MyPolls and it will show this order 8 7 6 5 9 8 7 6 instead (shows 8 instead of 4 on initial rendering).
What's causing this? It seems like my reducer state is not resetting ?
MyPolls.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import Loading from '../Loading';
import Poll from './Poll';
class MyPolls extends Component {
constructor(props) {
super(props);
this.state = {
skip: 0,
isLoading: true,
isLoadingMore: false,
};
}
componentDidMount() {
this.props.fetchMyPolls(this.state.skip)
.then(() => {
setTimeout(() => {
this.setState({
skip: this.state.skip + 4,
isLoading: false
});
}, 1000);
});
}
loadMore(skip) {
this.setState({ isLoadingMore: true });
setTimeout(() => {
this.props.fetchMyPolls(skip)
.then(() => {
const nextSkip = this.state.skip + 4;
this.setState({
skip: nextSkip,
isLoadingMore: false
});
});
}, 1000);
}
renderPolls() {
return this.props.polls.map(poll => {
return (
<Poll
key={poll._id}
title={poll.title}
options={poll.options}
/>
)
})
}
render() {
console.log(this.props.polls);
console.log('skip:', this.state.skip);
return (
<div className='center-align container'>
<h2>My Polls</h2>
{this.state.isLoading ? <Loading size='big' /> :
<div
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-evenly',
alignItems: 'center',
alignContent: 'center'
}}>
{this.renderPolls()}
</div>}
<div className='row'>
{this.state.isLoadingMore ? <Loading size='small' /> :
<button
className='btn red lighten-2 wave-effect waves-light' onClick={() => this.loadMore(this.state.skip)}>
Load More
</button>}
</div>
</div>
);
}
}
function mapStateToProps({ polls }) {
return { polls }
}
export default connect(mapStateToProps, actions)(MyPolls);
NewPoll.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm, Field, FieldArray, arrayPush } from 'redux-form';
import * as actions from '../../actions';
import { withRouter } from 'react-router-dom';
const cardStyle = {
width: '500px',
height: '75px',
margin: '10px auto',
display: 'flex',
alignItems: 'center',
padding: '10px'
};
class NewPoll extends Component {
constructor(props) {
super(props);
this.state = {
showOptions: false,
option: '',
title: ''
};
this.onOptionInputChange = this.onOptionInputChange.bind(this);
this.onAddOption = this.onAddOption.bind(this);
this.renderOption = this.renderOption.bind(this);
this.renderOptionCard = this.renderOptionCard.bind(this);
this.renderTitle = this.renderTitle.bind(this);
this.renderTitleCard = this.renderTitleCard.bind(this);
}
onOptionInputChange(event) {
this.setState({ option: event.target.value });
}
onAddOption() {
const { dispatch } = this.props;
dispatch(arrayPush('newPollForm', 'options', this.state.option));
this.setState({ option: '' });
}
renderOption(props) {
return (
<ul>
{props.fields.map((option, index) => (
<li key={index}>
<div
className='card'
style={cardStyle}>
<Field
type='text'
name={option}
index={index}
component={this.renderOptionCard}
/>
<i
className='material-icons right'
onClick={() => props.fields.remove(index)}
>
delete
</i>
</div>
<div className='red-text'>
{props.meta.error }
</div>
</li>
))}
</ul>
);
}
renderOptionCard({ index, input }) {
return (
<span className='card-title'
style={{ flex: '1' }}>
{`${index + 1})`} {input.value}
</span>
);
}
renderTitle({ input, type, placeholder, meta: { touched, error }}) {
return (
<div>
<div className='input-field inline'>
<input {...input}
type={type}
placeholder={placeholder}
style={{ width: '350px' }}
/>
<div className='red-text'>
{touched && error}
</div>
</div>
<button
type='text'
className='red lighten-2 btn waves-effect waves-light'
onClick={() => {
this.setState({ title: input.value });
input.value = '';
}}
disabled={!input.value}>
Add Title
<i className='material-icons right'>
add
</i>
</button>
</div>
)
}
renderTitleCard({ input }) {
return (
<div
className='card'
style={cardStyle}>
<span className='card-title' style={{ flex: '1' }}>
<strong><u>{input.value}</u></strong>
</span>
<i className='material-icons right' onClick={() => this.setState({ title: '' })}>
delete
</i>
</div>
)
}
onPollSubmit(values) {
const { history } = this.props;
this.props.submitPoll(values, history);
}
render() {
return (
<div className='center-align'>
<h3>Create a new poll:</h3>
<form onSubmit={this.props.handleSubmit(this.onPollSubmit.bind(this))}>
<Field
type='text'
placeholder='Title'
name='title'
component={this.state.title ? this.renderTitleCard : this.renderTitle}
/>
<FieldArray
name='options' component={this.renderOption}
/>
<div className='row'>
<div className='inline input-field'>
<input
value={this.state.option} onChange={this.onOptionInputChange}
placeholder='Option'
style={{ width: '300px' }}
/>
</div>
<button
type='text'
className='red lighten-2 btn waves-effect waves-light'
onClick={this.onAddOption}
disabled={!this.state.option}
>
Add Option
<i className='material-icons right'>
add
</i>
</button>
</div>
<button
type='submit'
className='teal btn-large waves-effect waves-light'
>
Submit
<i className='material-icons right'>
send
</i>
</button>
</form>
</div>
);
}
}
function validate(values) {
const errors = {};
if (!values.title) {
errors.title = 'You must provide a title';
}
if (!values.options || values.options.length < 2) {
errors.options = { _error: 'You must provide at least 2 options' };
}
return errors;
}
NewPoll = reduxForm({
form: 'newPollForm',
validate
})(NewPoll);
export default connect(null, actions)(withRouter(NewPoll));
action creators:
export const submitPoll = (values, history) => async dispatch => {
const res = await axios.post('/api/polls', values);
history.push('/mypolls');
dispatch({ type: FETCH_USER, payload: res.data });
}
export const fetchMyPolls = (skip) => async dispatch => {
const res = await axios.get(`/api/mypolls/${skip}`);
dispatch({ type: FETCH_MY_POLLS, payload: res.data });
}
Poll routes:
app.post('/api/polls', requireLogin, (req, res) => {
const { title, options } = req.body;
const poll = new Poll({
title,
options: options.map(option => ({ option: option.trim() })),
dateCreated: Date.now(),
_user: req.user.id
});
poll.save();
res.send(req.user);
});
app.get('/api/mypolls/:skip', requireLogin, (req, res) => {
Poll.find({ _user: req.user.id })
.sort({ dateCreated: -1 })
.skip(parseInt(req.params.skip))
.limit(4)
.then(polls => {
res.send(polls);
});
});
Poll reducer:
import { FETCH_MY_POLLS, UPDATE_POLL } from '../actions/types';
export default function(state = [], action) {
switch(action.type) {
case FETCH_MY_POLLS:
return [ ...state, ...action.payload];
case UPDATE_POLL:
return (
[...state].map(poll => {
if (poll._id === action.payload._id) {
return action.payload;
}
return poll;
})
)
default:
return state;
}
}
Demo of the app: https://voting-app-drhectapus.herokuapp.com
(Use riverfish#gmail.com and password 123 to login)
Github: https://github.com/drhectapus/voting-app

The MyPolls component is calling fetchMyPolls with 0 on componentDidMount. What is happening is, at first you are visiting /mypolls and server returns polls [8, 7, 6, 5]. This make your polls state [8,7,6,5]. When you create a new poll (say 9), you are redirected to /mypolls/ and calling againg fetchMyPolls with 0. Pay attention that in pollsReducer you have
case FETCH_MY_POLLS:
return [ ...state, ...action.payload];
which simply appends new polls to the end of the state. That's why the new state becomes [8, 7, 6, 5, 9, 8, 7, 6].
You are right that reducer doesn't reset the state. There is no action described in your app doing this. Indeed, it is a good thing not to reset your state, because the client already received the information, why not to use them instead of making a new request to the backend.
A good solution would be defining a new action, eg. FETCH_NEW_POLL and adding a new case to the pollsReducer
case FETCH_NEW_POLL
return [...action.payload, ...state];
You would also need to modify your reducer to have only unique items in your state.
In addition, in App.js you can use onEnter to fetch first 4 polls only if there is none in the state. This way you can remove fetchMyPolls call from the componentDidMount of MyPolls.

Related

React - Display product in Cart one row per id/quantity

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.

profile edit is not working when I change my component in profile detail

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
});

how to test react custom hook function call on button click

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&apos;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?

Unexpected token " in JSON at position 0

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)}
/>

Reactjs - setState doesn't re-render after update

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>

Resources