State update not re-rendering in NextJS app - javascript

I know, I know, this question gets asked a hundred times a day, but none of the solutions have been working for me. I'm updating a variable using useState and it is not re-rendering any of my components.
I'm POSTing some data using the NextJS API Routing which updates a document in a MongoDB and returns the updated document. Once it's returned, it updates a state with the updated list. I've got a button that just console logs the list variable and it's being updated correctly, but it's not re-rendering anything when it gets updated.
// /components/AddNew.jsx
export default function AddNew({ user, list, setList }) {
// ...
fetch('/api/lists', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then((response) => {
if (response.status === 200) {
console.log(response.list);
setList([...response.list]);
setMessage(<div><strong>{name}</strong> has been added</div>);
} else {
setMessage(<div>Something went wrong, sorry</div>);
}
})
// ...
}
// /api/lists.js
import clientPromise from "../../lib/mongodb";
export default async function handler(req, res) {
const client = await clientPromise;
const db = client.db("MY_DATABASE");
switch (req.method) {
case "POST":
let userListExists = await db.collection("lists").findOne({user_id: req.body.user_id});
if (userListExists) {
const updateResponse = await db.collection("lists").findOneAndUpdate(
{ user_id: req.body.user_id },
{ $push: { list: req.body.list[0] } },
{ returnOriginal: false }
);
const updatedList = updateResponse.value.list;
res.json({status: 200, list: updatedList});
} else {
let newList = await db.collection("lists").insertOne(req.body);
res.json({status: 200, list: newList});
}
break;
case "GET":
const userID = req.query.user_id;
const userList = await db.collection("lists").findOne({user_id: userID});
res.json({ status: 200, data: userList });
break;
}
}
The document is being updated correctly, the response.list that gets console logged in AddNew.jsx is showing the full, updated list, but it's just not re-rendering. I'm certain I'm just missing something glaringly obvious but I just can't see what I'm doing wrong.
const [list, setList] = useState(false); are set in a parent component and passed down;
Edit: In the AddNew component I'm returning this:
return (
<div className="add-new">
<h2>Add new item</h2>
{message}
<form onSubmit={handleSubmit}>
<label>
<span>Name of activity</span>
<input className="name" type="text" name="name" value={name} onChange={(e) => {
setName(e.target.value);
}} />
</label>
<label>
<span>Categories (comma separated)</span>
<input className="categories" type="text" name="categories" value={rawCategories} onChange={(e) => {
setRawCategories(e.target.value.split(','));
}} />
</label>
<button type="submit">Submit</button>
</form>
</div>
);
and a parent that's being passed the list and setList from it's component looks like this:
import Result from "#/components/Result";
import RandomiseButton from "#/components/RandomiseButton";
import CategorySelector from "#/components/CategorySelector";
import { useState } from "react";
import AddNew from "./AddNew";
export default function Home({ user, list, setList }) {
const { _id, username } = user;
const [category, setCategory] = useState('all');
const [selectedItem, setSelectedItem] = useState(false);
let categories = [
'all',
];
if (list) {
console.log('has list');
list.forEach(element => {
element.categories.forEach(cat => {
if (!categories.includes(cat)) {
categories.push(cat);
}
});
console.log(element);
});
} else {
console.log('no list');
}
return (
<>
<div className="controls">
<CategorySelector
category={category}
categories={categories}
setCategory={setCategory}
list={list}
/>
<RandomiseButton
category={category}
setSelectedItem={setSelectedItem}
list={list}
/>
</div>
<Result selectedItem={selectedItem} list={list} />
<AddNew user={user} list={list} setList={setList} />
</>
)
}

Embarrassingly, the answer was as simple as a typo - apologies to anyone with this same issue finding this now...

Related

React When receiving data from the server, the type is known, but only response.data does not work

When receiving data from the server, the type is known, but only response.data does not work.
If I just take a response, it works fine, but when I take a response.data it doesn't work.
ErrorMessage : Object is of type 'unknown.'
import { UserState } from '../../lib/atom';
import React, { useEffect, useState } from 'react';
import { useRecoilState } from 'recoil';
import styled from 'styled-components';
import Resizer from 'react-image-file-resizer';
import CustomButton from '../../components/CustomButton';
import axios, { AxiosRequestHeaders, AxiosResponse } from 'axios';
import { getCookie } from '../../lib/cookie/cookie';
import { checkUsernameApi } from '../../apis/apiClient';
import useDebounce from '../../hooks/useDebounce';
const headers: AxiosRequestHeaders = {
Authorization: `Bearer ${getCookie('accessToken')}`,
};
function UserProfile() {
const [userState, setUserState] = useRecoilState(UserState);
const [imgText, setImgText] = useState('');
const [username, setUserName] = useState(userState.username);
const debounceUsername = useDebounce(username, 500);
const [exist, setExist] = useState(false);
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const revise_form = {
email: userState.email,
groupInfo: '39th',
profileImg: userState.profileImg,
username,
};
console.log('Revise_Form', revise_form);
const response = axios.put(
`http://localhost:8080/api/userprofile/${userState.username}`,
revise_form,
{ headers }
);
console.log('put response', response);
}
async function FetchingUserName() {
try {
const response = await checkUsernameApi(username);
return response;
} catch (err) {
return err;
}
}
useEffect(() => {
(async () => {
const response = await FetchingUserName();
console.log(response);
/* eslint-disable-next-line */
setExist(response.data);
})();
}, [debounceUsername]);
function fileChangedHandler(e: React.ChangeEvent<HTMLInputElement>) {
if (e.currentTarget.files !== null) {
const file = e.currentTarget.files[0];
setImgText(file.name);
Resizer.imageFileResizer(
file,
300,
300,
'JPEG',
100,
0,
(uri) => {
setUserState({ ...userState, profileImg: uri });
},
'base64',
200,
200
);
}
}
return (
<ProfileContainer>
<RevisionForm onSubmit={handleSubmit}>
<Title>UserProfile</Title>
<AvatarBox>
<Avatar src={userState.profileImg + ''} alt="avatar" />
</AvatarBox>
<FileBox>
<input
type={''}
placeholder="아바타를 업로드 해주세요."
disabled={true}
value={imgText}
/>
<label htmlFor="user_avatar">Upload</label>
<FileInput
id="user_avatar"
type="file"
onChange={fileChangedHandler}
/>
</FileBox>
<InputBox>
<label>email</label>
<InputEmail type={'text'} value={userState.email} disabled={true} />
</InputBox>
<InputBox>
<label>username</label>
<Input
type={'text'}
value={username}
onChange={(e) => {
setUserName(e.target.value);
}}
/>
</InputBox>
<CustomButton
height="4rem"
bgColor="#5de0e6"
color="white"
width="18rem"
weight="bold"
>
Revision
</CustomButton>
<ErrorText>{}</ErrorText>
</RevisionForm>
</ProfileContainer>
);
}
export default UserProfile;
I want to get response from server and save only response.data in setExist.
The response.data received from the server is a boolean type.
However, this does not work even if the type is specified in useState() .
Is there anything else I need to specify in this code?
enter image description here
I have tried the methods below.
useState<boolean>(false)
I put the Response type included in Axios.
useState<AxiosResponse>()
But the above methods didn't work.

How to grab (orderId) payload value in form body to update the status of the order?

I am working on mern stack e-commerce project . I have a order update route . Order status get updated only by the admin . While updating order status admin gets the default(preload) status . Then admin enter new status for updation .
When I enter (accept) into input fields and hit update button it shows this and status may not get updated .
I am not able to grab orderID .
Here is my update status backend controller
exports.updateStatus = (req, res) => { Order.updateOne(
{ _id: req.body.orderId },
{ $set: { status: req.body.status } },
{ new: true, useFindAndModify: false },
(err, order) => {
if (err) {
return res.status(400).json({ error: "Cannot update order status" });
}
res.json(order);
} );};
update order route
router.put(
"/order-update/:userId",
isSignedIn,
isAuthenticated,
isAdmin,
updateStatus
);
API handler for frontend
export const updateOrder = (userId, token, order) => {
return fetch(`${API}/order-update/${userId}`, {
method: "PUT",
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(order),
})
.then((response) => {
return response.json();
})
.catch((err) => console.log(err));
};
Form for updating status
import React, { useEffect, useState } from "react";
import { isAutheticated } from "../auth/helper";
import Base from "../core/Base";
import { getOrder, updateOrder } from "./helper/adminapicall";
const UpdateOrder = ({ match }) => {
const { user, token } = isAutheticated();
const [status, setStatus] = useState("");
const [error, setError] = useState(false);
const [success, setSuccess] = useState(false);
const preload = (orderId) => {
getOrder(orderId, user._id, token).then((data) => {
console.log(data);
if (data?.error) {
setError(data?.error);
} else {
setStatus(data.status);
setError("");
setSuccess("");
}
});
};
useEffect(() => {
preload(match.params.orderId);
}, []);
const handleChange = (event) => {
setError(false);
setStatus(event.target.value);
setSuccess(false);
};
const onSubmit = (e) => {
e.preventDefault();
setError("");
setSuccess("");
updateOrder(user._id, token, { status }).then((data) => {
if (data?.error) {
setError(true);
} else {
setError("");
setSuccess(true);
setStatus("");
}
});
};
const successMessage = () => (
<div
className="alert alert-success mt-3"
style={{ display: success ? "" : "none" }}
>
<h4>updation successfull</h4>
</div>
);
const warningMessage = () => (
<div
className="alert alert-success mt-3"
style={{ display: error ? "" : "none" }}
>
<h4>updation failedl</h4>
</div>
);
const orderUpdateForm = () => (
<form>
<div className="form-group col-md-6">
<p className="lead">Update Order Status</p>
<input
type="text"
className="form-control my-3 col-md-6"
onChange={handleChange}
value={status}
autoFocus
required
/>
<button onClick={onSubmit} className="btn btn-outline-info col-md-6">
Update Order
</button>
</div>
</form>
);
return (
//
<Base title="Update Order Status" description="">
{successMessage()}
{warningMessage()}
{orderUpdateForm()}
</Base>
//
);
};
export default UpdateOrder;
You are trying to fetch orderId from the body but you are not sending OrderId in the body. Also, I am not able to understand why you sen UserId in route "/order-update/:userId".
I think you can send orderId as request param from the front end also you can update your route as
router.put(
"/order-update/:orderId",
isSignedIn,
isAuthenticated,
isAdmin,
updateStatus
);
After that, you can get orderId as
let { orderId } = req.params;

React: re render componet after button click

How do I make page refresh or reender content in page after I click submit button? I've trying to put window.location.reload() (I know that not the React way, this.forceUpdate() have same result) in submit functions(closeTicket(), openTicketSubmit()) but POST request don't get response
OpenTickets.js
import React from "react";
import axios from "axios";
import CardConversation from './CardConversation.jsx';
export default class PersonList extends React.Component {
constructor(props) {
super(props);
this.state = {
people: [],
send_to_number: "",
message_body: ""
};
this.closeTicket = this.closeTicket.bind(this);
this.openTicketsReply = this.openTicketsReply.bind(this);
this.openTicketsSubmit = this.openTicketsSubmit.bind(this);
this.getPhoneNumberOpenTickets = this.getPhoneNumberOpenTickets.bind(this);
}
openTicketsReply = async e => {
this.setState({
[e.target.name]: e.target.value
});
};
getPhoneNumberOpenTickets = async e => {
this.setState({
send_to_number: e
});
};
openTicketsSubmit = async e => {
e.preventDefault();
const formData = new FormData();
formData.set("send_to_number", this.state.send_to_number.slice(1));
formData.set("message_body", this.state.message_body);
axios({
method: "post",
url: "/outgoingsms",
data: formData,
headers: { "Content-Type": "multipart/form-data" }
})
};
closeTicket = async e => {
e.preventDefault();
const formData = new FormData();
formData.set("customernum", this.state.send_to_number.slice(1));
axios({
method: "post",
url: "/closeticket",
data: formData,
headers: { "Content-Type": "multipart/form-data" }
})
};
componentDidMount() {
this.getPeopleData();
}
getPeopleData = async () => {
try {
const { data } = await axios.get(`/getongoing?limit=10`);
this.setState({ people: data });
} catch (e) {
console.log("error: ", e);
}
};
render() {
const {
closeTicket,
openTicketsSubmit,
getPhoneNumberOpenTickets,
openTicketsReply
} = this;
return this.state.people.map(person => (
<CardConversation
person={person}
closeTicket={closeTicket}
openTicketsSubmit={openTicketsSubmit}
getPhoneNumberOpenTickets={getPhoneNumberOpenTickets}
openTicketsReply={openTicketsReply}
/>
));
}
}
CardConversation.jsx
import React, { useCallback, useEffect, useState } from "react";
import { Button, Accordion, Card, Form, Row, Col } from "react-bootstrap";
import axios from "axios";
const CardConversation = ({
person,
closeTicket,
openTicketsSubmit,
getPhoneNumberOpenTickets,
openTicketsReply,
}) => {
const [conversation, setConversation] = useState([]);
// Handlers
const handleSubmit = useCallback(
e => {
openTicketsSubmit(e);
},
[openTicketsSubmit]
);
const handleCloseTicket = useCallback(
e => {
closeTicket(e);
},
[closeTicket],
);
const handleClick = useCallback(() => {
getPhoneNumberOpenTickets(person);
},
[person, getPhoneNumberOpenTickets]);
const handleChange = useCallback(
e => {
openTicketsReply(e);
},
[openTicketsReply]
);
// Methods
const fetchConversation = useCallback(async () => {
try {
const { data } = await axios.get(
"/getconvfornum?customer_number=" + person.slice(1)
);
setConversation(data);
} catch (e) {
console.log("error: ", e);
}
}, [person, conversation]);
// Effects
useEffect(() => {
fetchConversation(person)
}, [person]);
return (
<Accordion defaultActiveKey="0">
<Card>
<Card.Header>
<Accordion.Toggle as={Button} variant="button" eventKey="0">
Conversation {person.indexOf(person)+1+ ' '}
Phone number: {person}
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>
{conversation.map(message => (
<div>
<p>{message.from}</p>
<p>{message.body}</p>
</div>
))}
<Form onSubmit={handleSubmit}>
<br />
<Form.Group as={Row} controlId="formPlaintextPassword">
<Col sm="10">
<Form.Control
type="text"
placeholder="Reply"
name="message_body"
onChange={handleChange}
/>
</Col>
<Button type={"submit"}
onClick={handleClick} column sm="2">
Reply
</Button>
</Form.Group>
</Form>
<Form onSubmit={handleCloseTicket}>
<Form.Group>
<Col sm="11">
<Button type={"submit"}
onClick={handleClick} column sm="4">
Close Ticket
</Button>
</Col>
</Form.Group>
</Form>
</Card.Body>
</Accordion.Collapse>
</Card>
<br />
</Accordion>
);
};
export default CardConversation;
A simple way to re-render would be to change the state variable on the submission of the Axios request causing the component to re-render automatically.
Example:
axios({...}).then(resp => {
this.setState({message_body:'',send_to_number:''}); // will cause to re-render
})
You can make the component re-render by updating its state ( after the POST ) :
closeTicket = async e => {
e.preventDefault();
const formData = new FormData();
formData.set("customernum", this.state.send_to_number.slice(1));
axios({
method: "post",
url: "/closeticket",
data: formData,
headers: { "Content-Type": "multipart/form-data" }
})
.then(() => {
this.setState({ /* */ })
// or
// this.forceUpdate();
})
};
React will rerender components once the state changes, which means that what you need is to change the state once the submit button is clicked. Since the submit button is inside the PersonList component and you also want to reload PersonList, you want to change the state of PersonList when the submit button is clicked.
Here is what you might want to do:
1) add a 'reload' state to PersonList, defaulting it to false. This will tell the component if you need to reload or not.
2) pass a function that sets the state of PersonList's reload value into the child component, in this case CardConversion. something like this.setState({reload:!this.state.reload}) should do.
3) when you finish handling what you need to handle within CardConversion, call your passed function to set the parent's state value, and the whole component should reload.
this.state = {
reload: false
...
}
...
shouldReload() {
this.setState({reload:!this.state.reload});
}
...
<CardConversation
person={person}
closeTicket={closeTicket}
openTicketsSubmit={openTicketsSubmit}
getPhoneNumberOpenTickets={getPhoneNumberOpenTickets}
openTicketsReply={openTicketsReply}
reloadParent={this.shouldReload.bind(this)}
/>
and on CardConversation
const handleClick = useCallback(() => {
getPhoneNumberOpenTickets(person);
this.props.reloadParent();
},
in opentickets.js
create a function updatePeople and in that function call this.getPeopleData();
eg
updatePeople = () =>{
this.getPeopleData();
}
then in
return this.state.people.map(person => (
<CardConversation
person={person}
closeTicket={closeTicket}
openTicketsSubmit={openTicketsSubmit}
getPhoneNumberOpenTickets={getPhoneNumberOpenTickets}
openTicketsReply={openTicketsReply}
refreshPeople = {this.updatePeople}
/>
));
in cardConversion.jsx
when you click the close button or whichever button gets you back to openTickets, put the callback function
this.props.refreshPeople();
because you have componentDidMount, everytime you call whatever is in componentDidMount, it will update the information and re render it
First of all, regarding you are not getting any data issue, you can check axios, and how they use post:
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
// where you can setState here
console.log(response);
})
.catch(function (error) {
console.log(error);
});
Mainly axios are handling the data asynchronously. By that, once you call the api, React will executes next line of code.
For more discussion of how to force update the component, you can check this post: Can you force a React component to rerender without calling setState?, which explain how to update component very well.
As far as I can see, what you're trying to do is reload the people list. If that's the case, you can solve it in two ways:
In both axios API calls, add .then() block and call this.getPeopleData().
Instead of re-fetching people's data, you get the added/deleted post data and update state in the .then() block with setState().
I suggest you to adopt Option 2, because fetching the list again will require more time for you to get the refreshed list.
Either way, just adding this.forceUpdate() to your .then() block will not give you the updated list. It won't do anything actually to the UI. (though it makes it re-render)
CardConversatio.jsx
import React, { useCallback, useEffect, useState } from "react";
import { Button, Accordion, Card, Form, Row, Col } from "react-bootstrap";
import axios from "axios";
const CardConversation = ({
person,
closeTicket,
openTicketsSubmit,
getPhoneNumberOpenTickets,
openTicketsReply,
getPhoneToCloseTicket,
}) => {
const [conversation, setConversation] = useState([]);
const [trigger, fireUpdate] = useState(false);
// Handlers
const renderConversation = useCallback(() => {
return conversation.map(message => (
<div key={message.date.$date + person}>
<p>{message.from}</p>
<p>{message.body}</p>
</div>
));
}, [conversation, person]);
const fetchConversation = useCallback(async () => {
try {
const { data } = await axios.get(
"/getconvfornum?customer_number=" + person.slice(1)
);
setConversation(data);
console.log("fetch ", data);
} catch (e) {
console.log("error: ", e);
}
}, [person]);
const handleClick = useCallback(async () => {
await getPhoneNumberOpenTickets(person);
setTimeout(() => fetchConversation(person), 500);
}, [getPhoneNumberOpenTickets, person, fetchConversation]);
const handleClickClose = useCallback(async () => {
await getPhoneToCloseTicket(person);
}, [person, getPhoneToCloseTicket]);
const handleChange = useCallback(
e => {
openTicketsReply(e);
},
[openTicketsReply]
);
useEffect(() => {
console.log("effect");
fetchConversation(person);
}, [fetchConversation, person]);
return (
<Accordion defaultActiveKey="0">
<Card>
<Card.Header>
<Accordion.Toggle as={Button} variant="button" eventKey="0">
Conversation {person.indexOf(person) + 1 + " "}
Phone number: {person}
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>
{renderConversation()}
<Form>
<br />
<Form.Group as={Row} controlId="formPlaintextPassword">
<Col sm="10">
<Form.Control
type="text"
placeholder="Reply"
name="message_body"
onChange={handleChange}
/>
</Col>
<Button onClick={handleClick} column sm="2">
Reply
</Button>
</Form.Group>
</Form>
<Form>
<Form.Group>
<Col sm="11">
<Button onClick={handleClickClose} column sm="4">
Close Ticket
</Button>
</Col>
</Form.Group>
</Form>
</Card.Body>
</Accordion.Collapse>
</Card>
<br />
</Accordion>
);
};
export default CardConversation;
OpenTickets.js
import React from "react";
import axios from "axios";
import CardConversation from './CardConversation.jsx';
export default class PersonList extends React.Component {
constructor(props) {
super(props);
this.state = {
people: [],
send_to_number: "",
message_body: "",
closed: false
};
this.closeTicket = this.closeTicket.bind(this);
this.openTicketsReply = this.openTicketsReply.bind(this);
this.openTicketsSubmit = this.openTicketsSubmit.bind(this);
this.getPhoneNumberOpenTickets = this.getPhoneNumberOpenTickets.bind(this);
this.getPhoneToCloseTicket = this.getPhoneToCloseTicket.bind(this);
}
openTicketsReply = async e => {
e.preventDefault();
this.setState({
message_body: e.target.value
});
};
getPhoneNumberOpenTickets = async e => {
//e.preventDefault();
this.setState({
send_to_number: e
}, async () => await this.openTicketsSubmit());
};
getPhoneToCloseTicket = async e => {
this.setState({
send_to_number: e
}, async () => this.closeTicket());
};
openTicketsSubmit = async e => {
const formData = new FormData();
formData.set("send_to_number", this.state.send_to_number.slice(1));
formData.set("message_body", this.state.message_body);
axios({
method: "post",
url: "/outgoingsms",
data: formData,
headers: { "Content-Type": "multipart/form-data" }
}).then(resp => {
this.setState({ closed: true });
}).catch(error => console.log(error))
};
closeTicket = async e => {
const formData = new FormData();
formData.set("customernum", this.state.send_to_number.slice(1));
axios({
method: "post",
url: "/closeticket",
data: formData,
headers: { "Content-Type": "multipart/form-data" }
}).then(resp => {
this.setState({ closed: true });
}).catch(error => console.log(error))
};
componentDidMount() {
this.getPeopleData();
}
getPeopleData = async () => {
try {
const { data } = await axios.get(`/getongoing?limit=10`);
this.setState({ people: data });
} catch (e) {
console.log("error: ", e);
}
};
render() {
const {
closeTicket,
getPhoneNumberOpenTickets,
openTicketsReply,
getPhoneToCloseTicket
} = this;
return this.state.people.map(person => (
<CardConversation
key={person}
person={person}
closeTicket={closeTicket}
getPhoneNumberOpenTickets={getPhoneNumberOpenTickets}
openTicketsReply={openTicketsReply}
getPhoneToCloseTicket={getPhoneToCloseTicket}
/>
));
}
}

Uploading images along with other text inputs in React?

It's extremely weird that all the tutorials I've found online show how to upload an image, but do not show how to do it with other text inputs included.
Thus, I've hit a roadblock trying to upload images, as well as other textual data in the same form. Spent hours searching on SO and Google, but couldn't find anything that fit my situation.
I'm using React & Redux, and the express-fileupload package for file uploads.
Anyway, here's what I've tried:
Backend
campgroundRoutes.js
const express = require('express');
const router = express.Router();
const fileUpload = require('express-fileupload');
router.use(fileUpload());
const Campground = require(`../../models/campground`);
const checkAuth = require('../../middleware/check-auth');
router.post('/', checkAuth, (req, res, next) => {
const file = req.files.file;
console.log('req.files: ', req.files); // req.files is undefined
uploadPath = './assets/uploadedImages/' + file.name;
file.mv(uploadPath, err => {
if (err) {
console.error('Error: ', err);
return res
.status(500)
.json({ error: err, message: 'Failed to upload file' });
}
res.json({ fileName: file.name });
});
const campgroundToPost = new Campground({
title: req.body.title,
description: req.body.description,
cost: req.body.cost,
imageName: file.name,
_author: {
id: req.userData.userId,
firstName: req.userData.firstName
}
});
campgroundToPost
.save()
.then(result => res.status(200).json({ campground: result }))
.catch(err => res.status(400).send(`Failed to add campground, ${err}`));
});
Frontend
addCampground.js
import React, { Component } from 'react';
import TextField from '#material-ui/core/TextField';
import Card from '#material-ui/core/Card';
import CardContent from '#material-ui/core/CardContent';
import Typography from '#material-ui/core/Typography';
import Button from '#material-ui/core/Button';
import '../../styles/addCampground.css';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
actionAddCampground,
getCampgroundDetails
} from './actions/campgroundActions';
class AddCampground extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
description: '',
cost: '',
selectedImage: null
};
}
handleChange = e => {
const { name, value } = e.target;
this.setState({
[name]: value
});
};
uploadImage = e => {
this.setState(
{
selectedImage: e.target.files[0]
},
() => console.log(this.state.selectedImage)
);
};
addCampground = () => {
const title = this.state.title;
const description = this.state.description;
const cost = this.state.cost;
const data = new FormData();
data.append('file', this.state.selectedImage);
this.props
.actionAddCampground({
title,
description,
cost,
data
})
.then(res => console.log(res))
.catch(err => console.log('Error: ', err));
this.props.history.push('/home');
};
render() {
return (
<Card className="add-campground-card">
<CardContent>
<Typography
style={{ fontWeight: '400' }}
className="text-center"
variant="h6"
component="h6">
Add Your Campground
</Typography>
</CardContent>
<TextField
autoComplete="off"
name="title"
className="textfield"
label="Campground name"
variant="outlined"
value={this.state.title}
onChange={e => this.handleChange(e)}
/>
<TextField
autoComplete="off"
name="description"
className="textfield"
label="Campground description"
variant="outlined"
value={this.state.description}
onChange={e => this.handleChange(e)}
/>
<TextField
autoComplete="off"
name="cost"
className="textfield"
type="number"
label="Campground cost"
variant="outlined"
value={this.state.cost}
onChange={e => this.handleChange(e)}
/>
<input onChange={this.uploadImage} type="file" name="file" />
<Button
className="add-campground"
variant="contained"
color="primary"
onClick={this.addCampground}>
Add Campground
</Button>
</Card>
);
}
}
const mapStateToProps = state => {
return {
campground: state.campgroundList.singleCampground
};
};
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
actionAddCampground,
getCampgroundDetails
},
dispatch
);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddCampground);
campgroundActions.js
// Only including add campground action here as only that's relevant
import {
ADD_CAMPGROUND,
} from '../actionTypes/types';
import axios from 'axios';
import { authHeader } from '../../../helpers/auth-header';
const API_URL = `http://localhost:5000/api`;
export const actionAddCampground = campground => {
return async dispatch => {
try {
const res = await axios.post(`${API_URL}/campgrounds`, campground, {
headers: authHeader()
});
dispatch({
type: ADD_CAMPGROUND,
payload: res
});
return res.data;
} catch (err) {
return console.log(err);
}
};
};
authHeader.js
export const authHeader = () => {
let user = JSON.parse(localStorage.getItem('user'));
let token = localStorage.getItem('token');
if (user && token) {
return {
Authorization: token
};
} else {
return {};
}
};
This is error I get in the backend:
TypeError: Cannot read property 'file' of undefined
I cannot figure out where I'm going wrong.
A few days back Even I was facing the same problem where I have to send the file and another Input field Eventually this below code helped me while making an API call see if it is any help to you
//-------------To insert Documents and InputField--------------//
export function documentsInsert( documentName, document ) {
var formData = new FormData();
formData.append("documentName", documentName)
formData.append("document", document)
return request2({
url: xyzzz,
method: 'POST',
body: formData
});
}

JavaScript React Redux: Cannot Delete Item on Web Page Without Refreshing First

I have the current web page below and want to delete a user when I click on the red 'x' button at the top of the card.
Currently, after I click the 'x' delete button, nothing happens. After I refresh the page, the user will be removed from my webpage. But I want this to happen without needing a refresh at all.
Sample Web Page Render:
Back-end Route:
To achieve this, I tried the following:
Setup back-end route:
const router = require('express').Router()
const {User} = require('../db/models')
module.exports = router
const {isUser, isAdmin} = require('../checks')
// 8080/api/users/:userId
router.delete('/:userId', isAdmin, async (req, res, next) => {
let userId = req.params.userId
try {
await User.destroy({
where: {
id: userId
}
})
res.status(204)
} catch (err) {
next(err)
}
})
Setup redux store and thunk:
import axios from 'axios'
// ACTION TYPES
const SET_USERS = 'SET_USERS'
const DELETE_USER = 'DELETE_USER'
// ACTION CREATORS
export const setUsers = users => ({
type: SET_USERS,
users: users
})
export const deleteUser = delUserId => ({
type: DELETE_USER,
delUserId: delUserId
})
// DOLLAR HELPER FOR CENTS FIELD
// export const toDollars = cents => {
// return `$${(cents / 100).toFixed(2)}`
// }
// THUNK CREATORS
export const getUsers = () => async dispatch => {
try {
const {data} = await axios.get('/api/users')
dispatch(setUsers(data))
console.log('getUsersThunk DATA ARRAY', data)
} catch (err) {
console.error(err)
}
}
export const deleteUserThunk = delUserId => async dispatch => {
try {
const response = await axios.delete(`/api/users/${delUserId}`)
const deleteUserId = response.data
dispatch(deleteUser(deleteUserId))
console.log('getUsersThunk DELETE', deleteUserId)
} catch (err) {
console.error(err)
}
}
// REDUCER
// INITIAL STATE
const allUsers = []
export default function(state = allUsers, action) {
switch (action.type) {
case SET_USERS:
return action.users
case DELETE_USER: {
let userRemovalArray = state.filter(user => user.id !== action.delUserId)
return userRemovalArray
}
default:
return state
}
}
Build front-end component that calls 'deleteUserThunk'
import React from 'react'
import {connect} from 'react-redux'
import {getUsers, deleteUserThunk} from '../store/allUsers'
import {updateUserThunk, fetchSingleUser} from '../store/singleUser'
// Status Filter import BeerFilter from './BeerFilter'
import Card from 'react-bootstrap/Card'
import Button from 'react-bootstrap/Button'
import {UncontrolledCollapse} from 'reactstrap'
export class AllUsers extends React.Component {
constructor(props) {
super(props)
this.state = {
showForm: false,
stat: ''
}
this.clickHandlerOne = this.clickHandlerOne.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
componentDidMount() {
try {
this.props.fetchInitialUsers()
} catch (error) {
console.error(error)
}
}
clickHandlerOne() {
let hidden = this.state.showForm
this.setState({
showForm: !hidden
})
}
handleChange(event) {
//console.log('event.target', event.target)
this.setState({
[event.target.name]: event.target.value
})
}
async handleSubmit(userId) {
event.preventDefault()
const updatedUser = {
id: userId,
isAdmin: this.state.stat
}
// console.log('UPDATE USER', updatedUser)
await this.props.updateUserThunk(updatedUser)
this.props.fetchInitialUsers()
}
render() {
const users = this.props.users
// console.log('PROPS', this.props)
console.log('USERS', this.props.users)
return (
<div>
{/* <div className="options">
<select onChange={this.handleChange}>
<option value="">Sort By...</option>
<option value="priceHighToLow">Price (high to low)</option>
<option value="priceLowToHigh">Price (low to high)</option>
<option value="name">Name</option>
</select>
<BeerFilter />
</div> */}
<div className="flex-cards">
{users.map(user => (
<Card style={{width: '18rem'}} key={user.id}>
{/* delete thunk */}
<div>
<Button
id={`delete${user.id}`}
variant="danger"
onClick={() => this.props.deleteUserThunk(user.id)}
>
X
</Button>
</div>
<Card.Body>
<Card.Title>User Id: {user.id}</Card.Title>
<Card.Text>
<div>
<ul>
<li>
<div className="highlight">
<img src={user.imageUrl} />
</div>
<div className="details">
<p>Username: {user.username}</p>
<p>User Email: {user.email}</p>
<p>Admin Status: {user.isAdmin ? 'true' : 'false'}</p>
<p>
Created Date:{' '}
{new Intl.DateTimeFormat('en-GB', {
month: 'short',
day: '2-digit',
year: 'numeric'
}).format(new Date(user.createdAt))}
</p>
<p />
<Button
id={`user${user.id}`}
onClick={() => {
this.clickHandlerOne()
}}
variant="outline-info"
>
Admin Status Toggle
</Button>
<UncontrolledCollapse toggler={`#user${user.id}`}>
<form onSubmit={() => this.handleSubmit(user.id)}>
<div>
<span>
<select
name="stat"
value={this.state.isAdmin}
onChange={this.handleChange}
>
<option value="">user isAdmin?</option>
<option value="true">true</option>
<option value="false">false</option>
</select>
</span>
<div>
{/* */}
<button type="submit">Submit</button>
</div>
</div>
</form>
</UncontrolledCollapse>
</div>
</li>
</ul>
</div>
</Card.Text>
</Card.Body>
</Card>
))}
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
users: state.allUsers
}
}
const mapDispatchToProps = dispatch => {
return {
loadSingleUser: id => dispatch(fetchSingleUser(id)),
updateUserThunk: updatedUser => dispatch(updateUserThunk(updatedUser)),
//getSortedBeers: (sortBy, beers) => dispatch(sortBeers(sortBy, beers)),
fetchInitialUsers: () => dispatch(getUsers()),
deleteUserThunk: userId => dispatch(deleteUserThunk(userId))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AllUsers)
With my code above, when I click on the red 'x' button nothing happens. I have to hit the refresh button for my now deleted user to be removed from my webpage.
How can I have the user removed from my current view without having to hit refresh?
This isn't a complete answer, but there's definitely a problem here:
const response = await axios.delete(`/api/users/${delUserId}`)
If you look at the screenshot you provided of the error in the web console, it's showing undefined where delUserId should be. So somewhere along the line between the click on the 'X' and the line above, you aren't passing the user ID correctly.
Here, as you are using mapDispatchToProps in your component "AllUsers"
your deleteUserThunk(in THUNK CREATORS) is assigned to deleteUserThunk(in your component "AllUsers")
hence, you need to call your THUNK CREATEORS function by calling the component function which is assigned in mapDispatchToProps
You have to call it in the following way
onClick={() => this.props.deleteUserThunk(user.id)}
This will pass your user.id to deleteUserThunk(in your component "AllUsers") to deleteUserThunk(in THUNK CREATORS)
As per your comments..
Firstly, you have to remove it from componentDidMount() because, you should not run your delete function when your component is mounted.
Secondly, if your reducer is updated, then your browser will update without any refresh. Try checking the parameters which are passed into DELETE_USER
my suggestion would be:
in your function deleteUserThunk (in THUNK CREATORS)
replace dispatch(deleteUser(deleteUserId)) with
dispatch(deleteUser(delUserId))

Categories

Resources