I need to create a new data using a modal box and this is how I implemented it but apparently the new data is not being added in the datatable. Is their a way to do this?
Here is my code:
let id = 0;
function createData(name, provider){
id += 1;
return [id, name, provider];
}
const data = [
createData("Dummy1", "oracle"),
createData("Dummy2", "mssql"),
createData("Dummy3", "oracle"),
];
function ModalBox(props){
const [open, setOpen] = React.useState(false);
const [state, setState] = React.useState({
dname: '',
dsource: '',
data
})
const handleChange = name => e =>{
setState({
...state,
[name]: e.target.value,
})
}
const handleClickOpen = () => {
setOpen(true);
}
const handleClose = () => {
setOpen(false);
}
const addDataSource = () =>{
data.push(createData(state.dname, state.dsource));
setOpen(false);
}
return(
<div>
<Button variant="contained" color="primary" onClick={handleClickOpen}>
Create New
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Subscribe</DialogTitle>
<DialogContent>
<DialogContentText>
To subscribe to this website, please enter your email address here. We will send
updates occasionally.
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
label="Name"
type="text"
value={state.dname || ''}
onChange={handleChange('dname')}
fullWidth
/>
<Select
native
fullWidth
value={state.dsource || ''}
onChange={handleChange('dsource')}
>
<option value="" />
<option value={'mssql'}>mssql</option>
<option value={'oracle'}>oracle</option>
</Select>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={addDataSource} color="primary">
Add
</Button>
</DialogActions>
</Dialog>
</div>
);
}
function TestSource(){
const columns = ["Id", "Name", "Provider"];
const options = {
filterType: 'checkbox',
};
return(
<div className="f-height fx-column-cont">
<MainToolbar/>
<Container>
<ModalBox/>
<MUIDataTable
title={"Test Source"}
data={data}
columns={columns}
options={options}
/>
</Container>
</div>
);
}
export default TestSource;
I think the problem is that I have a global array and I try to push new data inside a function. Is there a way to work around this in? Appreciate any advise you could provide on this.
You could lift the state up to the parent component:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import MUIDataTable from "mui-datatables";
import {
Button,
Select,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
TextField,
DialogActions
} from "#material-ui/core";
import "./styles.css";
function ModalBox(props) {
const [open, setOpen] = useState(false);
const [state, setState] = useState({
dname: "",
dsource: ""
});
const handleChange = name => e => {
setState({
...state,
[name]: e.target.value
});
};
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Button variant="contained" color="primary" onClick={handleClickOpen}>
Create New
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Subscribe</DialogTitle>
<DialogContent>
<DialogContentText>
To subscribe to this website, please enter your email address here.
We will send updates occasionally.
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
label="Name"
type="text"
value={state.dname || ""}
onChange={handleChange("dname")}
fullWidth
/>
<Select
native
fullWidth
value={state.dsource || ""}
onChange={handleChange("dsource")}
>
<option value="" />
<option value={"mssql"}>mssql</option>
<option value={"oracle"}>oracle</option>
</Select>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button
onClick={() => {
props.addDataSource(state.dname, state.dsource);
setOpen(false);
}}
color="primary"
>
Add
</Button>
</DialogActions>
</Dialog>
</div>
);
}
function App() {
const columns = ["Id", "Name", "Provider"];
const [data, setData] = useState([]);
let id = 0;
function createData(name, provider) {
id += 1;
return [id, name, provider];
}
useEffect(() => {
const data = [
createData("Dummy1", "oracle"),
createData("Dummy2", "mssql"),
createData("Dummy3", "oracle")
];
setData(data);
}, []);
const options = {
filterType: "checkbox"
};
const addDataSource = (dname, dsource) => {
const updated = [...data];
updated.push(createData(dname, dsource));
setData(updated);
};
return (
<div className="f-height fx-column-cont">
<div>
<ModalBox
addDataSource={(dname, dsource) => addDataSource(dname, dsource)}
/>
<MUIDataTable
title={"Test Source"}
data={data}
columns={columns}
options={options}
/>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I would also suggest to crate different files for the components and do some refactoring and cleanup :-) Hope that helps.
Related
React beginner on testing, i'm testing form submits, i have tested already few form submits successfully, but this one has 'Accept' button AND 'Delete' button and both uses same 'handleSubmit' function so thats why i have conditions inside that function, depending on which clicked. I need some advice on how to test form submit when
”Accept” button is clicked ?
English is not my mother language so could be mistakes. If any question just ask me.
My test will pass only if i delete one of those buttons but i need both.
test:
import React from "react";
import {
render,
screen,
RenderResult,
cleanup,
fireEvent,
getByRole,
getByDisplayValue,
} from "#testing-library/react";
const onFormSubmit = jest.fn();
const currentCamera = "0-0-0-2";
describe("Testing component", () => {
const AddCamera = () =>
render(
<Provider store={store}>
<MemoryRouter>
<CameraForm onSubmit={onFormSubmit} key={"cameraform-" + currentCamera} />
</MemoryRouter>
</Provider>
);
test("Testing Add Camera", () => {
AddCamera();
const AddName = screen.getByTestId(/^AddName/i);
expect(AddName).toBeInTheDocument();
fireEvent.change(AddName, { target: { value: "Camera 2" } });
expect(AddName).toHaveValue("Camera 2");
fireEvent.submit(screen.getByTestId("renderAddForm"));
expect(onFormSubmit).toHaveBeenCalled();
});
});
code:
import {
...
} from "#material-ui/core/";
const handleSubmit = (event) => {
event.preventDefault();
if (state.button === 1) {
if (!Camera) {
return;
}
const { name} = state;
refresh();
dispatch(
addCamera(site.identifier, Camera.identifier, {
name
})
);
externalOnSubmit();
}
if (state.button === 2) {
event.preventDefault();
if (!Camera) {
return;
}
refresh();
dispatch(
deleteCamera(site.identifier, Camera.identifier)
);
externalOnSubmit();
}
};
const renderAdd = () => {
const { helperText, error, name } = state;
return (
<React.Fragment>
<Box sx={boxStyle} data-testid="CameraForm">
<form
data-testid="renderAddForm"
onSubmit={handleSubmit}>
<div className="handle">
<Box>
<Button
onClick={nulll}
aria-label="close-settings-popup">
<Close />
</Button>
</Box>
</div>
<div>
<FormGroup>
<Box>
<FormControl>
<TextField
helperText={helperText.name}
error={error.name}
inputProps={{
"data-testid": "AddName",
}}
InputProps={{
className: classes.underline,
}}
InputLabelProps={{
className: classes.inputLabelColor,
}}
required={true}
type="text"
id="name"
value={name}
onChange={handleChange}
label='Name'
></TextField>
</FormControl>
</Box>
</FormGroup>
</div>
{!state.readOnly ? (
<div>
<Button
onClick={() => (state.button = 2)}
type="submit"
>
<Trans i18nKey="form.delete">Delete</Trans>
</Button>
<Button
data-testid="submitButton"
onClick={() => (state.button = 1)}
type="submit"
variant="contained"
color="primary"
disabled={
!currentSite ||
Object.values(state.error).some((v) => {
return v === true;
})
}
className={classes.button_basic}
startIcon={<Done />}
>
Accept
</Button>
</div>
) : (
""
)}
</form>
</Box>
</React.Fragment>
);
};
This is my reusable component:
import {
Dialog,
DialogContent,
DialogContentText,
DialogTitle,
Divider,
Button,
DialogActions,
} from "#mui/material";
const Modal = ({ title, subtitle, children, isOpen, handleClose }) => {
return (
<Dialog open={isOpen} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<DialogContentText>{subtitle}</DialogContentText>
<Divider />
{children}
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="error">
Close
</Button>
</DialogActions>
</Dialog>
);
};
export default Modal;
Parent component where I use this here:
const [isOpen, setisOpen] = useState(false);
const [isOpenDialog, setIsOpenDialog] = useState(false);
const handleDialogOpen = () => {
setisOpen(true);
};
const handleDialogClose = () => {
setisOpen(false);
};
const handleOpen = () => {
setIsOpenDialog(true);
};
const handleClose = () => {
setIsOpenDialog(false);
};
<Modal
title="confirmation"
isOpen={isOpen}
children={sample}
handleClose={handleDialogClose}
/>
<Button
color="error"
onClick={
() => handleDialogOpen}
>
Delete
</Button>
It does not show any error in the console and the app does not crash. How can I fix this? Also how can I add a button where the user can say yes since at the moment the modal only have a buttin to close it
There's a lot to unpack here. Here's a solution:
import {
Dialog,
DialogContent,
DialogContentText,
DialogTitle,
Divider,
Button,
DialogActions
} from "#mui/material";
const Modal = ({ title, subtitle, children, isOpen, handleClose }) => {
const handleConfirm = () => {
alert("You Agreed!");
handleClose();
};
return (
<Dialog open={isOpen} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<DialogContentText>{subtitle}</DialogContentText>
<Divider />
{children}
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="error">
Cancel
</Button>
<Button onClick={handleConfirm} color="primary">
Agree
</Button>
</DialogActions>
</Dialog>
);
};
export default Modal;
Here's the parent:
import Modal from "./modal";
import { useState } from "react";
import { Button } from "#mui/material";
export default function App() {
const [isOpen, setisOpen] = useState(false);
const handleOpen = () => {
setisOpen(true);
};
const handleClose = () => {
setisOpen(false);
};
return (
<div className="App">
<Modal title="confirmation" isOpen={isOpen} handleClose={handleClose} />
<Button color="primary" onClick={handleOpen}>
I Agree
</Button>
</div>
);
}
Here's the sandbox:
I have a register page and Modal component. In register has a useState for visibility of the Modal. I'm passing it as a prop to Modal. When the modal is closed how to change the useState value in the register page.
Register page:
import React, { useState } from 'react'
import {
CCard,
CButton,
CCardBody,
CCardHeader,
CCol,
CForm,
CFormInput,
CFormLabel,
CSpinner,
CRow,
} from '#coreui/react'
import CIcon from '#coreui/icons-react'
import { cilSend } from '#coreui/icons'
import Alert from 'src/components/Alert'
import Modal from 'src/components/Modal'
const FormControl = () => {
const [disabled, setDisabled] = useState(false)
const [visible, setVisible] = useState(false)
const [email, setEmail] = useState('')
const [name, setName] = useState('')
const handleAddMember = async () => {
try {
const data = { email, name }
const _data = await fetch('http://localhost:4000/api/v1/member/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + localStorage.getItem('token'),
},
body: JSON.stringify(data),
})
if (_data.status === 201) {
setVisible(true)
setDisabled(false)
} else if (_data.status === 422) {
setDisabled(false)
} else {
setDisabled(false)
throw new Error()
}
} catch (err) {
setDisabled(false)
}
}
return (
<CRow>
<Modal visible={visible} message="Member added to your community successfully!" />
<CCol xs={6}>
<CCard className="mb-4">
<CCardHeader>
<strong>Add New Member</strong>
</CCardHeader>
<CCardBody>
<p className="text-medium-emphasis small">
Fill in the email address field and name field to add a new member to your community.
</p>
<CForm>
<div className="mb-3">
<CFormLabel>Email address:</CFormLabel>
<CFormInput
type="email"
placeholder="name#example.com"
onChange={(e) => {
setEmail(e.target.value)
}}
/>
</div>
<div className="mb-3">
<CFormLabel>Name:</CFormLabel>
<CFormInput
type="text"
placeholder="Perera's Home"
onChange={(e) => {
setName(e.target.value)
}}
/>
</div>
<div className="mb-3">
<CButton color="primary" disabled={disabled} onClick={() => handleAddMember()}>
{disabled ? (
<CSpinner component="span" className="me-2" size="sm" aria-hidden="true" />
) : (
<CIcon icon={cilSend} className="me-2" />
)}
Submit
</CButton>
</div>
</CForm>
</CCardBody>
</CCard>
</CCol>
</CRow>
)
}
export default FormControl
Modal component:
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { CButton, CModal, CModalBody, CModalFooter, CModalHeader, CModalTitle } from '#coreui/react'
const Modal = (props) => {
const [visible, setVisible] = useState(props.visible)
return (
<CModal alignment="center" visible={visible} onClose={() => setVisible(false)}>
<CModalHeader>
<CModalTitle>Success!</CModalTitle>
</CModalHeader>
<CModalBody>{props.message}</CModalBody>
<CModalFooter>
<CButton color="primary" onClick={() => setVisible(false)}>
Close
</CButton>
</CModalFooter>
</CModal>
)
}
Modal.propTypes = {
visible: PropTypes.bool,
message: PropTypes.string,
}
export default React.memo(Modal)
You should have just one visible state member, either in the parent component or in the child (Modal), rather than having it in both places.
If you put it in the parent, you can pass it to the child just like any other prop:
return <Modal visible={visible} setVisible={setVisible}>{/*...*/}</Modal>
Modal's code can then call props.setVisible with the appropriate flag.
If you only want Modal to be able to hide itself (not show itself), you might instead pass a wrapper function that calls setVisible(false):
const hide = useCallback(() => setVisible(false), [setVisible]);
// Optional, see below −−−−−−−−−−−−−^^^^^^^^^^
// ...
return <Modal visible={visible} hide={hide}>{/*...*/}</Modal>
...and then Modal's code calls hide() to hide the modal.
(Making setVisible a dependency in the useCallback call is optional; state setter functions are stable; they don't change during the lifetime of the component. Some linters aren't quite smart enough to realize that and may nag you if you don't include it, but most are smarter than that.)
Here's a highly simplified example:
const {useState} = React;
const Example = () => {
const [visible, setVisible] = useState(false);
return <div>
<input type="button" value="Open" disabled={visible} onClick={() => setVisible(true)} />
<Modal visible={visible} setVisible={setVisible} />
</div>;
};
const Modal = (props) => {
if (!props.visible) {
return null;
}
return <div className="modal">
<div>This is the modal</div>
<input type="button" value="Close" onClick={() => props.setVisible(false)} />
</div>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
.modal {
border: 1px solid grey;
padding: 4px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Or with destructuring (I generally use destructuring with props, but it didn't look like you were):
const {useState} = React;
const Example = () => {
const [visible, setVisible] = useState(false);
return <div>
<input type="button" value="Open" disabled={visible} onClick={() => setVisible(true)} />
<Modal visible={visible} setVisible={setVisible} />
</div>;
};
const Modal = ({visible, setVisible}) => {
if (!visible) {
return null;
}
return <div className="modal">
<div>This is the modal</div>
<input type="button" value="Close" onClick={() => setVisible(false)} />
</div>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
.modal {
border: 1px solid grey;
padding: 4px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
you can pass the setVisible as well in the modal component and then use the same setState on both component
<Modal visible={visible} setVisible={setVisible} message="Member added to your community successfully!" />
use this like
props.visible
props.setVisible
I have a parent component that initializes the state using hooks. I pass in the state and setState of the hook into the child, but whenever I update the state in multiple children they update the state that is not the most updated one.
To reproduce problem: when you make a link and write in your info and click submit, it successfully appends to the parent state. If you add another one after that, it also successfully appends to the parent state. But when you go back and press submit on the first link, it destroys the second link for some reason. Please try it out on my codesandbox.
Basically what I want is a button that makes a new form. In each form you can select a social media type like fb, instagram, tiktok, and also input a textfield. These data is stored in the state, and in the end when you click apply changes, I want it to get stored in my database which is firestore. Could you help me fix this? Here is a code sandbox on it.
https://codesandbox.io/s/blissful-fog-oz10p
and here is my code:
Admin.js
import React, { useState } from 'react';
import Button from '#material-ui/core/Button';
import AddNewLink from './AddNewLink';
const Admin = () => {
const [links, setLinks] = useState({});
const [newLink, setNewLink] = useState([]);
const updateLinks = (socialMedia, url) => {
setLinks({
...links,
[socialMedia]: url
})
}
const linkData = {
links,
updateLinks,
}
const applyChanges = () => {
console.log(links);
// firebase.addLinksToUser(links);
}
return (
<>
{newLink ? newLink.map(child => child) : null}
<div className="container-sm">
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
onClick={() => {
setNewLink([ ...newLink, <AddNewLink key={Math.random()} linkData={linkData} /> ])}
}
>
Add new social media
</Button>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
style={{marginTop: '50px'}}
onClick={() => applyChanges()}
>
Apply Changes
</Button>
<h3>{JSON.stringify(links, null, 4)}</h3>
</div>
</>
);
}
export default Admin;
AddNewLink.js
const AddNewLink = props => {
const [socialMedia, setSocialMedia] = useState('');
const [url, setUrl] = useState('');
const { updateLinks } = props.linkData;
const handleSubmit = () => {
updateLinks(socialMedia, url)
}
return (
<>
<FormControl style={{marginTop: '30px', marginLeft: '35px', width: '90%'}}>
<InputLabel>Select Social Media</InputLabel>
<Select
value={socialMedia}
onChange={e => {setSocialMedia(e.target.value)}}
>
<MenuItem value={'facebook'}>Facebook</MenuItem>
<MenuItem value={'instagram'}>Instagram</MenuItem>
<MenuItem value={'tiktok'}>TikTok</MenuItem>
</Select>
</FormControl>
<form noValidate autoComplete="off" style={{marginBottom: '30px', marginLeft: '35px'}}>
<TextField id="standard-basic" label="Enter link" style={{width: '95%'}} onChange={e => {setUrl(e.target.value)}}/>
</form>
<div className="container-sm">
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
style={{marginBottom: '30px'}}
onClick={() => handleSubmit()}
>
Submit
</Button>
</div>
</>
)
}
export default AddNewLink;
All I see is that links in AddNewLink would be a stale closure but in your question you never use it. Here is your code "working" since you didn't describe what it is supposed to do it always "works"
const { useState } = React;
const AddNewLink = (props) => {
const [socialMedia, setSocialMedia] = useState('');
const [url, setUrl] = useState('');
const { updateLinks, links } = props.linkData;
console.log('links is a stale closure:', links);
const handleSubmit = () => {
updateLinks(socialMedia, url);
};
return (
<div>
<select
value={socialMedia}
onChange={(e) => {
setSocialMedia(e.target.value);
}}
>
<option value="">select item</option>
<option value={'facebook'}>Facebook</option>
<option value={'instagram'}>Instagram</option>
<option value={'tiktok'}>TikTok</option>
</select>
<input
type="text"
id="standard-basic"
label="Enter link"
style={{ width: '95%' }}
onChange={(e) => {
setUrl(e.target.value);
}}
/>
<button
type="submit"
variant="contained"
color="primary"
style={{ marginBottom: '30px' }}
onClick={() => handleSubmit()}
>
Submit
</button>
</div>
);
};
const Admin = () => {
const [links, setLinks] = useState({});
const [newLink, setNewLink] = useState([]);
const updateLinks = (socialMedia, url) =>
setLinks({
...links,
[socialMedia]: url,
});
const linkData = {
links,
updateLinks,
};
const applyChanges = () => {
console.log(links);
// firebase.addLinksToUser(links);
};
return (
<React.Fragment>
{newLink ? newLink.map((child) => child) : null}
<div className="container-sm">
<button
type="submit"
variant="contained"
color="primary"
onClick={() => {
setNewLink([
...newLink,
<AddNewLink
key={Math.random()}
linkData={linkData}
/>,
]);
}}
>
Add new social media
</button>
<button
type="submit"
variant="contained"
color="primary"
style={{ marginTop: '50px' }}
onClick={() => applyChanges()}
>
Apply Changes
</button>
<h3>{JSON.stringify(links, null, 4)}</h3>
</div>
</React.Fragment>
);
};
ReactDOM.render(<Admin />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
It is not a good idea to put jsx in local state, save the data in state instead and pass that to the component every render.
I use Antd. I have Modal Window which consist Form. I want to focus in Input Field when user open the modal widnow. How i can do it in functional component? I try this but in not work for me:
const EditForm = ({visible, widget, onSave, onCancel}) => {
const nameInput = useRef();
useEffect(() => nameInput.current && nameInput.current.focus());
const [form] = Form.useForm();
return (
<div>
<Modal
visible={visible}
title='Edit'
okText='Save'
cancelText='Cancel'
onCancel={onCancel}
onOk={() => {
form
.validateFields()
.then(values => {
form.resetFields();
onSave(values);
})
.catch(info => {
console.log('Validate Failed:', info);
});
}}
>
<Form
{...formItemLayout}
layout={formLayout}
form={form}
>
<Form.Item />
<Form.Item
name='nameWidget'
label='Name'
>
<Input name='nameWidget' ref={nameInput} onChange={handleChangeName} placeholder='Введите новое название' />
</Form.Item>
</Form>
</Modal>
</div>
);
};
try this way.
good luck ;)
import React, {useState, useRef, useEffect} from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Modal, Button, Input, Form } from 'antd';
const App = () => {
const [visible, setVisible] = useState(false)
const myRef = useRef();
/*
* This is the main different
*/
useEffect(()=>{
if (myRef && myRef.current) {
const { input } = myRef.current
input.focus()
}
})
const showModal = () => {
setVisible(true)
};
const handleOk = e => {
setVisible(false)
};
const handleCancel = e => {
setVisible(false)
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal with customized button props
</Button>
<Modal
title="Basic Modal"
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
okButtonProps={{ disabled: true }}
cancelButtonProps={{ disabled: true }}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<Form>
<Form.Item>
<Input ref={myRef} />
</Form.Item>
</Form>
</Modal>
</>
);
}
ReactDOM.render(<App />, document.getElementById('container'));