I am new to Reactjs, I have done a test code to manage different kinds of popups by state, I have a array to hold all popups and stored in state object.
When a popup is displayed, the code will add the popup into the array, and assign an unique handle to it. But when a popup is closed via close button, the popup stored in the array will be removed.
The popup can be displayed via button click, and the array in state is updated correctly. However, when the close button inside the popup is pressed, handleClose callback is called, for some reason, the array was changed to empty which is the initial value.
Could someone kindly help please?
Here is the code:
import React, { useState } from 'react';
import './App.css';
import "bootstrap/dist/css/bootstrap.min.css";
import Notification from './Notifification';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
interface componentType {
item: React.ReactNode;
handle: number;
}
interface stateType {
index: number;
unused?: number;
array: componentType[];
}
const App: React.FC = () => {
const [state, setstate] = useState<stateType>({
index: 1,
array: []
})
const handleClose = (ok: boolean, handle: number) => {
const comp = state.array.find(c => c.handle === handle)
if (comp === undefined) {
//It will run here as the array is empty
console.log("Error: not there, handle = " handle, " array = " state.array)
return
}
const newArray = state.array.filter((value, index, arr) => { return value.handle !== handle; })
console.log("new array in close popup = ", newArray)
setstate({ ...state, array: newArray })
}
const showPopup = () => {
console.log("show popup");
const idx = state.index 1
let comp: componentType = { item: <Notification handleClose={handleClose} title={'TEST'} body={'BODY'} handle={idx}></Notification>, handle: idx }
const newArray = [...state.array, comp]
console.log("new array in show popup = ", newArray)
setstate({ ...state, index: idx, array: newArray })
}
return (
<>
<Form>
<Button className="mr-sm-2" onClick={() => showPopup()}>Popup button</Button>
</Form>
{
//after the Popup button is clicked, the array should be correct, as the popup can be seen
state.array.map((component, index) => {
return React.isValidElement(component.item) ? React.cloneElement(component.item, { key: index }) : component.item
}
)}
</>
);
}
export default App;
Notification.tsx
import React from "react";
import Modal from "react-bootstrap/Modal";
import Button from "react-bootstrap/Button";
export interface NotificationProps {
handleClose: (ok: boolean, handle: number) => void;
title: string;
body: string;
handle: number;
}
const Notification: React.FC<NotificationProps> = ({ handleClose, handle, title, body, ...props }) => {
return (
<Modal
{...props}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
show={true}
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
{title}
</Modal.Title>
</Modal.Header>
<Modal.Body>
{body}
</Modal.Body>
<Modal.Footer>
<Button onClick={() => {
handleClose(true, handle)
}} className="mr-sm-2">OK</Button>
<Button variant="secondary" onClick={() => {
handleClose(false, handle)
}}>Cancel</Button>
</Modal.Footer>
</Modal >
);
}
export default Notification;
CodePudding user response:
The issue here is stale enclosures of state
variable in the handleClose
callback. Don't store instantiated React components in state, store just the data and render the JSX from state. You should also use functional state updates to ensure updating from the previous state and not the state closed over in callback scope.
const App: React.FC = () => {
const [state, setState] = useState<stateType>({
index: 1,
array: []
});
const handleClose = (ok: boolean, handle: number) => {
setState(state => {
if (state.array.find(c => c.handle === handle)) {
return {
...state,
array: state.array.filter(c => c.handle !== handle),
};
}
return state;
});
}
const showPopup = () => {
setState(state => ({
...state,
index: state.index 1,
array: [
...state.array,
{
item: Notification,
title: 'TEST',
body: 'BODY',
handle: state.index 1,
},
],
}));
}
return (
<>
<Form>
<Button className="mr-sm-2" onClick={showPopup}>
Popup button
</Button>
</Form>
{state.array.map(({ item, ...props }, index) => {
const Component = item;
return <Component key={props.handle} handleClose={handleClose} {...props} />;
})}
</>
);
}
Update
However the compiler does not seem to be happy with
const Component = item; return <Component handleClose={handleClose} {...props} />;
, the error message is"TypeScript error in E:/Dev/myapp/src/App.tsx(59,17): JSX element type 'Component' does not have any construct or call signatures"
Component
is already defined in Reactjs, so I changed to component as below:{state.array.map(({ item, ...props }, index) => { const component = item; return <component handleClose={handleClose} {...props} />; })}
but it is having typescript error“Property 'component' does not exist on type 'JSX.IntrinsicElements'. TS2339”
Yeah, seems it's complaining about the type of Component
missing, and then when you changed it to component
it is now no longer a valid React component. Remember, React component names are PascalCased.
You can try giving it another name instead, like Item
and provide the same type definition:
{state.array.map(({ item, ...props }, index) => {
const Item: React.ReactNode = item;
return <Item key={props.handle} handleClose={handleClose} {...props} />;
})}
Or just store it in state with the correct naming convention:
interface componentType {
Item: React.ReactNode;
handle: number;
}
const App: React.FC = () => {
const [state, setState] = useState<stateType>({
index: 1,
array: []
});
...
const showPopup = () => {
setState(state => ({
...state,
index: state.index 1,
array: [
...state.array,
{
Item: Notification,
title: 'TEST',
body: 'BODY',
handle: state.index 1,
},
],
}));
}
...
return (
<>
<Form>
<Button className="mr-sm-2" onClick={showPopup}>
Popup button
</Button>
</Form>
{state.array.map(({ Item, ...props }, index) => (
<Item key={props.handle} handleClose={handleClose} {...props} />
))}
</>
);
}
* Don't forget to include a React key when mapping.