What is a right way to work with reducer in Redux? - node.js

I am building an app, sort of dictionary app. In my app a user can add, update and delete a word. Something happened and now my update function does not work. I don't understand why. When I click an update button an error appears in the console. Your hints and suggestions are appreciated.
Word update API
// #route Put api/words/:id
// #desc Update a word by id
// #access Private
router.put(
'/:id',
passport.authenticate('jwt', { session: false }),
(req, res) => {
const { errors, isValid } = validateWordInput(req.body);
// Check validation
if (!isValid) {
// Return any errors
return res.status(400).json(errors);
}
Profile.findOne({ user: req.user.id }).then(profile => {
Word.findById(req.params.id).then(word => {
// Check for word owner
if (word.user.toString() !== req.user.id) {
return res.status(401).json({ notauthorized: 'User not authorized' });
}
const wordID = req.params.id;
const wordInput = req.body;
// Update
Word.findByIdAndUpdate(
{ _id: wordID },
{ $set: wordInput },
{ returnOriginal: false },
(err, word) => {
if (err) {
console.log(err);
}
}
)
.then(word => {
res.json(word);
})
.catch(err =>
res
.status(404)
.json({ wordcannotbeupdated: 'word cannot be updated' })
);
});
});
}
);
Word actions
export const updateWord = (id, updatedWord, history) => dispatch => {
axios
.put(`/api/words/${id}`, updatedWord)
.then(res => {
dispatch({
type: UPDATE_WORD,
payload: res.data
});
history.push('/my-words');
})
.catch(err =>
dispatch({
type: UPDATE_WORD,
payload: ''
})
);
};
Word reducer
import {
GET_WORDS_BY_USER,
ADD_WORD,
UPDATE_WORD,
GET_WORD_BY_ID,
DELETE_WORD,
SEARCH_WORD
} from '../actions/types';
const initialState = {
words: [],
word: {},
loading: false
};
export default function(state = initialState, action) {
switch (action.type) {
case ADD_WORD:
return {
...state,
words: [action.payload, ...state.words]
};
case GET_WORDS_BY_USER:
return {
...state,
words: action.payload
};
case GET_WORD_BY_ID:
return {
...state,
word: action.payload
};
case UPDATE_WORD:
return {
...state,
words: action.payload
};
case SEARCH_WORD:
return {
...state,
words: action.payload
};
case DELETE_WORD:
return {
...state,
words: state.words.filter(word => word._id !== action.payload)
};
default:
return state;
}
}
Edit Word React component
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
import TextFieldGroup from '../common/TextFieldGroup';
import SelectListGroup from '../common/SelectListGroup';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getWordByID, updateWord } from '../../actions/wordActions';
import isEmpty from '../../validation/is-empty';
import {
lexisOptions,
grammarOptions,
partOfSpeechOptions,
styleOptions,
originOptions,
sphereOptions
} from './options';
class EditWord extends Component {
constructor(props) {
super(props);
this.state = {
ugrWordCyr: '',
rusTranslation: '',
example: '',
exampleTranslation: '',
origin: '',
sphere: '',
see: '',
lexis: '',
grammar: '',
partOfSpeech: '',
style: '',
errors: {}
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.onCheck = this.onCheck.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({ errors: nextProps.errors });
}
if (nextProps.words.word[0]) {
nextProps.words.word.map(word => {
word.ugrWordCyr = !isEmpty(word.ugrWordCyr) ? word.ugrWordCyr : '';
word.rusTranslation = !isEmpty(word.rusTranslation)
? word.rusTranslation
: '';
word.rusTranslation = !isEmpty(word.rusTranslation)
? word.rusTranslation
: '';
word.example = !isEmpty(word.example) ? word.example : '';
word.exampleTranslation = !isEmpty(word.exampleTranslation)
? word.exampleTranslation
: '';
word.origin = !isEmpty(word.origin) ? word.origin : '';
word.sphere = !isEmpty(word.sphere) ? word.sphere : '';
word.see = !isEmpty(word.see) ? word.see : {};
word.lexis = !isEmpty(word.lexis) ? word.lexis : {};
word.grammar = !isEmpty(word.grammar) ? word.grammar : {};
word.see = !isEmpty(word.see) ? word.see : {};
word.style = !isEmpty(word.style) ? word.style : {};
this.setState({
ugrWordCyr: word.ugrWordCyr,
rusTranslation: word.rusTranslation,
example: word.example,
exampleTranslation: word.exampleTranslation,
origin: word.origin,
sphere: word.sphere,
style: word.style,
lexis: word.lexis,
grammar: word.grammar,
see: word.see,
partOfSpeech: word.partOfSpeech
});
});
}
}
componentDidMount() {
this.props.getWordByID(this.props.match.params.id);
}
onSubmit(e) {
e.preventDefault();
const wordData = {
ugrWordCyr: this.state.ugrWordCyr,
rusTranslation: this.state.rusTranslation,
example: this.state.example,
exampleTranslation: this.state.exampleTranslation,
origin: this.state.origin,
sphere: this.state.sphere,
see: this.state.see,
lexis: this.state.lexis,
grammar: this.state.grammar,
partOfSpeech: this.state.partOfSpeech,
style: this.state.style
};
let id = this.props.match.params.id;
// Change a function to update
this.props.updateWord(id, wordData, this.props.history);
}
onChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
onCheck(e) {
this.setState({
current: !this.state.current
});
}
render() {
const { errors } = this.state;
return (
<div className="add-word">
<div className="container">
<div className="row">
<div className="col-md-8 m-auto">
<Link to="/my-words" className="btn btn-light">
Go Back
</Link>
<h1 className="display-4 text-center">Edit Word</h1>
<form onSubmit={this.onSubmit}>
<TextFieldGroup
placeholder="Бала"
info="Введите слово на уйгурском"
name="ugrWordCyr"
value={this.state.ugrWordCyr}
onChange={this.onChange}
error={errors.ugrWordCyr}
/>
<TextFieldGroup
placeholder="Ребенок"
info="Введите слово на русском"
name="rusTranslation"
value={this.state.rusTranslation}
onChange={this.onChange}
error={errors.rusTranslation}
/>
<div className="form-check mb-form">
<input
type="checkbox"
className="form-check-input"
name="see"
value={this.state.see}
onChange={this.onCheck}
id="see"
/>
<label htmlFor="see">Смотри</label>
</div>
<TextFieldGroup
placeholder=""
info="Введите пример предложения на уйгурском"
name="example"
value={this.state.example}
onChange={this.onChange}
error={errors.example}
/>
<TextFieldGroup
placeholder=""
info="Введите перевод примерного предложения на русском"
name="exampleTranslation"
value={this.state.exampleTranslation}
onChange={this.onChange}
error={errors.exampleTranslation}
/>
<h6>Происхождение слова</h6>
<SelectListGroup
placeholder="Арабское"
name="origin"
value={this.state.origin}
onChange={this.onChange}
error={errors.origin}
options={originOptions}
/>
<h6>Сфера употребления слова</h6>
<SelectListGroup
placeholder="Физика"
name="sphere"
value={this.state.sphere}
onChange={this.onChange}
error={errors.sphere}
options={sphereOptions}
/>
<h6>Лексика слова</h6>
<SelectListGroup
placeholder="лексика"
name="lexis"
value={this.state.lexis}
onChange={this.onChange}
error={errors.lexis}
options={lexisOptions}
/>
<h6>Стиль слова</h6>
<SelectListGroup
placeholder="стиль"
name="style"
value={this.state.style}
onChange={this.onChange}
error={errors.style}
options={styleOptions}
/>
<h6>Часть речи</h6>
<SelectListGroup
placeholder=""
name="partOfSpeech"
value={this.state.partOfSpeech}
onChange={this.onChange}
error={errors.partOfSpeech}
options={partOfSpeechOptions}
/>
<h6>Грамматика слова</h6>
<SelectListGroup
placeholder="грамматика"
name="grammar"
value={this.state.grammar}
onChange={this.onChange}
error={errors.grammar}
options={grammarOptions}
/>
<input
type="submit"
value="submit"
className="btn btn-info btn-block mt-4"
/>
</form>
</div>
</div>
</div>
</div>
);
}
}
EditWord.propTypes = {
getWordByID: PropTypes.func.isRequired,
errors: PropTypes.object.isRequired,
updateWord: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
errors: state.errors,
words: state.words
});
export default connect(
mapStateToProps,
{ getWordByID, updateWord }
)(withRouter(EditWord));

axios
.put(/api/words/${id}, updatedWord)
if you are using relative api endpoint then it will be going to request to
<react-server-host:port(which is 3000 in your case)>/api/words/${id}
and i am assuming you are running your backend service on another port than 3000 . Thats why it is giving error 404 not found
So when you are requesting api, write absolute endpoint
<backend-server-host:port>/api/words/${id}

Related

Axios request on Next.js page freezes tab

Wondering how to debug (Or where to get started) on my current Axios issue. I have a Next.js project (12.3) and have an axios interceptor hook that handles all of my internal requests. The interceptor works on every action /on every page however this certain page /me/settings/password&security/email/verify.
This specific action is being used on several other pages with no issue. Evenb copying the entire page from a working one freezes the tab. It hangs in preflight for a few minutes then I get an iteration of [Violation] 'submit' handler took <N>ms.
Here is my entire page:
export default function VerifyEmailForm() {
const router = useRouter();
const user: UserState = useSelector((root: RootState) => root.user);
const [apiError, setApiError] = useState("");
const [schema, setSchema] = useState<any>();
const [resubmit, setResubmit] = useState(false);
const [updateEmail, setUpdateEmail] = useState(false);
const [submitSuccsess, setSubmitSuccess] = useState(false);
const [dispatchAxios, { success, loading, error }] = useAxios();
const initialValues = {
email: user?.email ? user?.email : "",
};
const handleSubmit = async (values: any) => {
if (!user.email || updateEmail) {
await dispatchAxios(
updateUser({
action: [
{
key: "email",
value: values?.email,
},
],
})
);
}
await dispatchAxios(requestEmailVerification(values?.email)).then((res) => {
if (res?.data?.error) {
setApiError(res?.data?.error);
} else {
setResubmit(true);
setSubmitSuccess(true);
}
});
};
useEffect(() => {
const yup = import("yup").then((yup) =>
setSchema(
yup.object().shape({
email: yup
.string()
.email("Must be a valid email")
.required("Email is required"),
})
)
);
}, []);
useEffect(() => {
if (error && !loading) {
setApiError(error?.message);
}
}, [loading, success, error]);
return (
<Formik
validationSchema={schema}
initialValues={initialValues}
onSubmit={handleSubmit}
validateOnBlur={true}
>
{({ handleSubmit, setFieldValue, errors, values, touched, isValid }) => {
return (
<Form
onSubmit={handleSubmit}
onBlur={() =>
setTimeout(() => {
setApiError("");
}, 200)
}
>
<div className="flex flex-wrap gap-9 pb-2.5 ">
{!user.email || updateEmail ? (
<div className="w-full relative">
<div className="relative w-full">
<Field
styles="bg-transparent text-center"
name="email"
type="email"
placeholder="Email address"
component={InputField}
error={errors.email}
/>
{values.email && (
<button
type="button"
onClick={() => setFieldValue("email", "")}
className={`flex items-center justify-center bg-background-secondary p-0.5 rounded-full w-4 h-4 absolute bottom-2 ${
errors.email ? "right-8 " : "right-2.5 "
}`}
>
<span className="sr-only">Clear Field</span>
<XMarkIcon
className="text-deactive w-2.5 "
aria-hidden="true"
/>
</button>
)}
</div>
<InputError
error={errors.email as string}
touched={touched?.email as boolean}
styles={"flex justify-center"}
/>
</div>
) : (
<></>
)}
<div className="w-full flex flex-col justify-center items-center">
<Button
type="submit"
disabled={!isValid}
classes={`w-full text-white p-3 rounded-2xl mx-8 ${
errors.email || !values.email.length
? "border-transparent text-deactive"
: "border-2 border-primary"
}`}
label={resubmit ? "Resend Verification" : "Verify"}
/>
{user.email && (
<Button
type="button"
classes={`w-full text-white p-3 rounded-2xl mx-8 border-transparent`}
label="Update Email"
onClick={() => setUpdateEmail(true)}
/>
)}
<Button
type="button"
classes={`w-full text-white p-3 rounded-2xl mx-8 border-transparent`}
label="Cancel"
onClick={() => router.back()}
/>
</div>
</div>
<ErrorAlertModal errors={apiError ? [apiError] : undefined} />
<SuccessAlertModal success={submitSuccsess} />
</Form>
);
}}
</Formik>
);
}
And Here is my Axios hook:
export function useAxios(
internal: boolean = true
): [DispatchAxios, IAxiosState] {
const { data: session, status } = useSession();
const dispatch = useDispatch();
const isMounted = useRef(true);
const user: UserState = useSelector((root: RootState) => root.user);
const env = process.env.NODE_ENV;
const { logoutUser } = useLogout();
const client = axios.create({
// baseURL: process.env.API_URL,
});
client.interceptors.request.use((config: AxiosRequestConfig) => {
config.headers = config.headers || {};
if (!config.headers["Content-Type"]) {
config.headers["Content-Type"] = "application/json";
}
if (!config.headers["Ocp-Apim-Subscription-Key"] && env === "production") {
config.headers[
"Ocp-Apim-Subscription-Key"
] = `${process.env.NEXT_PUBLIC_API_GATEWAY_KEY}`;
}
// Internal requests need tokens and other parameters added in.
if (internal) {
if (session?.accessToken) {
config.headers.Authorization = `Bearer ${session.accessToken}`;
}
}
return config;
});
const [state, setState] = useState<IAxiosState>({
loading: false,
error: null,
response: null,
success: false,
});
const dispatchAxios: DispatchAxios = useCallback(
(params: IAxiosAction) => {
const action: IAxiosAction = {
type: params.type,
config: params.config,
batchOrder: params.batchOrder || [],
};
if (params.data) {
action.data = params.data;
}
dispatch({
...action,
loading: true,
});
setState({
...state,
error: null,
response: null,
loading: true,
success: false,
});
return client(params.config)
.then((response: AxiosResponse) => {
// dispatch must come before setState
if (response.data.err === "INVALID TOKEN") {
throw action;
}
dispatch({
...action,
response,
loading: false,
});
if (isMounted.current) {
setState({
...state,
loading: false,
success: true,
error: null,
response,
});
}
return {
success: true,
error: null,
response,
};
})
.catch((error: AxiosError) => {
const originalReq = error.config;
// Token refresh failed, log user out.
if (
originalReq?.url ===
process.env.NEXT_PUBLIC_AUTH_REFRESH_API_ENDPOINT
) {
// logoutUser();
}
// dispatch must come before setState
dispatch({
...action,
error,
loading: false,
success: false,
});
if (isMounted.current) {
setState({
...state,
loading: false,
success: false,
response: null,
error,
});
}
return {
success: false,
response: null,
error,
};
});
},
[isMounted, dispatch, state, client]
);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, [state]);
return [dispatchAxios, state];
}
The updateUser action is being used in several other parts of the app as the user is able to change and update parts of their profile. As you can see the email is being targeted in this instance. We use this same request in another page /me/settings/password&security/email and it resolves just fine no freezing no hanging. Im convin
It was my own import of moment.js. Dynamically importing it in a wrapping component.
Somehow it cased the issue not sure why. :)

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

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

How to update the user feed after updating the post?

I have a UserFeed component and EditForm component. As soon as I edit the form, I need to be redirected to the UserFeed and the updated data should be shown on UserFeed(title and description of the post).
So, the flow is like-UserFeed, which list the posts, when click on edit,redirected to EditForm, updates the field, redirected to UserFeed again, but now UserFeed should list the posts with the updated data, not the old one.
In this I'm just redirectin to / just to see if it works. But I need to be redirected to the feed with the updated data.
EditForm
import React, { Component } from "react";
import { connect } from "react-redux";
import { getPost } from "../actions/userActions"
class EditForm extends Component {
constructor() {
super();
this.state = {
title: '',
description: ''
};
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]: value
})
};
componentDidMount() {
const id = this.props.match.params.id
this.props.dispatch(getPost(id))
}
componentDidUpdate(prevProps) {
if (prevProps.post !== this.props.post) {
this.setState({
title: this.props.post.post.title,
description: this.props.post.post.description
})
}
}
handleSubmit = () => {
const id = this.props.match.params.id
const data = this.state
this.props.dispatch(updatePost(id, data, () => {
this.props.history.push("/")
}))
}
render() {
const { title, description } = this.state
return (
<div>
<input
onChange={this.handleChange}
name="title"
value={title}
className="input"
placeholder="Title"
/>
<textarea
onChange={this.handleChange}
name="description"
value={description}
className="textarea"
></textarea>
<button>Submit</button>
</div>
);
}
}
const mapStateToProps = store => {
return store;
};
export default connect(mapStateToProps)(EditForm)
UserFeed
import React, { Component } from "react"
import { getUserPosts, getCurrentUser } from "../actions/userActions"
import { connect } from "react-redux"
import Cards from "./Cards"
class UserFeed extends Component {
componentDidMount() {
const authToken = localStorage.getItem("authToken")
if (authToken) {
this.props.dispatch(getCurrentUser(authToken))
if (this.props && this.props.userId) {
this.props.dispatch(getUserPosts(this.props.userId))
} else {
return null
}
}
}
render() {
const { isFetchingUserPosts, userPosts } = this.props
return isFetchingUserPosts ? (
<p>Fetching....</p>
) : (
<div>
{userPosts &&
userPosts.map(post => {
return <Cards key={post._id} post={post} />
})}
</div>
)
}
}
const mapStateToPros = state => {
return {
isFetchingUserPosts: state.userPosts.isFetchingUserPosts,
userPosts: state.userPosts.userPosts.userPosts,
userId: state.auth.user._id
}
}
export default connect(mapStateToPros)(UserFeed)
Cards
import React, { Component } from "react"
import { connect } from "react-redux"
import { compose } from "redux"
import { withRouter } from "react-router-dom"
class Cards extends Component {
handleEdit = _id => {
this.props.history.push(`/post/edit/${_id}`)
}
render() {
const { _id, title, description } = this.props.post
return (
<div className="card">
<div className="card-content">
<div className="media">
<div className="media-left">
<figure className="image is-48x48">
<img
src="https://bulma.io/images/placeholders/96x96.png"
alt="Placeholder image"
/>
</figure>
</div>
<div className="media-content" style={{ border: "1px grey" }}>
<p className="title is-5">{title}</p>
<p className="content">{description}</p>
<button
onClick={() => {
this.handleEdit(_id)
}}
className="button is-success"
>
Edit
</button>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
nothing: "nothing"
}
}
export default compose(withRouter, connect(mapStateToProps))(Cards)
updatePost action
export const updatePost = (id, data, redirect) => {
return async dispatch => {
dispatch( { type: "UPDATING_POST_START" })
try {
const res = await axios.put(`http://localhost:3000/api/v1/posts/${id}/edit`, data)
dispatch({
type: "UPDATING_POST_SUCCESS",
data: res.data
})
redirect()
} catch(error) {
dispatch({
type: "UPDATING_POST_FAILURE",
data: { error: "Something went wrong"}
})
}
}
}
I'm not sure if my action is correct or not.
Here's the updatePost controller.
updatePost: async (req, res, next) => {
try {
const data = {
title: req.body.title,
description: req.body.description
}
const post = await Post.findByIdAndUpdate(req.params.id, data, { new: true })
if (!post) {
return res.status(404).json({ message: "No post found "})
}
return res.status(200).json({ post })
} catch(error) {
return next(error)
}
}
One mistake is that to set the current fields you need to use $set in mongodb , also you want to build the object , for example if req.body does not have title it will generate an error
updatePost: async (req, res, next) => {
try {
const data = {};
if(title) data.title=req.body.title;
if(description) data.description=req.body.description
const post = await Post.findByIdAndUpdate(req.params.id, {$set:data}, { new: true })
if (!post) {
return res.status(404).json({ message: "No post found "})
}
return res.status(200).json({ post })
} catch(error) {
return next(error)
}
}

Trouble with sending data from react to node

I'm trying to build a website where user post queries and other users are able to answer in it. I'm trying to implement various tags in it using react-tags-input module. However i'm having problem in sending those tags to node server. Help needed.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Link } from 'react-router-dom';
import '../Login.css';
import "./Home.css";
import Post from "./Post";
import { WithContext as ReactTags } from 'react-tag-input';
const KeyCodes = {
comma: 188,
enter: 13,
};
const delimiters = [KeyCodes.comma, KeyCodes.enter];
class query extends Component {
constructor() {
super();
this.state = {
userquery:'',
queries:[],
tags: [],
suggestions: [
{ id: "Javascript", text: "Javascript" },
{ id: "Java", text: "Java" },
{ id: 'node.js', text: 'node.js' },
{ id: 'react.js', text: 'react.js' },
{ id: 'express', text: 'express' },
{ id: 'bootstrap', text: 'bootstrap' },
{ id: 'python', text: 'python' },
{ id: 'c++', text: 'c++' }
]
};
this.handleDelete = this.handleDelete.bind(this);
this.handleAddition = this.handleAddition.bind(this);
this.handleDrag = this.handleDrag.bind(this);
}
onChange = (e) => {
const state = this.state
state[e.target.name] = e.target.value;
this.setState(state);
}
componentDidMount(){
fetch('/query').
then((Response)=>Response.json()).
then(data =>{
this.setState({queries:data.reverse()});
})
}
handleDelete(i) {
const { tags } = this.state;
this.setState({
tags: tags.filter((tag, index) => index !== i),
});
}
handleAddition(tag) {
this.setState(state => ({ tags: [...state.tags, tag] }));
}
handleDrag(tag, currPos, newPos) {
const tags = [...this.state.tags];
const newTags = tags.slice();
newTags.splice(currPos, 1);
newTags.splice(newPos, 0, tag);
// re-render
this.setState({ tags: newTags });
}
render() {
const {userquery } = this.state;
const { tags, suggestions } = this.state;
return (
<div class="container">
<form action="/queries" method="POST">
<h2 class="form-signin-heading" color="blue">Want to ask something? ask here!</h2>
<label for="inputQuery" class="sr-only">query</label>
<textarea type="text" class="form-control" placeholder="want to ask something? ask here!" name="userquery" required/>
<br/>
<div class="form-group ">
<input class="form-control" type="text" name="image" placeholder="image url"/>
</div>
<div class="form-group ">
<ReactTags
name='tags'
tags={tags}
suggestions={suggestions}
handleDelete={this.handleDelete}
handleAddition={this.handleAddition}
handleDrag={this.handleDrag}
delimiters={delimiters}
/>
</div>
<br/>
<button class="btn btn-lg btn-primary btn-block" >Ask</button>
</form>
<section>
<h2> Recent Posts</h2>
</section>
{this.state.queries.map((item, key) => {
return (<Post item={item} key={key} />)
}
)
}
</div>
);
}
}
export default query;
server file - I'm able to get userquery and image but req.body.tags is returning an empty object.
app.post("/queries",isLoggedIn,function(req,res){
var postQuery =req.body.userquery;
var userImages =req.body.image;
console.log(req.body.tags);
username=req.user.username;
var newQuery = {
name:username,
image:userImages,
description:postQuery
}
Query.create(newQuery,function(err,newlyCreated){
if(err){
console.log(err);
}
else{
res.redirect("http://localhost:3000/home");
}
})
// res.send("you hit the post route")
});
Edited
onSubmit = e => {
e.preventDefault()
const {someText, tags} = this.state
const data = {someText, tags: tags.map(x => x.id)}
alert(`Submitting: ${JSON.stringify(data)}`)
fetch(
'queries',
{
method: 'POST',
mode: "cors", // no-cors, cors, *same-origin
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
credentials: "same-origin",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
},
)
}
server.js
app.post("/queries",function(req,res){
console.log("tags are:-",req.body)
});
output
tags are:- {}
The problem is in react-tags storing selected tags as a bunch of span elements, not input. So, these values are not included in form data being submitted.
You have to handle form submit logic manually.
Here is an example of handlers you need to add/change:
onSubmit = e => {
e.preventDefault() // stop default form submission
const { field1, field2, tags } = this.state // select form input values to submit
const data = { field1, field2, tags: tags.map(t => t.id) } // create an object to submit
fetch(
'url',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
},
) // send POST request
}
onChange = e => this.setState({ [e.target.name]: e.target.value }) // for plain inputs
onAdd = tag => this.setState(state => ({ tags: [...state.tags, tag] })) // for tags input
render() {
return (
<form onSubmit={this.onSubmit}>
<input name="field1" onChange={this.onChange} />
<input name="field2" onChange={this.onChange} />
<ReactTags
tags={this.state.tags}
handleAddition={this.onAdd}
/>
<button type="submit">submit</button>
</form>
)
}
Working example is in this sandbox.
Result is here: https://prnt.sc/kg2j9p
Update: now codesandbox uses fetch and posts the form data, you can try to submit form and see outgoing request in network tab in browser dev tools.

Resources