React: re render componet after button click - javascript

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

Related

State update not re-rendering in NextJS app

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...

Autocomplete data in input

I want to display data from the autocomplete in the input as indicated below:
Autocomplete function
When i'm trying to do this i get an error:
×
TypeError: Cannot read property 'setState' of undefined
onSelect
94 | onSelect={ value => this.setState({ value }) }
I'm stuck on this and i'm probably doing it wrong. Hopefully someone can help me because i've tried everything i know and just cant see the problem. So please help me :)
privateMovie.js
import React, { useState, useEffect, setState } from "react";
import Layout from "../core/Layout";
import axios from "axios";
import { isAuth, getCookie, signout, updateUser } from "../auth/helpers";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.min.css";
import Autocomplete from "react-autocomplete";
import { MoviesData, renderMovieTitle } from "./movie-data";
const Private = ({ history }) => {
const [values, setValues ] = useState({
value: "",
suggestions: [],
movie: "",
buttonText: "Submit"
});
const token = getCookie("token");
useEffect(() => {
loadProfile();
}, []);
const loadProfile = () => {
axios({
method: "get",
url: `${process.env.REACT_APP_API}/user/${isAuth()._id}`,
headers: {
Authorization: `Bearer ${token}`
}
})
.then(response => {
console.log("PRIVATE PROFILE UPDATE", response);
const { movie } = response.data;
setValues({ ...values, movie });
})
.catch(error => {
console.log("PRIVATE PROFILE UPDATE ERROR", error.response.data.error);
if (error.response.status === 401) {
signout(() => {
history.push("/");
});
}
});
};
const { movie, buttonText } = values;
const handleChange = value => event => {
// console.log(event.target.value);
setValues({ ...values, [value]: event.target.value });
};
const clickSubmit = event => {
event.preventDefault();
setValues({ ...values, buttonText: "Submitting" });
axios({
method: "POST",
url: `${process.env.REACT_APP_API}/movie/create`,
headers: {
Authorization: `Bearer ${token}`
},
data: { movie }
})
.then(response => {
console.log("PRIVATE PROFILE UPDATE SUCCESS", response);
updateUser(response, () => {
setValues({ ...values, buttonText: "Submitted" });
toast.success("Profile updated successfully");
});
})
.catch(error => {
console.log("PRIVATE PROFILE UPDATE ERROR", error.response.data.error);
setValues({ ...values, buttonText: "Submit" });
toast.error(error.response.data.error);
});
};
const updateForm = () => (
<form>
<div className="form-group">
<label className="text-muted">AUTOCOMPLETE</label>
<Autocomplete
type="text"
getItemValue={item => item.title}
items={MoviesData()}
shouldItemRender={renderMovieTitle}
renderItem={(item, isHighlighted) => (
<div style={{ background: isHighlighted ? "lightgray" : "white" }}>
{item.title}
</div>
)}
onChange={(event, value) => this.setState({ value }) }
onSelect={ value => this.setState({ value }) }
/>
<input
onChange={handleChange("movie")}
value={movie}
type="text"
className="form-control"
/>
</div>
<div>
<button className="btn btn-primary" onClick={clickSubmit}>
{buttonText}
</button>
</div>
</form>
);
return (
<Layout>
<div className="col-md-6 offset-md-3">
<ToastContainer />
<h1 className="pt-5 text-center"></h1>
<p className="lead text-center"></p>
{updateForm()}
</div>
</Layout>
);
};
export default Private;
I think you mixed it up.
Instead you call this.setState()
call your destructed function setValues()
wich you have declared in the beginning of your component function
const [values, setValues ] = useState({
value: "",
suggestions: [],
movie: "",
buttonText: "Submit"
});
And if you use "useState" and destruct it in "getValues" and "setValues" you can get rid of setState in your import.
See the docs:
https://reactjs.org/docs/hooks-state.html

I keep getting Can't perform a React state update on an unmounted component

I keep getting this error for two of my React components and i can't figure it out. I just started to learn to use hooks and I can't seem to fix this error message.
I tried searching online and going through a few suggested methods but it doesn't seem to work for me.
I saw an article saying to add this code below but it has no effect at all.
let mounted = true
return function cleanup() {
mounted = false
}
Here's the two errors:
"index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in BoardAdmin (at App.js:125)
in component (at PrivateRoute.js:9)"
"Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in SFTPList (at CsvExtractor.js:206)
in div (created by Col)
in Col (at CsvExtractor.js:205)
in div (created by Row)
in Row (at CsvExtractor.js:183)
in form (created by Form)
in Form (at CsvExtractor.js:129)
in CsvExtractor (at BoardAdmin.js:30)
in header (at BoardAdmin.js:29)
in div (at BoardAdmin.js:28)
in BoardAdmin (at App.js:125)
in component (at PrivateRoute.js:9)"
csvextractor.js
import React, { useState, useEffect, useRef } from 'react';
import Dropzone from 'react-dropzone';
import MultiSelect from 'react-multiple-select-dropdown-lite';
import 'react-multiple-select-dropdown-lite/dist/index.css';
import 'react-datepicker/dist/react-datepicker.css';
import DatePicker from 'react-datepicker';
import SFTPList from './SFTPDownload';
import { Form, Row, Col, Button } from 'react-bootstrap';
import FileService from '../services/file.service';
import { history } from '../helpers/history';
const CsvExtractor = () => {
const [file, setFile] = useState(null); // state for storing actual file
const [details, setDetails] = useState({
title: '',
description: '',
});
const [errorMsg, setErrorMsg] = useState('');
const dropRef = useRef(); // React ref for managing the hover state of droppable area
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(null);
const [filters, setFilters] = useState([]);
const filterOptions = [
{
value: 'translate',
label: 'Translate to EN',
},
{
value: 'emailentrant',
label: 'Email Entrant',
},
{
value: 'emailsortant',
label: 'Email Sortant',
},
{
value: 'appelentrant',
label: 'Appel Entrant',
},
{
value: 'appelsortant',
label: 'Appel Sortrant',
},
{
value: 'chat',
label: 'Chat',
},
];
const handleSelect = (selections) => {
setDetails({
...details,
description: `${selections}`,
});
setFilters(selections.split(','));
};
const handleInputChange = (event) => {
setDetails({
...details,
[event.target.name]: event.target.value,
});
};
const onDrop = (files) => {
const [uploadedFile] = files;
setFile(uploadedFile);
const fileReader = new FileReader();
fileReader.onload = () => {};
fileReader.readAsDataURL(uploadedFile);
dropRef.current.style.border = '2px dashed #e9ebeb';
};
const onChange = (dates) => {
const [start, end] = dates;
const tdate = end === null ? '' : end.toString().slice(4, 15);
setDetails({
...details,
title: `${start.toString().slice(4, 15)} - ${tdate}`,
});
setStartDate(start);
setEndDate(end);
};
const updateBorder = (dragState) => {
if (dragState === 'over') {
dropRef.current.style.border = '2px solid #000';
} else if (dragState === 'leave') {
dropRef.current.style.border = '2px dashed #e9ebeb';
}
};
const handleOnSubmit = async (event) => {
event.preventDefault();
try {
const { title, description } = details;
if (title.trim() !== '' && description.trim() !== '') {
if (file) {
const formData = new FormData();
formData.append('startDate', startDate);
formData.append('endDate', endDate);
formData.append('filters', filters);
formData.append('file', file);
formData.append('title', title);
formData.append('description', description);
setErrorMsg('');
await FileService.uploadFile(formData);
history.push('/list');
} else {
setErrorMsg('Please select a file to add.');
}
} else {
setErrorMsg('Please enter all the field values.');
}
} catch (error) {
error.response && setErrorMsg(error.response.data);
}
};
return (
<React.Fragment>
<Form className="search-form">
{errorMsg && <p className="errorMsg">{errorMsg}</p>}
<Row>
<Col>
<Form.Group controlId="title">
<Form.Control
type="text"
name="title"
value={details.title || ''}
placeholder="Enter title"
onChange={handleInputChange}
/>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<Form.Group controlId="description">
<Form.Control
type="text"
name="description"
value={details.description || ''}
placeholder="Enter description"
onChange={handleInputChange}
/>
</Form.Group>
</Col>
</Row>
<Row>
<Col>
<div>
<h5>Select date range</h5>
</div>
<DatePicker
selected={startDate}
onChange={onChange}
startDate={startDate}
endDate={endDate}
selectsRange
inline
/>
</Col>
<Col>
<h5>Select filters (at least one)</h5>
<MultiSelect
style={{ backgroundColor: 'white', marginTop: '10px' }}
onChange={handleSelect}
options={filterOptions}
/>
</Col>
</Row>
<Row>
<Col xs={12}>
{/* <div className="upload-section"> */}
<Dropzone
onDrop={onDrop}
onDragEnter={() => updateBorder('over')}
onDragLeave={() => updateBorder('leave')}
>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps({ className: 'drop-zone' })} ref={dropRef}>
<input {...getInputProps()} />
<p>Drag and drop a file OR click here to select a file</p>
{file && (
<div>
<strong>Selected file:</strong> {file.name}
</div>
)}
</div>
)}
</Dropzone>
{/* </div> */}
</Col>
<Col>
<SFTPList />
</Col>
</Row>
<Button variant="primary" type="submit" onClick={handleOnSubmit}>
Submit
</Button>
</Form>
</React.Fragment>
);
};
export default CsvExtractor;
adminboard.js is below
import React, { useState, useEffect, useRef } from 'react';
import 'react-multiple-select-dropdown-lite/dist/index.css';
import 'react-datepicker/dist/react-datepicker.css';
import UserService from '../services/user.service';
import CsvExtractor from './CsvExtractor';
const BoardAdmin = () => {
const [content, setContent] = useState('');
useEffect(() => {
UserService.getAdminBoard().then(
(response) => {
setContent(response.data);
},
(error) => {
const _content =
(error.response && error.response.data && error.response.data.message) ||
error.message ||
error.toString();
setContent(_content);
},
);
}, []);
return (
<div className="container">
<header className="jumbotron">
<CsvExtractor />
</header>
</div>
);
};
export default BoardAdmin;
If fixed it with the code below
const [filesList, setFilesList] = useState([]);
const [errorMsg, setErrorMsg] = useState('');
useEffect(() => {
const source = axios.CancelToken.source();
const getFilesList = async () => {
try {
const { data } = await axios.get('api/file/getallfiles/', {
headers: authHeader(),
cancelToken: source.token,
});
setErrorMsg('');
setFilesList(data);
} catch (error) {
if (axios.isCancel(error)) {
} else {
error.response && setErrorMsg(error.response.data);
}
}
};
getFilesList();
return () => {
source.cancel();
};
}, []);

Warning: Can't perform a React state update on an unmounted component

I'm getting the following warning:
"Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in AddComment (at CommentCard.js:50)
in div (created by Comment" (line 50 from CommentCard is the line where the AddComment component is)
I have the CommentCard component which displays a comment with the help of the Comment component from ant design. I use the children property of the Comment component in order to display the AddComment component for a specific comment.
The AddComment component adds a reply to a comment.
To display the AddComment component for the corresponding comment I'm using an array of states and I display the component only for the comments that have the state equal with 1.
After I add a reply I want the AddComment component to be removed. To do this I change the state of the comment after the reply was added successfully. I'm getting the warning right after I post a reply.
Here's my CommentCard component
function CommentCard(props) {
const [hasReplyCommentBox, setHasReplyCommentBox] = useState([]);
const { t } = useTranslation();
const commentStyle = {
padding: '10px',
backgroundColor: 'white',
'whiteSpace': 'pre',
width: '100%'
};
function toggleReplyBoxVisibility(commentIndex) {
var auxState = { ...hasReplyCommentBox };
auxState[commentIndex] = auxState[commentIndex] ? 0 : 1;
setHasReplyCommentBox(auxState);
}
const actions = [
<span
id={"reply-button-" + props.commentIndex}
onClick={() => toggleReplyBoxVisibility(props.commentIndex)}>
{t('Reply to')}
</span>
];
const commentReplyBox = (
hasReplyCommentBox[props.commentIndex]
? <AddComment
id={props.codeId}
parentCommentId={props.parentCommentId}
commentIndex={props.commentIndex}
toggleReplyBoxVisibility={toggleReplyBoxVisibility}
updateComments={props.updateComments}
/>
: null
);
return (
<Comment
author={props.userId}
datetime={props.datePosted}
content={props.body}
actions={actions}
children={commentReplyBox}
style={commentStyle}
/>
);
}
Here's my AddComment component:
function AddComment(props) {
const { t } = useTranslation();
const { TextArea } = Input;
const [form] = Form.useForm();
const [comment, setComment] = useState();
const buttonStyle = { float: 'right' };
function onCommentChange(newComment) {
setComment(newComment.target.value);
}
function resetCommentInput() {
setComment('');
}
function onFormReset() {
form.resetFields();
}
function submitComment() {
let request = {
body: comment,
code_id: props.id,
line_number: props.lineNumber,
parent_comment_id: props.parentCommentId
};
fetch('/api/comment/add',
{
method: 'POST',
body: JSON.stringify(request)
}
).then(response => response.json())
.then(data => {
if (data.success === 1) {
if (props.parentCommentId) {
props.toggleReplyBoxVisibility(props.commentIndex);
}
props.updateComments();
resetCommentInput();
}
});
}
return (
<>
<Form form={form} name="comment" className="comment-form"
onFinish={submitComment}
id={"add-comment-form" + props.parentCommentId}>
<Form.Item name="body" label={t('Comment')}>
<TextArea placeholder={t('Leave a comment')}
onChange={onCommentChange}
id={getCommentTextAreaId(props.lineNumber, props.parentCommentId)}
/>
</Form.Item>
<Form.Item style={buttonStyle}>
<Space>
<Button type="primary" htmlType="submit"
id={
getPostCommentButtonId(props.lineNumber, props.parentCommentId)
}
className = "comment-form-button" onClick={onFormReset}>
{t('Post')}
</Button>
{props.parentCommentId
? <Button id={"cancel-add-reply-comment-" + props.parentCommentId}
type="secondary" className="comment-form-button"
onClick={
() => props.toggleReplyBoxVisibility(props.commentIndex)
}>
{t('Cancel')}
</Button>
: null
}
</Space>
</Form.Item>
</Form>
</>
);
}
Try this
function submitComment() {
let request = {
body: comment,
code_id: props.id,
line_number: props.lineNumber,
parent_comment_id: props.parentCommentId
};
fetch('/api/comment/add',
{
method: 'POST',
body: JSON.stringify(request)
}
).then(response => response.json())
.then(data => {
if (data.success === 1) {
props.updateComments();
resetCommentInput();
// Run resetCommentInput before props.toggleReplyBoxVisibility
if (props.parentCommentId) {
props.toggleReplyBoxVisibility(props.commentIndex);
}
}
});
}
you should update the component state before unmounting it

React view only gets fresh data on page reload?

I'm building a simple campgrounds CRUD app to get some practice with the MERN stack and Redux.
Adding a campground is working fine. I'm routing to the campgrounds list page after adding a campground. But unless I reload the page, fresh data isn't retrieved.
I figured it has something to do with React's lifecycle methods.
Code:
manageCampground.js
import React, { Component } from 'react';
import TextField from '#material-ui/core/TextField';
import Card from '#material-ui/core/Card';
import Button from '#material-ui/core/Button';
import '../../styles/addCampground.css';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
actionAddCampground,
getCampgroundDetails,
actionUpdateCampground
} from './actions/campgroundActions';
class AddCampground extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
description: '',
cost: ''
};
}
componentDidMount() {
const campground = this.props.campground;
if (campground._id) {
this.props.getCampgroundDetails(campground._id);
this.setState({
name: campground.name,
description: campground.description,
cost: campground.cost
});
}
}
handleChange = e => {
const { name, value } = e.target;
this.setState({
[name]: value
});
};
addCampground = () => {
const name = this.state.name;
const description = this.state.description;
const cost = this.state.cost;
this.props.actionAddCampground({
name,
description,
cost
});
this.props.history.push('/home');
console.log('Campground added successfully');
};
updateCampground = () => {
const name = this.state.name;
const description = this.state.description;
const cost = this.state.cost;
this.props.actionUpdateCampground({
name,
description,
cost
});
this.props.history.push('/home');
console.log('Updated successfully');
};
render() {
console.log(this.props);
return (
<Card className="add-campground-card">
<TextField
name="name"
className="textfield"
label="Campground name"
variant="outlined"
value={this.state.name}
onChange={e => this.handleChange(e)}
/>
<TextField
name="description"
className="textfield"
label="Campground description"
variant="outlined"
value={this.state.description}
onChange={e => this.handleChange(e)}
/>
<TextField
name="cost"
className="textfield"
type="number"
label="Campground cost"
variant="outlined"
value={this.state.cost}
onChange={e => this.handleChange(e)}
/>
{!this.props.campground._id ? (
<Button
variant="contained"
color="primary"
onClick={this.addCampground}>
Add Campground
</Button>
) : (
<Button
variant="contained"
color="primary"
className="update-campground-btn"
onClick={this.updateCampground}>
Update Campground
</Button>
)}
</Card>
);
}
}
const mapStateToProps = state => {
return {
campground: state.campgroundList.singleCampground || ''
};
};
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
actionAddCampground,
getCampgroundDetails,
actionUpdateCampground
},
dispatch
);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddCampground);
campgroundList.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getAllCampgrounds } from './actions/campgroundActions';
import Header from '../common/Header';
import Card from '#material-ui/core/Card';
import CardActionArea from '#material-ui/core/CardActionArea';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import { Link } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import '../../styles/landingPage.css';
class CampgroundLanding extends Component {
componentDidMount() {
this.props.getAllCampgrounds();
console.log('From component did mount');
}
render() {
const { campgrounds } = this.props;
return (
<>
<Header />
{campgrounds.map(campground => (
<Card className="campground-card" key={campground._id}>
<CardActionArea>
<CardContent>
<Typography gutterBottom variant="h5"
component="h2">
{campground.name}
</Typography>
<Typography variant="body2" color="textSecondary"
component="p">
{campground.description}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Link
style={{ textDecoration: 'none', color: 'white' }}
to={`/campgrounds/${campground._id}`}>
<Button size="small" color="primary">
View Details
</Button>
</Link>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Card>
))}
<Link
style={{ textDecoration: 'none', color: 'white' }}
to="/campgrounds/add">
<Button color="primary">Add Campground</Button>
</Link>
</>
);
}
}
const mapStateToProps = state => {
return {
campgrounds: state.campgroundList.campgrounds
};
};
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
getAllCampgrounds
},
dispatch
);
};
export default connect(
mapStateToProps,
null
)(CampgroundLanding);
campgroundActions.js
import {
GET_ALL_CAMPGROUNDS,
ADD_CAMPGROUND,
GET_CAMPGROUND_DETAILS,
EDIT_CAMPGROUND
} from '../actionTypes/types';
import axios from 'axios';
const API_URL = `http://localhost:5000/api`;
export const getAllCampgrounds = () => {
return dispatch => {
axios
.get(`${API_URL}/campgrounds`)
.then(res => {
dispatch({
type: GET_ALL_CAMPGROUNDS,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const actionAddCampground = campground => {
return dispatch => {
axios
.post(`${API_URL}/campgrounds`, campground)
.then(res => {
console.log(res);
dispatch({
type: ADD_CAMPGROUND,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const getCampgroundDetails = id => {
return dispatch => {
axios
.get(`${API_URL}/campgrounds/${id}`)
.then(res => {
dispatch({
type: GET_CAMPGROUND_DETAILS,
payload: res
});
})
.catch(err => console.log(err));
};
};
export const actionUpdateCampground = id => {
return dispatch => {
axios
.put(`${API_URL}/campgrounds/${id}`)
.then(res => {
console.log(res);
dispatch({
type: EDIT_CAMPGROUND,
payload: res
});
})
.catch(err => console.log(err));
};
};
campgroundReducers.js
import {
GET_ALL_CAMPGROUNDS,
ADD_CAMPGROUND,
GET_CAMPGROUND_DETAILS,
EDIT_CAMPGROUND
} from '../actionTypes/types';
const initialState = {
campgrounds: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_ALL_CAMPGROUNDS:
const { campgroundList } = action.payload.data;
state.campgrounds = campgroundList;
return {
...state
};
case ADD_CAMPGROUND:
const { campground } = action.payload.data;
return {
...state,
campground
};
case GET_CAMPGROUND_DETAILS:
const { singleCampground } = action.payload.data;
return { ...state, singleCampground };
case EDIT_CAMPGROUND:
const { editedCampground } = action.payload.data;
return { ...state, editedCampground };
default:
return state;
}
};
If I'm using componentDidUpdate, it's leading to infinite loop.
componentDidUpdate(prevProps) {
if (prevProps.campgrounds !== this.props.campgrounds) {
this.props.getAllCampgrounds();
}
}
I know I'm going wrong somewhere, but I can't figure out where.
You have to fix your componentDidUpdate method to avoid this infinity loop. Your are trying to compare objects via === method which will always fail according to this example:
const a = {key: 1}
const b = {key: 1}
console.log(a === b); // false
You could use for example isEqual method from lodash module to compare objects like you want https://lodash.com/docs/4.17.15#isEqual
console.log(_.isEqual({key: 1}, {key: 1})) // true
console.log({key: 1} === {key: 1}) // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Actually why do you want refresh data in this case? When you add object with success into the store this will refresh your component so you don't have to request for fresh data.

Categories

Resources