so I've been working on an admin control panel for a page that displays a list of cards that once clicked, redirect the user to either a video ora text-based link. I must also add a CRUD feature that allows me to edit / remove posts from the API provided.
The API is locally hosted, so requesting "localhost:3000/videos/{Id}" will load up the object with the following fields: "id", "url", "title" and "thumbnail"
I have a class that is used for making the card, called HelpCard.tsx The code is as follows:
import React, { Component } from "react";
import "../help/HelpCard.css";
import "../help/HelpList";
import spinner from "../help/spinner.gif";
import { string, number } from "prop-types";
import { Link } from "react-router-dom";
interface Props {
url: string;
title: string;
thumbnail: string;
}
interface State {
title: string;
thumbnail: string;
id: string;
imageLoading?: boolean;
tooManyRequests?: boolean;
}
export default class HelpCard extends Component<Props, State> {
state = {
id: "",
title: "",
imageLoading: true,
tooManyRequests: false,
thumbnail: "",
deleteProduct: true
};
componentDidMount() {
const { url, title, thumbnail } = this.props;
const id = url.split("/")[url.split("/").length - 2];
this.setState({
id,
title,
thumbnail,
imageLoading: true,
tooManyRequests: false
});
}
render() {
const isThumbnail = this.state.thumbnail;
const adminhelpcard = this.state;
return (
<div>
{isThumbnail ? (
<div className="horizontalCard">
<div className="innerCard">
<div className="leftImage">
<img
className="Sprite"
onLoad={() => this.setState({ imageLoading: false })}
onError={() => this.setState({ tooManyRequests: true })}
src={this.state.thumbnail}
style={
this.state.tooManyRequests
? { display: "none" }
: this.state.imageLoading
? { display: "null" }
: { display: "null" }
}
/>
</div>
<div className="rightText">
<div className="card-body">
{this.state.title}
<div className="cardButtons">
<button className="btn btn-update btn-outline-primary">Update</button>
<button
onClick={() => adminhelpcard.deleteProduct(this.state.id)}
className="btn btn-outline-primary">
Delete
</button>
</div>
</div>
</div>
</div>
</div>
And then I have the HelpList.tsx module that is responsible for displaying the cards in the form of a list. and the code is as follows:
import React, { Component } from "react";
import HelpCard from "./HelpCard";
import "../help/HelpCard.css";
import axios from "axios";
import InfiniteScroll from "react-infinite-scroller";
import { Button } from "components/ui";
interface State {
url: string;
adminhelpcard: SingleAdminHelpCard[];
error: null;
response: {};
}
interface SingleAdminHelpCard {
id: string;
url: string;
title: string;
thumbnail: string;
}
interface Props {}
export default class HelpList extends Component<Props, State> {
state = {
id: "",
url: "http://localhost:3000/videos/",
adminhelpcard: [],
itemsCountPerPage: 1,
activePage: 1,
error: null,
response: {}
};
loadAdminHelpCard = () => {
axios
.get(this.state.url)
.then((res) => {
this.setState((prevState) => {
const adminhelpcard = prevState.adminhelpcard;
return {
adminhelpcard: [...prevState.adminhelpcard, ...res.data],
url: res.data.next
};
});
})
.catch(function(error) {
// handle error
console.log(error);
});
};
async componentDidMount() {
const apiUrl = "http://localhost:3000/videos/";
const res = await axios.get(this.state.url);
this.setState({ adminhelpcard: res.data });
fetch(apiUrl)
.then((res) => res.json())
.then(
(result) => {
this.setState({
adminhelpcard: result
});
},
(error) => {
this.setState({ error });
}
);
}
deleteProduct(id: any) {
const { adminhelpcard } = this.state;
const apiUrl = `http://localhost:3000/videos/${this.state.id}`;
const options = {
method: "DELETE"
};
fetch(apiUrl, options)
.then((res) => res.json())
.then(
(result) => {
this.setState({
response: result,
adminhelpcard: adminhelpcard.filter((adminhelpcard: SingleAdminHelpCard) => adminhelpcard.id !== id)
});
},
(error) => {
this.setState({ error });
}
);
}
render() {
console.log(this.state.adminhelpcard);
return (
<div>
<React.Fragment>
{this.state.adminhelpcard ? (
<div className="row">
<InfiniteScroll
pageStart={1}
loadMore={this.loadAdminHelpCard}
hasMore={this.state.url ? true : false}
threshold={0}
loader={
<div className="loader" key={0}>
Loading ...
</div>
}>
{this.state.adminhelpcard.map((adminhelpcard: SingleAdminHelpCard, i) => (
<HelpCard
key={adminhelpcard.id + i}
title={adminhelpcard.title}
url={adminhelpcard.url}
thumbnail={adminhelpcard.thumbnail}
/>
))}
</InfiniteScroll>
</div>
) : (
<h1>Loading Cards</h1>
)}
</React.Fragment>
</div>
);
}
}
I get the following error:
This expression is not callable.
Type 'Boolean' has no call signatures.ts(2349)
from the "deleteProduct" function in the line of code:
onClick={() => adminhelpcard.deleteProduct(this.state.id)}
When I try to click the delete button on the helpCards, it says that the no function called "deleteProduct" is recognized. How do I go about fixing this?
--------------------------EDIT----------------------------
Error given for adding deleteProduct to HelpCard component.
"No overload matches this call.
Overload 1 of 2, '(props: Readonly): HelpCard', gave the following error.
Type '{ key: string; title: string; url: string; thumbnail: string; deleteProduct: (id: any) => void; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes & Readonly & Readonly<{ children?: ReactNode; }>'.
Property 'deleteProduct' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes & Readonly & Readonly<{ children?: ReactNode; }>'."
First, you need to pass the deleteProduct function as a prop to the HelpCard component.
So in your HelpList.tsx, add another prop to the <HelpCard/> element as follows.
<HelpCard
key={adminhelpcard.id + i}
title={adminhelpcard.title}
url={adminhelpcard.url}
thumbnail={adminhelpcard.thumbnail}
deleteProduct={this.deleteProduct.bind(this)}
/>
Then you need to use it from the onClick handler within the HelpCard component.
<button
onClick={() => this.props.deleteProduct(this.state.id)}
className="btn btn-outline-primary">
Delete
</button>
Probably you may need to change the Props interface in your HelpCard to cater to this new prop as well.
interface Props {
url: string;
title: string;
thumbnail: string;
deleteProduct: (id: any) => void;
}
Related
tried to figure this out but could not find any solution here. I am relatively new to TypeScript, and trying to migrate a JS project to TS, which uses Formik, did not had major problems with it, except this one, where I cannot specify the type of resetForm and setSubmitting.
import { Formik, Form, FormikState, FormikHelpers } from 'formik';
import * as yup from 'yup';
import { useAppSelector } from 'custom-hooks';
import SelectOption from 'components/elements/select';
import PriceBelowInput from 'components/elements/pricebelowinput';
import Button from 'components/elements/button';
import EditableTitle from 'components/elements/editabletitle';
import { tracking, currencyLabels } from 'shared/js';
import { BUTTON_COLORS, BUTTON_TYPE } from 'components/elements/button/consts';
interface BudgetBoostSchedule extends HandleSubmitValues {
chargeAmountKWh: number;
isActive: boolean;
singleChargeDay: string;
}
interface HandleSubmitValues {
priceBelow: number;
scheduleNickName: string;
chargingOutputName: string;
id?: string;
scheduleType?: string;
}
interface IAddSchedule {
color: string;
condition: {
BUDGET_BOOST: boolean;
BUDGET_CHARGE: boolean;
SCHEDULED_BOOST: boolean;
SCHEDULED_CHARGE: boolean;
SINGLE_CHARGE: boolean;
};
current: Element;
heaters: { value: string; label: string }[];
labels: {
boostLabel: string;
cancelBtnLabel: string;
chargeLabel: string;
defaultTitle: string;
submitBtnLabel: string;
updateBtnLabel: string;
validation: {
chargeAmountMinutes: string;
scheduleNickName: string;
scheduleNickNameMax: string;
};
};
name: string;
onCancelBtnClick: () => void;
onClose: () => void;
onSubmitBtnClick: (x: HandleSubmitValues) => void;
schedule: BudgetBoostSchedule | null;
setter: () => void;
}
const AddSchedule = (props: IAddSchedule) => {
console.log('🚀 ~ file: addschedule.tsx:15 ~ AddSchedule ~ props', props);
const { labels, schedule, heaters, onCancelBtnClick, onSubmitBtnClick } = props;
const store = useAppSelector((state) => state);
const { countryCode } = store.location.activeLocation.address;
const getInitialValues = () => ({
scheduleNickName: schedule?.scheduleNickName || labels?.defaultTitle,
chargingOutputName: schedule?.chargingOutputName || heaters?.[0]?.value,
priceBelow: schedule?.priceBelow || 0,
});
const getValidationSchema = () => {
const validationText = labels?.validation;
return yup.object().shape({
scheduleNickName: yup.string().max(24, validationText?.scheduleNickNameMax).required(validationText?.scheduleNickName),
});
};
const handleSubmit = (
values: HandleSubmitValues,
{ resetForm, setSubmitting }: { resetForm: any; setSubmitting: FormikHelpers<HandleSubmitValues> }
) => {
const scheduleToSubmit = { ...values, priceBelow: values.priceBelow || 10, scheduleType: 'Budget' };
console.log('🚀 ~ file: addschedule.tsx:94 ~ handleSubmit ~ values', values);
if (schedule) {
scheduleToSubmit.id = schedule.id;
}
onSubmitBtnClick(scheduleToSubmit);
setSubmitting(false);
resetForm(getInitialValues());
tracking({
name: `${schedule ? labels?.updateBtnLabel : labels?.submitBtnLabel} button`,
location: `schedules/ add or edit schedule`,
action: 'submit',
});
};
const handleOnClick = () => {
onCancelBtnClick();
tracking({
name: `${labels?.cancelBtnLabel} button`,
location: `schedules/ add or edit schedule`,
action: 'click',
});
};
return (
<div className="bud-boost-add-schedule">
<Formik initialValues={getInitialValues()} enableReinitialize onSubmit={handleSubmit} validationSchema={getValidationSchema()}>
{({ isSubmitting }) => (
<Form>
<div className="bud-boost-add-schedule__content">
<EditableTitle className="bud-boost-add-schedule__content--title" name="scheduleNickName" id="scheduleNickName" iconName="editWhite" />
<SelectOption name="chargingOutputName" optionList={heaters} label={labels?.boostLabel} />
<PriceBelowInput title={labels?.chargeLabel} name="priceBelow" unitLabel={currencyLabels(countryCode)} />
</div>
<div className="bud-boost-add-schedule__buttons">
<Button className={BUTTON_COLORS.pink} label={labels?.cancelBtnLabel} onClick={handleOnClick} />
<Button
className={`${BUTTON_COLORS.pink} ${BUTTON_COLORS.filled}`}
type={BUTTON_TYPE.SUBMIT}
isDisabled={isSubmitting}
label={schedule ? labels?.updateBtnLabel : labels?.submitBtnLabel}
/>
</div>
</Form>
)}
</Formik>
</div>
);
};
export default AddSchedule;
I understand that I am not typing correctly setSubmitting and resetForm, but can't figure out where to look at.
I get the error in the handleSubmit function:
setSubmitting(false);
resetForm(getInitialValues());
with these too, where if I put anything besides any, triggers the no call signature error
A componenet VerifiedComp renders children if the email is verified(a prop from redux). If the email is not verified than it displays a button. The problem that I am having is that typescript is asking for children in the Parent Component.
Here is the CreatePage Component which is the parent component.
type StateTypes = {
history: { push: (link: string) => {} };
};
const mapStateToProps = ({ history }: StateTypes) => ({
history,
});
const connector = connect(mapStateToProps, { startCreateMovie });
type PropsFromRedux = ConnectedProps<typeof connector>;
export const CreatePage = ({ history, startCreateMovie }: PropsFromRedux) => {
return (
<VerifiedComp>
<CreateForm ... />
</VerifiedComp>
);
};
And here is the VerifiedComp.
type PropTypes = {
children: React.ReactNode
};
type StateTypes = {
auth: { user: { emailVerified: boolean }, providerData: {email: string} };
}
const mapStateToProps = (state: StateTypes, props: PropTypes) => ({
email: state.auth.providerData.email,
children: props.children,
emailVerified: state.auth.user.emailVerified
});
const connector = connect(mapStateToProps);
type PropsFromRedux = ConnectedProps<typeof connector>;
const VerifiedComp: React.FC<PropsFromRedux> = (props: PropsFromRedux) => {
return props.emailVerified ? (
<React.Fragment>{props.children}</React.Fragment>
) : (
<Box>.....</Box>
)
Here is the complete problem
Type '{ children: Element; }' is missing the following properties from type '{ email: string; emailVerified: boolean; }': email, emailVerified TS2739
19 | export const CreatePage = ({ history, startCreateMovie }: PropsFromRedux) => {
20 | return (
> 21 | <VerifiedComp>
| ^
22 | <MovieForm
It's because you have made children property as mandatory
type PropTypes = {
children: React.ReactNode
};
instead make it optional as below:
type PropTypes = {
children?: React.ReactNode
};
I have a component of checkbox, basically what im trying to do is to make array of the checked ones and send the information to an api.
but im having trouble updating the state array without deleteing the last object inserted.
im using concat and still same result.
here is the code for the whole component:
import React, { Fragment, Component } from 'react';
import { Inputt, Checkbox } from './style';
interface Istate {
checked: boolean;
products?: any[];
}
interface Iprops {
id: any;
value: any;
changeValue?: (checkBoxId: string, value: any) => void;
}
class CheckBoxComp extends Component<Iprops, Istate> {
state = {
checked: true,
products: [] as any,
};
addOne = (id: any, checked: any) => {
let { products } = this.state;
const newObj = { id, checked };
products = products.concat(newObj);
this.setState({ products }, () => {
console.log(products);
});
};
isChecked = (id: any) => {
const { checked, products } = this.state;
const { changeValue } = this.props;
this.setState({
checked: !checked,
});
if (changeValue) {
changeValue(id, checked);
}
this.addOne(id, checked);
};
render() {
const { id, value } = this.props;
const { checked, products } = this.state;
return (
<Fragment>
<Checkbox>
<span />{' '}
<label className="checkbox-wrapper">
<span className="display" />
<Inputt
type="checkbox"
value={checked}
onChange={() => {
this.isChecked(id);
}}
/>
<span className="checkmark" />
</label>
</Checkbox>
</Fragment>
);
}
}
export default CheckBoxComp;
You can try this approach which utilizes the spread operator to make an array copy:
addOne = (id: any, checked: any) => {
this.setState((state: Istate): Partial<Istate> => ({
products: [
...state.products,
{ id, checked },
],
}), () => console.log(this.state.products));
};
so I am currently working on an admin control panel for a page that displays a list of card. The cards contain details passed on from a local API that is hosted with a range of data such as "id", "title", "URL" and "ThumbnailUrl" and this is obtained from "localhost:0000/videos".
So I have a module, one that creates the Card called HelpCard.tsx and the code is as follows:
import React, { Component } from "react";
import "../help/HelpCardTwo.css";
import "../help/HelpList";
import Popup from "reactjs-popup";
import spinner from "../help/spinner.gif";
import placeholder from "../help/placeholder.jpeg";
interface Props {
id: string;
url: string;
title: string;
thumbnail: string;
deleteProduct: (id: any) => void;
editProduct: (id: any, title: string, url: string, thumbnail: string) => void;
}
interface State {
title: string;
thumbnail: string;
id: string;
url: string;
imageLoading?: boolean;
tooManyRequests?: boolean;
}
export default class HelpCard extends Component<Props, State> {
state = {
url: "",
id: "",
title: "",
imageLoading: true,
tooManyRequests: false,
thumbnail: ""
};
componentDidMount() {
const { url, title, thumbnail } = this.props;
const id = url.split("/")[url.split("/").length - 2];
this.setState({
url,
id,
title,
thumbnail,
imageLoading: true,
tooManyRequests: false
});
}
render() {
const isThumbnail = this.state.thumbnail;
const adminhelpcard = this.state;
return (
<React.Fragment>
<div className="cards">
<article className="card card--2">
<div className="card__info-hover"></div>
<div className="card__img">
{this.state.imageLoading ? <img src={placeholder} style={{ width: "100%", height: "100%" }}></img> : null}
<img
className="Sprite"
onLoad={() => this.setState({ imageLoading: false })}
onError={() => this.setState({ tooManyRequests: false })}
src={this.state.thumbnail}
/>
</div>
<div className="card__info">
<span className="card__category">{this.state.title}</span>
<div className="cardButtons">
<Popup trigger={<button className="btn blue-outline">Edit</button>} modal position="left top">
<form
onSubmit={(e) => e.preventDefault()}
id="videoCardEdit"
style={{ width: "auto", height: "auto" }}>
<div>
<div>
<label>Title:</label>
<input
className="input"
style={{ width: "100%" }}
name="videoCardTitle"
onChange={(e) => {
this.setState({ title: e.target.value });
}}
value={this.state.title}></input>
</div>
<div>
<label>URL:</label>
<input
className="input"
style={{ width: "100%" }}
name="videoCardURL"
onChange={(e) => {
this.setState({ url: e.target.value });
}}
value={this.state.url}></input>
</div>
<div>
<label>Thumbnail URL:</label>
<input
className="input"
style={{ width: "100%" }}
name="videoCardThumbnail"
onChange={(e) => {
this.setState({ thumbnail: e.target.value });
}}
value={this.state.thumbnail}></input>
</div>
<br></br>
</div>
<button
className="btn blue-outline"
style={{
float: "left"
}}
onClick={() =>
this.props.editProduct(this.props.id, this.state.title, this.state.url, this.state.thumbnail)
}
id="btn blue-outline">
confirm
</button>
</form>
</Popup>
<button onClick={() => this.props.deleteProduct(this.props.id)} className="btn blue-outline">
Delete
</button>
</div>
</div>
</article>
</div>
</React.Fragment>
);
}
}
I then also have HelpList.tsx that has the following code:
import React, { Component } from "react";
import HelpCard from "./HelpCard";
import "../help/HelpCard.css";
import axios from "axios";
import InfiniteScroll from "react-infinite-scroller";
interface State {
url: string;
title: string;
adminhelpcard: SingleAdminHelpCard[];
error: null;
response: {};
thumbnail: string;
}
interface SingleAdminHelpCard {
id: string;
url: string;
title: string;
thumbnail: string;
}
interface Props {}
export default class HelpList extends Component<Props, State> {
state = {
title: "",
thumbnail: "",
id: "",
url: "http://localhost:3000/videos/",
adminhelpcard: [],
itemsCountPerPage: 1,
activePage: 1,
error: null,
response: {}
};
loadAdminHelpCard = () => {
axios
.get(this.state.url)
.then((res) => {
this.setState((prevState) => {
const adminhelpcard = prevState.adminhelpcard;
return {
adminhelpcard: [...prevState.adminhelpcard, ...res.data],
url: res.data.next
};
});
})
.catch(function(error) {
// handle error
console.log(error);
});
};
static props: any;
async componentDidMount() {
const apiVideoUrl = "http://localhost:3000/videos/";
const apiManualUrl = "http://localhost:3000/manuals/";
const res = await axios.get(this.state.url);
this.setState({ adminhelpcard: res.data });
fetch(apiVideoUrl)
.then((res) => res.json())
.then(
(result) => {
this.setState({
adminhelpcard: result
});
},
(error) => {
this.setState({ error });
}
);
fetch(apiManualUrl)
.then((res) => res.json())
.then(
(result) => {
this.setState({
adminhelpcard: result
});
},
(error) => {
this.setState({ error });
}
);
}
deleteProduct(id: any) {
const { adminhelpcard } = this.state;
const apiVideoUrl = `http://localhost:3000/videos/${id}`;
const apiManualUrl = `http://localhost:3000/manuals/${id}`;
const options = {
method: "DELETE"
};
fetch(apiVideoUrl, options)
.then((res) => res.json())
.then(
(result) => {
this.setState({
response: result,
adminhelpcard: adminhelpcard.filter((adminhelpcard: SingleAdminHelpCard) => adminhelpcard.id !== id)
});
},
(error) => {
this.setState({ error });
}
);
fetch(apiManualUrl, options)
.then((res) => res.json())
.then(
(result) => {
this.setState({
response: result,
adminhelpcard: adminhelpcard.filter((adminhelpcard: SingleAdminHelpCard) => adminhelpcard.id !== id)
});
},
(error) => {
this.setState({ error });
}
);
console.log(this.state.id);
}
editProduct(id: any, title: string, url: string, thumbnail: string) {
const { adminhelpcard } = this.state;
const apiVideoUrl = `http://localhost:3000/videos/${id}`;
const apiManualUrl = `http://localhost:3000/manuals/${id}`;
const options = {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title,
url,
thumbnail
})
};
fetch(apiVideoUrl, options)
.then((res) => res.json())
.then(
(result) => {
this.setState({
response: result,
adminhelpcard: adminhelpcard.filter((adminhelpcard: SingleAdminHelpCard) => adminhelpcard.id !== id)
});
},
(error) => {
this.setState({ error });
}
);
fetch(apiManualUrl, options)
.then((res) => res.json())
.then(
(result) => {
this.setState({
response: result,
adminhelpcard: adminhelpcard.filter((adminhelpcard: SingleAdminHelpCard) => adminhelpcard.id !== id)
});
},
(error) => {
this.setState({ error });
}
);
}
render() {
console.log(this.state.adminhelpcard);
return (
<div>
<React.Fragment>
{this.state.adminhelpcard ? (
<div className="row">
<InfiniteScroll
pageStart={1}
loadMore={this.loadAdminHelpCard}
hasMore={this.state.url ? true : false}
threshold={0}
loader={
<div className="loader" key={0}>
Loading ...
</div>
}>
{this.state.adminhelpcard.map((adminhelpcard: SingleAdminHelpCard, i) => (
<HelpCard
id={adminhelpcard.id}
key={adminhelpcard.id + i}
title={adminhelpcard.title}
url={adminhelpcard.url}
thumbnail={adminhelpcard.thumbnail}
deleteProduct={this.deleteProduct.bind(this)}
editProduct={this.editProduct.bind(this)}
/>
))}
</InfiniteScroll>
</div>
) : (
<h1>Loading Cards</h1>
)}
</React.Fragment>
</div>
);
}
}
So I created these two classes to obtain all the information from localhost:0000/videos before realizing theres also localhost:0000/texts which also send the same data such as "Id, title, URL, thumbnailUrl". How can I go about loading all the cards from both urls simultaneosly? The list now only seems to bring in /texts cards and not both together.
The easiest way to do that is to use a Promise.all(getVideos(),getTexts()). Assuming getVideos() and getTexts() are methods that are returning promises like so:
async function getVideos() {
const res = await fetch('http://localhost:3000/videos/')
return await res.json();
}
async function getText() {
const res = await fetch('http://localhost:3000/texts/')
return await res.json();
}
Promise.all(getVideos(), getTexts()).then(data = {
const [ videos, texts ] = data;
// all data arrived at the same time
})
I would imagine your componentDidMount to look like this:
async componentDidMount() {
const apiVideoUrl = "http://localhost:3000/videos/";
const apiManualUrl = "http://localhost:3000/manuals/";
const getVideos = async() => {
return await axios.get(apiVideoUrl);
}
const getManuals = async() => {
return await axios.get(apiManualUrl);
}
try {
const [videos, manuals] = await Promise.all(getVideos(), getManuals());
// render to state setState({ prop: ? })
} catch(err) {
this.setState({ error });
}
}
not sure why you have two fetch calls in edit and delete, it looks like the calls are overwriting each other?
I am trying to add Delete Functionality, to my React Application. So, I created a Delete Model Component. And I am using it in my main page.
Main-Page Component:
import IUser from '../../dto/IUser';
import DeleteUser from '../../components/DeleteUser';
import { listUsers, getUser, deleteUser } from '../../config/service';
interface UserDetailsProps extends RouteComponentProps<RouteUserInfo> {
notify(options: object): any;
actualValue: string;
callBack: any;
label: string;
}
interface RouteUserInfo {
username: string;
}
export interface IState {
errorMessage: LensesHttpResponseObj | null;
isUserDeleteModalOpen: boolean;
isLoading: boolean;
user: IUser | null;
}
const UserToolTip = (props: any): JSX.Element => (
<LensesTooltip id="isActive" place="right" {...props} />
);
export class UserDetailsPage extends Component<UserDetailsProps, IState> {
hasBeenMounted = false;
state: IState = {
isUserDeleteModalOpen: false,
errorMessage: null,
isLoading: false,
user: null
};
componentDidMount(): any {
this.hasBeenMounted = true;
this.onFetchData();
}
componentWillUnmount(): void {
this.hasBeenMounted = false;
}
getUserUsername = (): string => {
const { match } = this.props;
return match.params.username;
};
onFetchData = () => {
this.setState({
isLoading: true
});
return this.onFetchUser();
};
onFetchUser = () =>
getUser(this.getUserUsername())
.then(username => {
if (this.hasBeenMounted && typeof username !== 'undefined') {
this.setState({
isLoading: false,
user: username.data
});
}
})
.catch((errorResponse: HttpResponseObj | null = null) => {
if (this.hasBeenMounted) {
this.setState({
isLoading: false,
user: null,
errorMessage: errorResponse
});
}
});
openUserDeleteModal = () => {
this.setState(prevState => ({
...prevState,
isUserDeleteModalOpen: true
}));
};
closeUserDeleteModal = () => {
this.setState(prevState => ({
...prevState,
isUserDeleteModalOpen: false
}));
};
// Dropdown Render Method:
<Item onClick={this.openUserDeleteModal()}> // The error appears when I add the onClik
<Icon icon="trash-o" className=" pl-0 py-2 col-1" />
<span className="col pr-0 mr-0">Delete User</span>
</Item>
Of course I call the dropdown render Method inside the main render(), along with the render method for the Delete Component:
renderUserDeleteModal = (): JSX.Element | null | void => {
const { isUserDeleteModalOpen, user } = this.state;
if (!user || !user.username) {
return null;
}
return (
<DeleteUser
isModalOpen={isUserDeleteModalOpen}
user={user}
onSuccess={this.closeDeleteModalSuccess}
onCloseModal={this.closeUserDeleteModal}
/>
);
};
But I get this ERROR: warning: Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state.
I am not sure what am I doing wrong here. To me, it seems legit. Can you explain what am I doing wrong. Thank you!!
you are making call to openUserDeleteModal onClick={this.openUserDeleteModal()} which is causing update of state while rendering the component try the following :
<Item onClick={this.openUserDeleteModal}>
onClik
<Icon icon="trash-o" className=" pl-0 py-2 col-1" />
<span className="col pr-0 mr-0">Delete User</span>
</Item>
You need not invoke the callback to your onClick as that will end up being immediately called upon render.
Remove the parenthesis following onClick={openUserDelete()}.
Your openUserDelete is being called straight away (upon render) and changes the state Object.
changing the state causes a re-render and you can imagine how this would get out of hand...
render > change > render > change...etc