I have a parent component MasterList and I need to open a modal popup which contains a form to add designation and organization condionally based on activeTab (Designation or Organization) as I have two grids for Designation and Organization. I have ModalContainer component used for modal popup , which has the Input component as child component to add new master data (Deignation or Organization). So here the Input component has input field which losses the focus when I try to enter in the input field. I know this is happening due the re-rendering of the component that's why it is lossing focus, but I want a solution to resolve it. Or the best solution used in this kind of case. I am providing below MasterLst component, ModalContainer, Input component.
MasterLst component:
const MasterList = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const [addMasterData, setAddMasterData] = useState({
designation: { value: '', error: '' },
description: { value: '', error: '' },
organization: { value: '', error: '' },
});
const [addDesignationData, setAddDesignationData] = useState({
designation: { value: '', error: '' },
description: { value: '', error: '' },
});
const [addOrganizationData, setAddOrganizationData] = useState({
organization: { value: '', error: '' },
});
const [activeTab, setActiveTab] = useState('Designation');
const [showModal, setShowModal] = useState(false);
const { mastersByType, loading: masterLoading } = useSelector((state) => state.mastersByType);
const { credentials } = useSelector((state) => state.login);
const resetaddMasterData = useCallback(() => {
setAddMasterData({
designation: { value: '', error: '' },
description: { value: '', error: '' },
organization: { value: '', error: '' },
});
}, []);
const resetAddDesignationData = useCallback(() => {
setAddDesignationData({
designation: { value: '', error: '' },
description: { value: '', error: '' },
});
}, []);
const resetAddOrganizationData = useCallback(() => {
setAddOrganizationData({
organization: { value: '', error: '' },
});
}, []);
const onMasterDataChange = useCallback(
(event) => {
const { name, value } = event.target;
setAddMasterData((prevData) => ({
...prevData,
[name]: { value, error: '' },
}));
},
[setAddMasterData]
);
const onDesignationChange = useCallback(
(event) => {
const { name, value } = event.target;
setAddDesignationData((prevData) => ({
...prevData,
[name]: { value, error: '' },
}));
},
[setAddDesignationData]
);
const onOrganizationChange = useCallback(
(event) => {
const { name, value } = event.target;
setAddOrganizationData((prevData) => ({
...prevData,
[name]: { value, error: '' },
}));
},
[setAddOrganizationData]
);
const handleTabClick = useCallback(
(tab) => {
setActiveTab(tab);
},
[setActiveTab]
);
const inputRef = useRef(null);
useEffect(() => {
if (showModal) {
inputRef.current && inputRef.current.focus();
}
}, [showModal]);
useEffect(() => {
if (credentials) {
const data = {
masterType: activeTab === 'Designation' ? 'Designation' : 'Organization',
};
dispatch(getMastersByType(data));
}
}, [activeTab, dispatch, credentials]);
return (
<PageContainer>
<div className={styles.topContainer}>
<div className={styles.left}>
<label>Master List</label>
</div>
<div className={styles.right}>
<img src="./images/scenario.png" />
</div>
</div>
<div className={styles.mainContainer}>
<div className={styles.mainTopContainer}>
<div className={styles.mainTopLeft}>
<div className={styles.designationsContainer}>
<div className={styles.designationsTop}
onClick={() => handleTabClick('Designation')}
>
<label
style={{
color: activeTab === 'Designation' ?
'var(--primary)' :
'var(--input_label)'
}}
>
Designations
</label>
</div>
<div
className={styles.designationsBottom}
style={{
backgroundColor: activeTab === 'Designation' ?
'var(--primary)' :
'var(--input_label)'
}}
></div>
</div>
<div className={styles.organizationsContainer}
onClick={() => handleTabClick('Organization')}
>
<div className={styles.organizationsTop}>
<label
style={{
color: activeTab === 'Organization' ?
'var(--primary)' :
'var(--input_label)'
}}
>
Organizations
</label>
</div>
<div
className={styles.organizationsBottom}
style={{
backgroundColor: activeTab === 'Organization' ?
'var(--primary)' :
'var(--input_label)'
}}
></div>
</div>
</div>
<div className={styles.mainTopRight}>
<Button
onClick={() => {
setShowModal(true);
}}
>
Add New
</Button>
</div>
</div>
<div className={styles.mainBottomContainer}>
{/* Master List Table:: start */}
<div className={styles.mainTableContainer}>
<table className={styles.table_content}>
<thead>
{activeTab === 'Designation' ?
(
<tr>
<th></th>
<th>#</th>
<th>Designation</th>
<th>Description</th>
<th>Date Created</th>
<th>Scenario</th>
<th>Status</th>
<th></th>
</tr>
) : (
<tr>
<th></th>
<th>#</th>
<th>Organization</th>
<th>Member Users</th>
<th>Date Created</th>
<th>Games Played</th>
<th>Status</th>
<th></th>
</tr>
)}
</thead>
<tbody>
{activeTab === 'Designation' ?
(
mastersByType &&
mastersByType.success &&
mastersByType.data &&
JSON.parse(mastersByType.data)?.map((master, index) => (
<tr key={index}>
<td>
<Checkbox />
</td>
<td>{index + 1}</td>
<td>{master.MasterDisplayName}</td>
<td>Description</td>
<td>1 Jan 2024</td>
<td>5</td>
<td>Active</td>
<td>
<div className={styles.actions}>
<div className={styles.circleSvg}>
<svg>
<use xlinkHref="sprite.svg#edit_icon" />
</svg>
</div>
<div className={styles.circleSvg}>
<svg>
<use xlinkHref="sprite.svg#delete_icon" />
</svg>
</div>
</div>
</td>
</tr>
))
) : (
mastersByType &&
mastersByType.success &&
mastersByType.data &&
JSON.parse(mastersByType.data)?.map((master, index) => (
<tr key={index}>
<td>
<Checkbox />
</td>
<td>{index + 1}</td>
<td>{master.MasterDisplayName}</td>
<td>25</td>
<td>1 Jan 2024</td>
<td>5</td>
<td>Active</td>
<td>
<div className={styles.actions}>
<div className={styles.circleSvg}>
<svg>
<use xlinkHref="sprite.svg#edit_icon" />
</svg>
</div>
<div className={styles.circleSvg}>
<svg>
<use xlinkHref="sprite.svg#delete_icon" />
</svg>
</div>
</div>
</td>
</tr>
))
)
}
</tbody>
</table>
</div>
{/* Master List Table:: end */}
</div>
</div>
{/* Modal Container :: start*/}
{showModal && (
<ModalContainer>
<div className="modal_content">
<div className="modal_header">
<div>
{activeTab === 'Designation' ? 'Add Designation' : 'Add Organization'}
</div>
<div>
<svg
className="modal_crossIcon"
onClick={() => {
setShowModal(false);
// resetAddGroupData();
}}
>
<use xlinkHref={"sprite.svg#crossIcon"} />
</svg>
</div>
</div>
<div className={styles.modalInputContainer}>
{activeTab === 'Designation' ?
(
<div>
<Input
type="text"
customStyle={{ marginTop: '1rem', }}
value={addDesignationData.designation.value}
name={"designation"}
placeholder="Designation Name"
onChange={onDesignationChange}
/>
<Input
type="text"
customStyle={{ marginTop: '1rem', }}
value={addDesignationData.description.value}
name={"description"}
placeholder="Description"
textAreaStyleClass={styles.textAreaStyleClass}
onChange={onDesignationChange}
textArea
/>
</div>
) : (
<div>
<Input
type="text"
customStyle={{ marginTop: '1rem', }}
value={addOrganizationData.organization.value}
name={"organization"}
placeholder="Organization Name"
onChange={onOrganizationChange}
/>
</div>
)
}
</div>
<div className="modal_buttonContainer">
<Button
buttonType={"cancel"}
onClick={() => {
setShowModal(false);
// resetAddGroupData();
}}
>
Cancel
</Button>
<Button
customStyle={{
marginLeft: "1rem",
}}
// onClick={onAddGroup}
>
Add
</Button>
</div>
</div>
</ModalContainer>
)}
{/* Modal Container :: end*/}
</PageContainer>
);
};
export default MasterList;
ModalContainer component:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import styles from "./modal.module.css";
const ModalContainer = ({ children }) => {
const modalRoot = document.getElementById("modal-root");
const modalElement = document.createElement("div");
modalRoot.appendChild(modalElement);
return ReactDOM.createPortal(
<div className={styles.container}>{children}</div>,
modalElement
);
};
export default ModalContainer;
Input Component :
import style from "./input.module.css";
const Input = ({
textArea,
type,
customStyle,
label,
name,
disabled = false,
value = "",
labelStyle = "",
textAreaStyleClass,
onChange = () => {},
ref,
...props
}) => {
return (
<div style={customStyle} className={style.formGroup}>
<label className={labelStyle}>{label}</label>
{textArea ? (
<textarea
disabled={disabled}
name={name}
value={value}
className={`${style.formControl} ${textAreaStyleClass}`}
placeholder={label}
{...props}
onChange={onChange}
/>
) : (
<input
disabled={disabled}
type={type}
name={name}
value={value}
className={style.formControl}
placeholder={label}
ref={ref}
{...props}
onChange={onChange}
/>
)}
</div>
);
};
export default Input;
I tried using autoFocus
, useRef
, but none of them worked.
I am new to React and I don't know how to resolve this.
My one of the collegue suggested to create the container inside the modal in the same file as a component but without exporting and using in the same file i.e MasterList. Any suggestion, solutions would be helpfull.
You are creating new div
and attaching it to #modal-root
on each render. This makes createPortal
to receive new element and that unmounts the previous content and mounts the new one. Something like this should solve this problem:
const ModalContainer = ({ children }) => {
const element = useRef()
// here the element is created only once
if (!element.current) element.current = document.createElement('div')
useLayoutEffect(() => {
const target = document.getElementById('modal-root')
// element is attached to the target only once
target.appendChild(element.current)
return () => {
// remove your created element on unmount
target.removeChild(element.current)
}
}, [])
return createPortal(children, element.current)
}