How to change the state of the child component? - javascript

I am creating a simple application using react and I have two components: Form and Modal. The Modal should be opened when the Form is submitted. How to change the state of the Modal component to achieve this ?
This is the code for the Form.js compent:
import Modal from './Modal'
export default function Form() {
const handleSubmit = (e) => {
e.preventDefault()
}
return (
<form onSubmit={handleSubmit}>
<SuccessModal />
<div className='mt-1'>
...
</form>
)}
This is the code for the Modal.js compent:
export default function Modal() {
const [open, setOpen] = useState(false)
return (
<Transition.Root show={open} as={Fragment}>
<Dialog
as='div'
static
open={open}
onClose={setOpen}
>
...
</Dialog>
</Transition.Root>
I tried to pass this state as a property, but I think I'm doing something wrong. I would be very grateful if any of you could explain to me the principle of how this works.

You can have modal state in Form.js and then pass the state as props to Modal.js.
On Form submit set the modal state.
Form.js
import Modal from './Modal'
export default function Form() {
const [open, setOpen] = useState(false)
const handleSubmit = (e) => {
e.preventDefault()
...
setOpen(true);
}
return (
<>
<form onSubmit={handleSubmit}>
<SuccessModal />
<div className='mt-1'>
...
</form>
{open && <Modal open={open} setOpen={setOpen} />}
</>
)}
Modal.js
export default function Modal({ open, setOpen }) {
return (
<Transition.Root show={open} as={Fragment}>
<Dialog
as='div'
static
open={open}
onClose={setOpen}
>
...
</Dialog>
</Transition.Root>

the state controller useState should be in the parent, not the Modal.
Then you can simply use a prop to define the open state.
One way we do this at my work is creating a custom hook for it:
// stateless modal component
export default function Modal({ open }) {
return (
<Transition.Root show={open} as={Fragment}>
<Dialog
as='div'
static
open={open}
onClose={setOpen}
>
...
</Dialog>
</Transition.Root>
}
// custom hook
export function useModal({ initialState }) {
const [open, setOpen] = useState(initialState)
return {
component: <Modal open={open} />,
setOpen,
}
}
// parent code
import { useModal } from './Modal'
export default function Form() {
const modal = useModal(false)
const handleSubmit = (e) => {
e.preventDefault()
modal.setOpen(true)
}
return (
<form onSubmit={handleSubmit}>
{modal.component}
<div className='mt-1'>
...
</form>
)}

You can try lifting the state of the child (Modal) to the state of the parent (Form). Refer this

You don't use state for show or hide Modal component. Use the props. With props components will be working like this:
import Modal from './Modal'
interface IFormProps {}
interface IFormState {
isModalOpen: boolean
}
export class Form extends React.Component<IFormProps, IFormState> => {
state: IFormState = {
isModalOpen: false,
}
const handleSubmit = (e): void => {
this.setState({ isModalOpen: true })
e.preventDefault()
}
const handleModalClose = (): boolean => {
this.setState({ isModalOpen: false })
}
return (
<form onSubmit={handleSubmit}>
<Modal isOpen={this.state.isModalOpen} handleClose={this.handleModalClose} />
<div className='mt-1'>
...
</form>
)}
interface IModalProps {
isOpen: boolean
handleClose: () => boolean
}
interface IModalState {}
export class Modal extends React.Compnent<IModalProps, IModalState> {
const { isOpen, handleClose } = this.props
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog
as='div'
static
open={isOpen}
onClose={handleClose}
>
...
</Dialog>
</Transition.Root>

Related

React | ForwardedRef using React.Component

I'm creating a custom component in React, and I need to export it using forwardedRef. But when I try, this error occurs:
error
my code:
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>{
ref?: React.RefObject<HTMLButtonElement>;
}
class Button extends React.Component<ButtonProps> {
render() {
const {
ref,
children,
...otherProps
} = this.props;
return (
<button
{...otherProps}
ref={ref}
>
{children}
</button>
)
}
}
const ButtonForwarded = React.forwardRef<ButtonProps>((props, ref) =>
<Button {...props} ref={ref} /> );
ButtonForwarded.displayName = 'Button';
export default ButtonForwarded;
Create the ButtonForwarded component like this:
const ButtonForwarded = React.forwardRef((props: ButtonProps, ref: LegacyRef<Button>) => <Button {...props} ref={ref} /> );

How can I make a reusable component for Material-UI Snackbar?

I have this Alert component to be used just to have a message that says "Successfully submitted" and I'm trying to use thin in a parent component. However, nothing shows in the parent component.
AlertComponent
import React, { useState } from "react";
import { Snackbar, Alert } from "#mui/material";
const AlertComponent = () => {
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen(true);
};
const handleClose = (event, reason) => {
if (reason === "clickaway") {
return;
}
setOpen(false);
};
return (
<div>
{" "}
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success">
Successfully Submitted!
</Alert>
</Snackbar>
</div>
);
};
export default AlertComponent;
Parent Component
const ParentComponent = () => {
const [open, setOpen] = useState(false);
const onSubmit = async (data) => {
//codes to submit the data
setOpen(true); //trigger the alert component
};
return (
<div>
//form here to submit
<AlertComponent open={open} />
</div>
);
};
export default ParentComponent;
How can I fix this? Thank you.
Although #Ghader Salehi commented already the solution but if anyone is not sure how to control the alert from parent here is the code.
AlertComponent.js
import React from "react";
import { Snackbar, Alert } from "#mui/material";
function AlertComponenet(props) {
const { open, handleClose } = props;
return (
<div>
{" "}
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success">
Successfully Submitted!
</Alert>
</Snackbar>
</div>
);
}
export default AlertComponenet;
Parent Component (In my code-sandbox I used App.js)
import React, { useState } from "react";
import "./styles.css";
import AlertComponent from "./AlertComponent";
export default function App() {
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen(true);
};
const handleClose = (event, reason) => {
if (reason === "clickaway") {
return;
}
setOpen(false);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={handleClick}>Show Alert</button>
<AlertComponent open={open} handleClose={handleClose} />
</div>
);
}

Modal not closed

In react.js, I want setShowModal to be false when the Backdrop component is clicked and the Backdrop component to be hidden, but setShowModal is not false and does not even show console.log ('a').
import { useState } from 'react';
import Backdrop from './Backdrop';
import Modal from './Modal';
function Todo(props) {
const [showModal, setShowModal] = useState(false);
function showModalHandler() {
setShowModal(true);
}
function closeModalHandler() {
setShowModal(false);
console.log('a');
}
return (
<div className='card'>
<h2>{props.text}</h2>
<div className='actions'>
<button className='btn' onClick={showModalHandler}>
Delete
</button>
</div>
{showModal && <Modal />}
{showModal && <Backdrop onClick={closeModalHandler} />}
</div>
);
}
export default Todo;
it seems that there was a problem with the binding of the closeModalHandler function. I've made some changes to your code and it is actually closing the modal and dropping the 'a' in the console.
Defining the functions as arrow functions use to help in these cases.
Hope it works to you.
import { useState } from 'react';
function Todo(props) {
const [showModal, setShowModal] = useState(true);
const showModalHandler = () => {
setShowModal(true);
}
const closeModalHandler = () => {
setShowModal(false);
console.log('a');
}
return (
<div className='card'>
<h2>{props.text}</h2>
<div className='actions'>
<button className='btn' onClick={() => showModalHandler()}>
Delete
</button>
</div>
{showModal && <button> ShowModalIsActive</button>}
{showModal && <button onClick={() => closeModalHandler()}>Button</button>}
</div>
);
}
export default Todo;

Opening Modal from different component

CardComponent:
export class Card extends Component<Prop, State> {
state = {
isCancelModalOpen: false,
};
marketService = new MarketService();
deleteMarket = () => {
this.marketService
.deleteMar()
.then((response) => {
})
.catch((error) => {
console.log(error);
});
};
handleModalToggle = () => {
this.setState(({ isCancelModalOpen }) => ({
isCancelModalOpen: !isCancelModalOpen,
}));
};
render() {
const {isCancelModalOpen, isDropdownOpen } = this.state;
const dropdownItems = [
<DropdownItem
key="action"
component="button"
onClick={this.handleModalToggle}
>
Delete
</DropdownItem>
];
return (
{this.CancelModal.map((listing) => (
<DeleteModal handleModal={this.handleModalToggle}
deleteProd = {this.deleteProduct}
description = {listing.description} ></DeleteModal>
))}
);
}
}
DeleteModal Component:
interface Prop {
description: string;
handleModal: Function;
deleteProd: Function;
}
class DeleteModal extends Component<Prop, State> {
state = {
deleteConfirmModel: false
};
render() {
const { deleteConfirmModel } = this.state;
const {description} = this.props;
return (
<Modal
isSmall
title="Confirmation"
isOpen={deleteConfirmModel}
onClose={() => this.props.handleModal}
showClose = {false}
actions={[
<Button
key="confirm"
variant="primary"
onClick={() => this.props.deleteProd}
>
Delete
</Button>,
<Button key="cancel" variant="link" onClick={() => this.props.handleModal}>
Cancel
</Button>
]}
>
{description}
</Modal>
);
}
}
export default DeleteModal;
I want DeleteModal component to get open on click of Dropdown Delete button available in Card component. There is no error in the code, still I am not able to trigger modal on click of Dropdown from Card component. Can anyone help me with what's wrong with the code?
So I found the solution to call the modal from different component:
In the card component: to call deleteModal component:
<DeleteModal
displayModal={deleteModalOpen}
handleModal={this.handleModalToggle}
description="Are you sure you want to delete"
/>
DisplayModal, handleModal,decription will be props in DeleteModal component:
DeleteModal:
interface Prop {
displayModal: boolean;
handleModal: Function;
description: string;
}
<Modal
isSmall
title="Confirmation"
isOpen={this.props.displayModal}
onClose={() => this.props.handleModal()}
showClose={false}
actions={[
<Button
key="cancel"
variant="link"
onClick={() => this.props.handleModal()}
>
Cancel
</Button>,
]}
>
{this.props.description}
</Modal>

How to pass props to one React Class to Another React Class?

I am trying to have a button enabled in a modal when text is entered in an input field. But my form is built in another class and is used in a parent class. How can I pass an onChange method my form component.
Here is my parent component:
import React from 'react';
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle
} from '#material-ui/core';
import CompanyFinancialModalForm from '../CompanyFinancialModalForm/CompanyFinancialModalForm';
interface CompanyFinancialModalState {
addEnabled: boolean;
}
interface CompanyFinancialModalProps {
open: boolean;
onClose: () => void;
}
export class CompanyFinancialModal extends React.Component<
CompanyFinancialModalProps,
CompanyFinancialModalState
> {
constructor(props: CompanyFinancialModalProps) {
super(props);
this.state = {
addEnabled: false
};
}
private enableButton = () => {
this.setState({ addEnabled: true});
}
public render() {
const { open, onClose } = this.props;
const { addEnabled } = this.state;
return (
<>
<Dialog
open={open}
onClose={onClose}
className="company-financial-modal"
>
<DialogTitle id="company-financial-modal-title">
{'Company and Financial Data'}
</DialogTitle>
<DialogContent>
<CompanyFinancialModalForm onChange={this.enableButton}/>
</DialogContent>
<DialogActions>
<Button
id="company-financial-modal-add"
disabled={!addEnabled}
onClick={onClose}
color="primary"
>
Add
</Button>
<Button
id="company-financial-modal-cancel"
onClick={onClose}
color="secondary"
autoFocus={true}
>
Cancel
</Button>
</DialogActions>
</Dialog>
</>
);
}
}
export default CompanyFinancialModal;
Here is my class that my form is in:
import React from 'react';
import axios from 'axios';
import { Form, Field } from 'react-final-form';
import { TextField, Select } from 'final-form-material-ui';
import {
Paper,
Grid,
MenuItem,
} from '#material-ui/core';
export interface IValues {
company_name: string;
critical_technology: [];
}
export interface IFormState {
[key: string]: any;
values: IValues[];
submitSuccess: boolean;
}
export default class CompanyFinancialModalForm extends React.Component<{}, IFormState> {
constructor(props: {}) {
super(props);
this.state = {
company_name: '',
critical_technology: [],
values: [],
submitSuccess: false
};
}
private processFormSubmission = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
this.setState({ loading: true });
const formData = {
company_name: this.state.company_name,
critical_technology: this.state.critical_technology
};
this.setState({
submitSuccess: true,
values: [...this.state.values, formData],
loading: false
});
axios.post(`http://localhost:8081/companies`, formData);
}
private onChange = (e: React.FormEvent<HTMLInputElement>) => {
const { name, value } = e.target;
// other form-related logic
this.props.onChange({ name, value }, e);
}
public render() {
const { submitSuccess, loading } = this.state;
const { onChange } = this.props;
return (
<div>
<Form
onSubmit={this.processFormSubmission}
// validate={this.validateForm}
render={({ handleSubmit,/* reset, submitting, pristine, values*/ }) => (
<form onSubmit={handleSubmit} noValidate>
<Paper style={{ padding: 16 }}>
<Grid container alignItems="flex-start" spacing={2}>
<Grid item xs={6}>
<Field
fullWidth
required
name="companyName"
component={TextField}
type="text"
label="Company Name"
onChange={onChange}
/>
</Grid>
<Grid item xs={12}>
<Field
name="critical_technology"
label="Critical Technology"
component={Select as any}
>
<MenuItem value="hypersonics">Hypersonics</MenuItem>
<MenuItem value="directed_energy">Directed Energy</MenuItem>
<MenuItem value="command_control_and_communications">Command, Control and Communications </MenuItem>
<MenuItem value="space_offense_and_defense">Space Offense and Defense</MenuItem>
<MenuItem value="cybersecurity">Cybersecurity</MenuItem>
<MenuItem value="artificial_intelligence_machine_learning">Artificial Intelligence/Machine Learning</MenuItem>
<MenuItem value="missile_defense">Missile Defense</MenuItem>
<MenuItem value="quantum_science_and_computing">Quantum Science and Computing </MenuItem>
<MenuItem value="microelectronics">Microelectronics</MenuItem>
<MenuItem value="autonomy">Autonomy</MenuItem>
</Field>
</Grid>
</Grid>
</Paper>
</form>
)}
/>
</div>
);
}
}
I want to pass a prop to <CompanyFinancialModalForm /> that enables the add button when the Textfield has text typed into it.
For future reference, it will be more beneficial if you only include the relevant code, because it takes more time to find when scrolling through irrelevant code, anyways:
I'm not 100% clear on what you're looking for, but I'll try to answer what I think I understand. You can add an onChange method on your parent component, and pass that as a prop to the form, and the form can call that function every time it runs it's own onChange method. Below is a simplified version:
class Parent extends Component {
state = {
buttonEnabled: false,
// formInputValue: '', <-- if you need this
};
// - omitting constructor/bind for simplicity for now
onChange({ name, value }, e) {
// your logic to determine whether button is enabled or not
// this is just me guessing what you want to implement
if (value) this.setState({ buttonEnabled: true });
else this.setState({ buttonEnabled: false });
}
render() {
return (
<Fragment>
<YourForm onChange={this.onChange} />
<Button enabled={this.state.buttonEnabled} />
</Fragment>
);
}
}
class YourForm extends Component {
onChange(e) {
const { name, value } = e.target;
// other form-related logic
this.props.onChange({ name, value }, e);
}
}
is this what you're looking for?
You can simply pass a child a reference to a function that exists in the parent and then use the parent's function to validate and enable the button.
Codesandbox Demo
Simplified Code:
function Child (props) {
return (
<input type="text" onChange={props.doIt}/>
)
}
function App() {
const [disabled, setDisabled] = useState(true);
function doIt(e) {
setDisabled(e.currentTarget.value.length === 0);
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Child doIt={doIt} />
<button disabled={disabled}>Add</button>
</div>
);
}

Categories

Resources