Passing state in function inside React functional component - javascript

I am still pretty new to react and typescript in general, so there might be some other issues I am not seeing. Most of the tutorials I am finding are for class based components instead of functional ones, making it more difficult.
I have a component that contains two checkboxes. When toggling the checkbox, I would also like to post this update to a url. Currently, the toggles are working and I am able to update the state accordingly. The issue is when attempting to post the update, the updated state is not set in the request, but rather the previous state.
Below is the main Document component. I think the issue is with the updateDocument function, since the state has not necessarily been set by setToggles when it is called. From what I have read, I need to use a callback, but I am unsure how I would implement this.
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [toggles, setToggles] = useState<DocumentToggles>(_documentToggles)
const updateDocument = (uri: string, id: string, desc: string, checked: boolean, expired: boolean) => {
axios.post(uri, {
id: id,
description: desc,
checked: checked,
expired: expired
}).then(response => {
console.log(response.data)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: any) => {
console.log(data)
if (e.currentTarget !== null) {
const {name, checked} = data;
setToggles(prevState => ({...prevState, [name]: checked}))
// THIS IS NOT WORKING
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
const handleSubmit = (e: FormEvent) => {
if (e.currentTarget !== null) {
e.preventDefault()
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
return (
<Container>
<Form onSubmit={handleSubmit}>
<DocumentCheckboxes
checked={toggles.checked}
expired={toggles.expired}
handleToggle={handleToggle}
/>
<Form.Field>
<Button fluid type="submit">
Save
</Button>
</Form.Field>
</Form>
</Container>
);
};
I just want to be able to pass up-to date values from the "state" provided by useState to a function within a functional component.
I will also add the whole file for the sake of completeness. But basically it is just a wrapper component around an array of Documents:
const _DOCS: IDocument[] = [{id: "666666666666", description: "TESTDESC", checked: false, expired: false}]
const MainAppView = () => {
return (
<div>
<DocumentViewBox documents={_DOCS}/>
</div>
);
}
interface IDocument {
id: string;
description: string;
checked: boolean;
expired: boolean;
}
// DocumentViewBox is used to display a list of documents.
const DocumentViewBox: FC<{ documents: IDocument[] }> = ({documents}): ReactElement => {
return (
<div>
{documents.map(doc => {
return <Document key={doc.id} document={doc}/>
})}
</div>
);
};
interface DocumentToggles {
checked: boolean;
expired: boolean;
}
const _documentToggles: DocumentToggles = {checked: false, expired: false}
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [toggles, setToggles] = useState<DocumentToggles>(_documentToggles)
const updateDocument = (uri: string, id: string, desc: string, checked: boolean, expired: boolean) => {
axios.post(uri, {
id: id,
description: desc,
checked: checked,
expired: expired
}).then(response => {
console.log(response.data)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: any) => {
console.log(data)
if (e.currentTarget !== null) {
const {name, checked} = data;
setToggles(prevState => ({...prevState, [name]: checked}))
// THIS IS NOT WORKING
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
const handleSubmit = (e: FormEvent) => {
if (e.currentTarget !== null) {
e.preventDefault()
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
return (
<Container>
<Form onSubmit={handleSubmit}>
<DocumentCheckboxes
checked={toggles.checked}
expired={toggles.expired}
handleToggle={handleToggle}
/>
<Form.Field>
<Button fluid type="submit">
Save
</Button>
</Form.Field>
</Form>
</Container>
);
};
const DocumentCheckboxes: FC<{ checked: boolean, expired: boolean, handleToggle: (e: FormEvent<HTMLInputElement>, data: any) => void }> = ({checked, expired, handleToggle}): ReactElement => {
return (
<Container textAlign="left">
<Divider hidden fitted/>
<Checkbox
toggle
label="Checked"
name="checked"
onChange={handleToggle}
checked={checked}
/>
<Divider hidden/>
<Checkbox
toggle
label="Expired"
name="expired"
onChange={handleToggle}
checked={expired}
/>
<Divider hidden fitted/>
</Container>
);
}
UPDATE:
Updated Document component with the change provided by #Ibz. The only issue now is that the POST request to the update url is run twice if multiple toggles are toggled. Toggling only a single component will not do this.
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [toggles, setToggles] = useState<DocumentToggles>(_documentToggles)
const updateDocument = (uri: string, id: string, desc: string, checked: boolean, expired: boolean) => {
axios.post(uri, {
id: id,
description: desc,
checked: checked,
expired: expired
}).then(response => {
console.log(response.data)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: any) => {
console.log(data)
if (e.currentTarget !== null) {
e.preventDefault()
setToggles(prevState => {
const newState = {...prevState, [data.name]: data.checked};
updateDocument('http://example.com/update', document.id, document.description, newState.checked, newState.expired);
return newState;
})
}
}
const handleSubmit = (e: FormEvent) => {
if (e.currentTarget !== null) {
e.preventDefault()
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}
}
return (
<Container>
<Form onSubmit={handleSubmit}>
<DocumentCheckboxes
checked={toggles.checked}
expired={toggles.expired}
handleToggle={handleToggle}
/>
<Form.Field>
<Button fluid type="submit">
Save
</Button>
</Form.Field>
</Form>
</Container>
);
};
UPDATE 2:
Below is the final working code, slightly simplified from the OP. Thanks to #Ibz for all the help!
Regarding the duplicate POST requests: I was using yarn start to run a development server when I was seeing this issue. After building with yarn build and serving the files with the actual server, the issue is no longer present. This answer on the axios issues page made me try this.
import React, {Component, useState, useEffect, FC, ReactElement, MouseEvent, FormEvent, ChangeEvent} from "react";
import {Container, Segment, Label, Checkbox, CheckboxProps} from "semantic-ui-react";
import axios from "axios";
interface IDocument {
id: string;
description: string;
checked: boolean;
expired: boolean;
}
const _DOCS: IDocument[] = [{id: '0', description: '', checked: false, expired: false}]
const MainAppView = () => {
return (
<div>
<DocumentViewBox documents={_DOCS}/>
</div>
);
}
const DocumentViewBox: FC<{ documents: IDocument[] }> = ({documents}): ReactElement => {
return (
<div>
{documents.map(doc => <Document key={doc.id} document={doc}/>)}
</div>
);
};
const defaultDocumentProps: IDocument = {id: '', description: '', checked: false, expired: false};
const Document: FC<{ document: IDocument }> = ({document}): ReactElement => {
const [documentProps, setDocumentProps] = useState<IDocument>(defaultDocumentProps);
// Run only once and set data from doc
// as the initial state.
useEffect(() => {
setDocumentProps(document)
}, []);
const updateDocument = (uri: string, updateDoc: IDocument) => {
axios.post(uri, updateDoc).then(response => {
console.log('updateDocument response:')
console.log(response.data)
}).catch(err => {
console.log('updateDocument error:' + err)
});
}
const handleToggle = (e: FormEvent<HTMLInputElement>, data: CheckboxProps) => {
e.preventDefault()
setDocumentProps(prevState => {
const {name, checked} = data;
const newState = {...prevState, [name as string]: checked};
console.log('handleToggle new state:')
console.log(newState)
updateDocument('http://example.com/update', newState);
return newState;
});
}
return (
<Checkbox
toggle
label='Checked'
name='checked'
onChange={handleToggle}
checked={documentProps.checked}
/>
);
};

With useState, there's no guarantee that the action is executed in order, as your component needs to re-render to have all the up to date state.
setToggles(prevState => ({...prevState, [name]: checked}))
// THIS IS NOT WORKING
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
That means with this piece of code, your component renders, and you have some value in toggles. When you get to setToggles(...), react queues the update of the state for the next render, so when you get to updateDocument, it's being run with the previous value of toggles.
To get around this, we would usually use useEffect. This is a hook which runs some code whenever some other value changes. In your instance, you would want something like:
useEffect(() => {
updateDocument('http://example.com/update', document.id, document.description, toggles.checked, toggles.expired)
}, [document.id, document.description, toggles.checked, toggles.expired])
The second argument to useEffect is called the Dependency Array, and is a list of values that when changed, causes the function inside useEffect to run.
It can be a little tricky wraping your head around state at first, but I hope this helped. Any other questions, just leave a comment. You can find more information here: https://reactjs.org/docs/hooks-effect.html

Related

How to update redux state using onChange with React Redux

I'm using React Redux and want to be able to change the title and description of a post, using the onChange method. When only using React the way you would do this is that you keep an useState which you change whenever a change occurs, but I can't seem to get it to work with using redux in react. Instead of the state changing the original title, and description remains and cannot be changed.
From what I have read the basic idea is to have a listener on the input (onChange, usually) and have that fire a redux action. You then have the action tell the reducer to make the change to the store.
I have tried doing this, but could make it work correctly. What am I doing wrong and how do you solve it? I'm also wondering how do I specify that I want to change either title or description when using onChange, or do I simply send everything in post each time a change occurs?
This is what the redux state looks like when entering a post:
{
auth: {
isSignedIn: true,
user: {
id: '624481f22566374c138cf974',
username: 'obiwan',}
},
posts: {
'62448632b87b223847eaafde': {
_id: '62448632b87b223847eaafde',
title: 'hellothere',
desc: 'its been a long time since I heard that name...',
username: 'vorbrodt',
email: 'example#gmail.com',
categories: [],
createdAt: '2022-03-30T16:32:50.158Z',
updatedAt: '2022-03-30T16:32:50.158Z',
__v: 0
}
},
}
Here is where the onChange happens.
Post.js
import { getPostById, editPost } from "../actions";
const Post = ({ getPostById, editPost, username }) => {
const [updateMode, setUpdateMode] = useState(false);
let { id } = useParams();
let post = useSelector((state) => state.posts[id]);
const handleInputChange = (e) => {
try {
editPost(e.target.value);
} catch (err) {}
};
return (
<div className="post">
<div className="post-wrapper">
{updateMode ? (
<input
type="text"
value={post.title}
className="post-title-input"
autoFocus
onChange={(e) => handleInputChange(e)}
/>
) : (
<h1 className="post-title">
{post.title}
</h1>
)}
<div className="desc-area">
{updateMode ? (
<textarea
className="post-desc-input"
value={post.desc}
onChange={(e) => handleInputChange(e)}
/>
) : (
<p className="post-desc">{post.desc}</p>
)}
</div>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return { username: state.auth.user.username };
};
export default connect(mapStateToProps, { getPostById, editPost })(Post);
Here is the action creator:
//edit post in redux state
const editPost = (postValues) => (dispatch) => {
dispatch({ type: EDIT_POST, payload: postValues });
};
And here is the reducer which is suppose to change the state.
postReducer.js
import _ from "lodash";
import { GET_POSTS, GET_POST, CREATE_POST, EDIT_POST } from "../actions/types";
function postReducer(state = {}, action) {
switch (action.type) {
case GET_POSTS:
return { ...state, ..._.mapKeys(action.payload, "_id") };
case GET_POST:
return { ...state, [action.payload._id]: action.payload };
case CREATE_POST:
return { ...state, [action.payload._id]: action.payload };
case EDIT_POST:
//here the change should occur, not sure how to specify if title or desc should
//change
return { ...state, [action.payload._id]: action.payload };
default:
return state;
}
}
export default postReducer;
Hey there something like this should be of help
const handleInputChange = (e, key, id) => {
try {
editPost({ [key]: e.target.value, id });
} catch (err) {}
};
Usage
<textarea
className="post-desc-input"
value={post.desc}
onChange={(e) => handleInputChange(e, "title", post.id)}
/>
action
const editPost = (postValues) => (dispatch) => {
dispatch({ type: EDIT_POST, payload: postValues });
};
Reducer
case EDIT_POST:
//here we destructure the id and return the data without the id cause we //need it below
const {id, ...newData} = action.payload
const indexToUpdate = state.posts.find(post => post.id === id)
const newPostsData = [...state.posts]
//Here we update the actual object and its property that is in the state at //the specific value
newPostsData[indexToUpdate] = {...newPostData[indexToUpdate], {...newData}
return { ...state, posts: newPostsData};

Reactjs: how to keep a selected item state in the pagination? React-hooks

I would like to keep the coloring of the selected item in the state, however, when changing pages (Ex: from 1 to 2 and back to 1), it loses the coloring and as default states start as false, a request is sent to remove all items from the state in useEffect.
is filtering 10 items per page, and when I change pages, apparently the state resets and starts from scratch, even though there are selected items on the page.
Types
type ISkySelected = {
id: string
}
interface ISkusProps {
id: string
description: string
code_sap: string
stock_pe: string
stock_atc: string
enableSku: boolean
skusSelectedList: (selected: ISkySelected) => void
removeSkuSelected: (selected: ISkySelected) => void
}
export function CardList ({ id, description, code_sap, stock_pe, stock_atc, enableSku, skusSelectedList, removeSkuSelected }: ISkusProps) {
const [selected, setSelected] = useState<boolean>(false)
const [color, setColor] = useState<string>('red.500')
const [cursor, setCursor] = useState<string>('')
function selectedSku (event: MouseEvent) {
event.preventDefault()
if (!enableSku) {
return
}
setSelected(!selected)
}
useEffect(() => {
if (selected) {
setColor('red.500')
skusSelectedList({ id: id })
}
if (!selected) {
removeSkuSelected({ id: id })
setColor('white')
}
}, [selected])
useEffect(() => {
if (enableSku) {
setCursor('pointer')
}
if (!enableSku) {
setSelected(false)
setCursor('')
}
}, [enableSku])
return (
<Box
cursor={cursor}
borderRadius='2px'
overflow='hidden'
h='400px'
w='225px'
mt={5}
borderWidth='4px'
borderColor={color}
onClick={(e) => selectedSku(e)}
> ...myComponente </Box>
)
})
PRINTS
selected an item: enter image description here
back one page: enter image description here
returning to the page that was: enter image description here
The problem is that when CardList is unmounted and remounted, the state of the component is reinitialized. You can store IDs array of selected cards higher up the tree. And then pass the selected prop to the card.
For example like this:
interface CardProps {
id: string;
selected: boolean;
toggleSelect: (id: string) => void;
}
const Card = ({ id, selected, toggleSelect }: CardProps) => {
const [color, setColor] = useState<string>('red.500');
const handleClick = () => {
toggleSelect(id);
setColor(selected ? 'red.500' : 'white');
};
return (
<Box onClick={handleClick} color={color}>
...Component
</Box>
);
};
const List = () => {
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const toggleCardSelected = (cardId: string) => {
const isSelected = selectedIds.includes(cardId);
if (isSelected) {
setSelectedIds((prev) => prev.filter((id) => id !== cardId));
} else {
setSelectedIds((prev) => [...prev, cardId]);
}
};
return (
<>
{cards.map(({ id }) => (
<Card id={id} selected={selectedIds.includes(id)} toggleSelect={toggleCardSelected} />
))}
</>
);
};

Abstraction for cleaner CRUD apps in React

I have a new app I'm working on that has a lot of CRUD functionality. A list/grid and then a modal with multiple tabs of content and a save button in the modal header. I'm wondering about patterns to separate state logic from the JSX component so it's easier to read/maintain.
I read about "lifting state", but that makes this item component unruly with the callbacks (and it'll get worse for sure as more detail is added). Is it possible to put the handlers and the state itself in a custom hook and then pull them in where needed, instead of having it at "the closest common ancestor"? For example,
const {company, setCompany, updateCompany, getCompanyByCode, createCompany, onNameChangeHandler, onTitleChangeHandler, etc.} from "./useCompany"
Would all users of this hook have the same view of the data and be able to see and effect updates? I've read about putting data fetching logic in hooks, but what about change handlers for what could be 20 or more fields? I think it's the clutter of the callbacks that bothers me the most.
Here is a shortened version of a component that renders the tabs and data for a specific domain item. Is this a use case for context and/or custom hooks?
import {
getCompanyByCode,
updateCompany,
createCompany,
companyImageUpload,
} from "../../api/companyApi";
const initialState: CompanyItemDetailModel = {
code: "",
name: { en: "", es: "" },
title: { en: "", es: "" },
description: { en: "", es: "" },
displayOrder: 0,
enabled: false,
};
export interface CompanyItemProps {
onDetailsDialogCloseHandler: DetailsCancelHandler;
companyCode: string;
onSaveHandler: () => void;
}
const CompanyItem = (props: CompanyItemProps) => {
const { CompanyCode, onDetailsDialogCloseHandler, onSaveHandler } = props;
const classes = useStyles();
const [tabValue, setTabValue] = useState<Number>(0);
const [isLoading, setIsLoading] = useState(true);
const nameRef = useRef({ en: "", es: "" });
const [Company, setCompany] = useState<CompanyItemDetailModel>(initialState);
const [saveGraphics, setSaveGraphics] = useState(false);
useEffect(() => {
async function getCompany(code: string) {
try {
const payload = await getCompanyByCode(code);
nameRef.current.en = payload.name.en;
setCompany(payload);
} catch (e) {
console.log(e);
} finally {
setIsLoading(false);
}
}
if (CompanyCode.length > 0) {
getCompany(CompanyCode);
}
}, [CompanyCode]);
function handleTabChange(e: React.ChangeEvent<{}>, newValue: number) {
setTabValue(newValue);
}
function detailsDialogSaveHandler() {
const CompanyToSave = {
...Company,
name: JSON.stringify({ ...Company.name }),
description: JSON.stringify({ ...Company.description }),
title: JSON.stringify({ ...Company.title }),
};
if (CompanyCode.length > 0) {
updateCompany(CompanyToSave as any).then((e) => handleSaveComplete());
} else {
createCompany(CompanyToSave as any).then((e) => handleSaveComplete());
}
setSaveGraphics(true);
}
function handleSaveComplete() {
onSaveHandler();
}
function detailsDialogCloseHandler(e: React.MouseEvent) {
onDetailsDialogCloseHandler(e);
}
function handleNameTextChange(e: React.ChangeEvent<HTMLInputElement>) {
setCompany((prevState) => ({
...prevState,
name: {
...prevState.name,
en: e.target.value,
},
}));
}
function handleCompanyCodeTextChange(e: React.ChangeEvent<HTMLInputElement>) {
setCompany((prevState) => ({
...prevState,
code: e.target.value,
}));
}
function handleEnabledCheckboxChange(e: React.ChangeEvent<HTMLInputElement>) {
setCompany((prevState) => ({
...prevState,
enabled: e.target.checked,
}));
}
function handleTitleTextChange(e: React.ChangeEvent<HTMLInputElement>) {
setCompany((prevState) => ({
...prevState,
title: {
...prevState.title,
en: e.target.value,
},
}));
}
return (
<DetailsDialog>
<div className={classes.root}>
<Form>
<ModalHeader
name={nameRef.current}
languageCode={"en"}
onTabChange={handleTabChange}
onCancelHandler={detailsDialogCloseHandler}
onSaveHandler={detailsDialogSaveHandler}
tabTypes={[
DetailsTabTypes.Details,
DetailsTabTypes.Images,
]}
/>
<TabPanel value={tabValue} index={0} dialog={true}>
<CompanyDetailsTab
details={Company}
isEditMode={CompanyCode.length > 1}
languageCode={"en"}
handleNameTextChange={handleNameTextChange}
handleCompanyCodeTextChange={handleCompanyCodeTextChange}
handleEnabledCheckboxChange={handleEnabledCheckboxChange}
handleTitleTextChange={handleTitleTextChange}
handleDescriptionTextChange={handleDescriptionTextChange}
/>
</TabPanel>
<TabPanel value={tabValue} index={1} dialog={true}>
<ImageList />
</TabPanel>
</Form>
</div>
</DetailsDialog>
);
};
export default CompanyItem;

Can't access prevState with useState hook

I have a component where I'm attempting to capture the previousState of something but no matter what it keeps returning the initial value. This is leading me to believe that there's re-rendering that's happening so it keeps defaulting to the initial state.
import React, { useState } from "react";
import { InfoWindow } from "../info-window/info-window";
import { LocationProps } from "../../../interfaces/location-inteface";
export interface Props {
lat: number;
lng: number;
location: LocationProps;
}
type PreviousLocation = {
isActive: boolean;
location: Props["location"];
};
export const InStoreMarker: React.FC<Props> = (props: Props) => {
const { location } = props;
const [isActive, setIsActive] = useState(false);
const [infoWindowVisible, setActiveInfoWindowVisible] = useState(false);
const [activeLocation, setActiveLocation] = useState({
isActive: false,
location: null
});
const [previousLocation, setPreviousLocation] = useState(null);
const onClick = (location: LocationProps, prevLocation: PreviousLocation) => {
setIsActive(true);
setActiveInfoWindowVisible(true);
setPreviousLocation(prevLocation);
setActiveLocation({
isActive: true,
location: location,
});
console.log("Click previousLocation", previousLocation);
console.log("Click location", location);
};
const onClose = (value: boolean) => {
setIsActive(value);
setActiveInfoWindowVisible(value);
};
useEffect(() => {
console.log("activeLocation", activeLocation);
console.log("previousLocation", previousLocation);
}, [previousLocation, activeLocation]);
return (
<div className={styles.inStoreMarkerContainer}>
{isActive && infoWindowVisible && (
<InfoWindow
location={location}
onClose={onClose}
/>
)}
<div
className={styles.inStoreMarker}
onClick={() => onClick(location, activeLocation)}
/>
</div>
);
}
The console log inside of the setActiveLocation callback keeps returning
{
id: null,
isActive: false,
location: null
}
I tried creating a usePrevious function as shown at https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state but that would always return undefined so I'm a little stuck right now.
Maybe a custom hook can help you?
Maybe usePrevious could solve this without adding boilerplate to you actual component
Maybe its would be helpful.
const onClick = useCallback(({ location: { id, isActive, location } }) => {
setActiveLocation((prevState: PreviousLocation) => {
console.log("previousState", prevState);
return new Map(prevState).set(id, isActive, location);
});
}, []);

ReactJS | Cannot update during an existing state transition

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

Categories