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.
Related
quick rundown of code before I ask question (Im using Material UI in react)
this is a container that should just hold chat messages
const ChatContainer = ({ chatMessages }) => {
const classes = useStyles();
return (
<Paper className={classes.chatContainer}>
{chatMessages.map((msg) => (
<ChatMessage key={msg.id} author={msg.author} content={msg.content} />
))}
</Paper>
);
};
export default ChatContainer;
this is a component to send things in this case a chat message
const SendInput = ({ label, onSubmit }) => {
const [inputValue, setInputValue] = useState("");
const classes = useStyles();
const handleChange = (e) => setInputValue(e.target.value);
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(inputValue);
setInputValue("");
};
return (
<form onSubmit={(e) => handleSubmit(e)}>
<TextField
label={label}
placeholder={label}
variant="outlined"
value={inputValue}
onChange={handleChange}
fullWidth
InputProps={{
endAdornment: (
<>
<Divider orientation="vertical" className={classes.divider} />
<IconButton type="submit">
<SendIcon color="primary" />
</IconButton>
</>
),
}}
/>
</form>
);
};
export default SendInput;
this is how im rendering them together
<Box>
<ChatContainer chatMessages={chatMsgs} />
<SendInput label="Send message" onSubmit={handleSubmit} />
</Box
here is what the screen looks like https://gyazo.com/d96744d356ceef81aa06345f0f0c2304
what I want is the ChatContainer to fill up the whitespace and push the input to the bottom of the screen. any help would be appreciated thx
There are multiple ways to achieve this. This question has many of them. I use flexbox approach which is given that answer.
Make the height of root item (Box) 100vh to fill all screen, make it flex, and set its direction as column to show child items vertically.
const useStyles = makeStyles({
root: {
display: "flex",
flexDirection: "column",
height: "100vh"
}
});
export default function App() {
const classes = useStyles();
const handleSubmit = console.log;
return (
<Box className={classes.root}>
<ChatContainer chatMessages={chatMsgs} />
<SendInput label="Send message" onSubmit={handleSubmit} />
</Box>
);
}
Make marginTop property of last item auto to push it bottom.
const useStyles = makeStyles({
root: {
marginTop: "auto"
}
});
const SendInput = ({ label, onSubmit }) => {
const classes = useStyles();
// removed for simplicity
return (
<form onSubmit={(e) => handleSubmit(e)} className={classes.root}>
{/* removed for simplicity */}
</form>
);
};
View at codesandbox.
following is the AddCourse page
AddCourse.js
import React, { useEffect, useState } from 'react';
import { Button, Form, FormGroup, Label, Input, FormText, Container } from 'reactstrap';
import database from '../services/fire';
import { useSelector, useDispatch } from 'react-redux';
import uuid from 'react-uuid';
import '../App.css';
const AddCourse = () => {
const [courseId, setCourseId] = useState('');
const [courseTitle, setCourseTitle] = useState('');
const [courseDesc, setCourseDesc] = useState('');
const dispatch = useDispatch();
const user = useSelector(state => state.auth.user.uid);
useEffect(() => {
document.title = "Add Courses"
}, [])
const addCourse = () => {
const payload = { id: uuid(), courseId:courseId, courseTitle: courseTitle, courseDesc: courseDesc }
const dbcoursesWrapper = database.ref().child(user).child('courses');
// const dbcoursesWrapper = database.ref(`users/${user}/courses`).push(courseId, courseTitle, setCourseDesc);
return dbcoursesWrapper.child(payload.id).update(payload).then(() => {
setCourseId('');
setCourseTitle('');
setCourseDesc('');
dispatch({ type: "ADD_COURSES", payload });
})
}
return (
<div>
<h1 className="text-center my-3">Fill Course Detail</h1>
<Form onSubmit={(e) => {
e.preventDefault(e.target.value);
addCourse();
}}>
<FormGroup>
<label for="UserId">Course Id</label>
<Input
type="text"
value={courseId}
onChange={(e) => setCourseId(e.target.value)}
placeholder="Enter your Id"
name="userId"
id="UserId"
/>
</FormGroup>
<FormGroup>
<label for="title">Course Title</label>
<Input
type="text"
value={courseTitle}
onChange={(e)=> setCourseTitle(e.target.value)}
placeholder="Enter Course Title"
name="title"
id="title"
/>
</FormGroup>
<label for="description">Course Description</label>
<Input
value={courseDesc}
onChange={(e) => setCourseDesc(e.target.value)}
type="textarea"
placeholder="Enter Course Description"
name="description"
id="description"
style={{ height: 150 }}
/>
<Container className="text-center">
<Button color="success" type='submit'>Add Course</Button>
<Button color="warning ml-3">clear</Button>
</Container>
</Form>
</div>
);
};
export default AddCourse;
courses.js here is the update button when i click on it i want it to open the AddCourse page with the same values of the course i want to update not getting any clue how can i do this
import React from 'react';
import {
Card, CardText, CardBody,
CardTitle, Button, Container
} from 'reactstrap';
import database from '../services/fire';
import { useSelector, useDispatch } from 'react-redux';
import { fetchCourse } from '../actions/courses';
import AddCourse from './AddCourse';
const Course = ({course}) => {
const user = useSelector(state => state.auth.user.uid);
const dispatch = useDispatch();
const removeCourse = (id) => {
console.log(id);
const dbtasksWrapper = database.ref().child(user).child('courses');
dbtasksWrapper.child(id).remove().then(() => {
dispatch({ type: 'DELETE_COURSE', id: id })
dispatch(fetchCourse(user));
})
}
return (
<div>
<Card>
<CardBody className="text-center ">
<CardText className="text-center"><h2>CourseID: {course.courseId}</h2></CardText>
<CardTitle className="font-weight-bold text-center"><h1>{course.courseTitle}</h1></CardTitle>
<CardText className="text-center">{course.courseDesc}.</CardText>
<Container className="text-center">
{/* here is the update button and when onclick its goes to add course page with the course vale need to update** */}
<Button color="warning"onClick={}>Update</Button>
<Button color="danger ml-4" onClick={()=>removeCourse(course.id)}>Delete</Button>
</Container>
</CardBody>
</Card>
</div>
);
};
export default Course;
Sorry, not getting your question properly. You are trying to add a course using AddCourse.js component on submitting the form, then you want to display the course ID, Title and Description. In order to do this, you need the following:
1 - localStorage,
2 - Context API or Redux,
3 - Create a new state on your Context API or redux to store the values and pass it down to children components, in your example courses.js
If I understand correctly you want to switch between viewing a course and editing/updating a course?
One way to achieve this is:
const Course = ({ course }) => {
const user = useSelector((state) => state.auth.user.uid);
const dispatch = useDispatch();
const removeCourse = (id) => {
console.log(id);
const dbtasksWrapper = database.ref().child(user).child('courses');
dbtasksWrapper
.child(id)
.remove()
.then(() => {
dispatch({ type: 'DELETE_COURSE', id });
dispatch(fetchCourse(user));
});
};
// state to switch between updating the course and viewing the course
const [isUpdating, setIsUpdating] = useState(false);
return (
<div>
{isUpdating ? (
{/* pass the course down and a callback to close update component */}
<AddCourse course={course} finishUpdate={() => setIsUpdating(false)} />
) : (
<Card>
<CardBody className="text-center ">
<CardText className="text-center">
<h2>CourseID: {course.courseId}</h2>
</CardText>
<CardTitle className="font-weight-bold text-center">
<h1>{course.courseTitle}</h1>
</CardTitle>
<CardText className="text-center">{course.courseDesc}.</CardText>
<Container className="text-center">
{/* Set isUpdating to true */}
<Button color="warning" onClick={() => setIsUpdating(true)}>
Update
</Button>
<Button color="danger ml-4" onClick={() => removeCourse(course.id)}>
Delete
</Button>
</Container>
</CardBody>
</Card>
)}
</div>
);
};
This will change depending on your setup, if this isn't what you wanted please provide some more details about how you would like this to function.
Creating multiple child components, which each can setState of its parent, makes these child components have different versions of history of the state (aka stale state)
To reproduce the error:
create 2 child components by clicking "add new social media"
submit both child components to set parent state
submit the firstly created component again, and now the second component input disappears from the resulting state
Another error:
create 2 child components by clicking "add new social media"
submit the secondly created child component
submit the firstly created child component, and now the second component input disappears from the resulting state
All in all, I want the resulting state to have ALL the data of each components. How can I fix this?
https://codesandbox.io/s/blissful-fog-oz10p
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);
};
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;
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;
The problem you are facing is caused by updateLinks() function closing over the links state.
When you create two AddNewLink components, each of them is passed updateLinks() function. Since initially, links is an empty object, updateLinks() function passed to both instances of AddNewLink, has a closure over the links variable and links at that time refers to an empty object {}. So when you submit the form, as far as updateLinks() function is concerned, links is an empty object. So when merging links with the data passed from AddNewLink component, only the data from the submitted form is saved in the state because links is an empty object.
Solution:
You could use useRef hook to access the latest value of links inside updateLinks() function.
const Admin = () => {
...
const linkRef = useRef();
const updateLinks = (socialMedia, url) => {
linkRef.current = { ...linkRef.current, [socialMedia]: url };
setLinks(linkRef.current);
};
...
};
Also i don't think you need more than one instance of AddNewLink component. You could add more than one social media link in the state with only a single instance of AddNewLink component.
Demo:
I have made a rudimentary recipe searching app in React, the data received from the API is displayed in recipe cards in the Recipe component. I want to add buttons which once click filter the results to display the recipes cards with the Vegan healthLabel.
This is the App component which interacts with the API. I'm stuck on how to get the results to only display the data with a certain label on click.
const App = () =>
const APP_ID = '072f4029';
const APP_KEY = '1e1f9dc0b5c22bdd26363da4bbaa74b8';
const [recipes, setRecipes] = useState([]);
const [search, setSearch] = useState('');
const [query, setQuery] = useState('');
useEffect(() => {
getRecipes();
}, [query])
const getRecipes = async () => {
const response = await fetch(`https://api.edamam.com/search?q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}&from=0&to=12`)
const data = await response.json();
setRecipes(data.hits);
}
const updateSearch = e => {
setSearch(e.target.value);
}
const getSearch = e => {
e.preventDefault();
setQuery(search);
setSearch('');
}
const props = useSpring({ opacity: 1, from: { opacity: 0 } })
return (
<div className='App'>
<div className="header">
<div className="logo">
<img className="knife" src={logo} alt="Logo" />
<h1>Recipe Finder</h1>
</div>
</div>
<form onSubmit={getSearch} className="search-form">
<InputGroup>
<InputGroupAddon addonType="prepend">
<InputGroupText><FontAwesomeIcon icon={faSearch} /></InputGroupText>
</InputGroupAddon>
<Input className="search-bar" type="text" placeholder="Search for recipe..." value={search} onChange={updateSearch} />
</InputGroup>
<Button color="primary" size="sm" className="search-button" type="submit">Search</Button>
</form>
<UncontrolledAlert className="alert" color="info">
sambailey.dev
</UncontrolledAlert>
<div style={props} className="recipes">
{recipes.map(recipe => (
<Recipe
key={recipe.recipe.label}
title={recipe.recipe.label}
theUrl={recipe.recipe.url}
image={recipe.recipe.image}
ingredients={recipe.recipe.ingredients}
source={recipe.recipe.source}
healthLabels={recipe.recipe.healthLabels}
servings={recipe.recipe.yield} />
))}
</div>
</div>
);
}
export default App;
This is the Recipe card component
const Recipe = ({ title, theUrl, image, ingredients, source, healthLabels, servings, deleteRecipe }) => {
const [modal, setModal] = useState(false);
const toggle = () => setModal(!modal);
const down = <FontAwesomeIcon icon={faSortDown} />
const zoom = <FontAwesomeIcon onClick={toggle} className={style.maximise} icon={faSearchPlus} />
const Heart = styled(Checkbox)({
position: 'absolute',
top: 1,
right: 1,
});
return (
<div className={style.recipe}>
<Heart className={style.heart} icon={<FavoriteBorder />} checkedIcon={<Favorite />} name="checkedH" />
<div className={style.top}>
<h6>{title}</h6>
<Badge className={style.badge} color="primary">{source}</Badge>
<p>Serves: <Badge color="primary" pill>{servings}</Badge></p>
<div className={style.imageContainer}>
<img onClick={toggle} src={image} alt='food' />
{zoom}
</div>
<Modal isOpen={modal} toggle={toggle}>
<img src={image} alt="" className={style.maxi} />
</Modal>
</div>
<ol className={style.allergens}>
{healthLabels.map(healthLabel => (
<li>{healthLabel}</li>
))}
</ol>
<div className={style.ingr}>
<p className={style.inghead} id="toggler">Ingredients <Badge color="secondary">{ingredients.length}</Badge> {down}</p>
<UncontrolledCollapse toggler="#toggler">
<ol id="myol">
{ingredients.map(ingredient => (
<li className={style.customList}>{ingredient.text}</li>
))}
</ol>
</UncontrolledCollapse>
<Button className={style.button} outline color="primary" size="sm" href={theUrl} target="_blank">Method</Button>
</div>
<div className={style.info}>
<div className={style.share}>
<WhatsappShareButton url={theUrl}><WhatsappIcon round={true} size={20} /></WhatsappShareButton>
<FacebookShareButton url={theUrl}><FacebookIcon round={true} size={20} /></FacebookShareButton>
<EmailShareButton url={theUrl}><EmailIcon round={true} size={20} /></EmailShareButton>
</div>
</div>
</div >
);
}
export default Recipe;
Your useEffect is already dependent on the query property. To trigger a new fetch, you could set the state of the query parameter to the one you want to fetch:
label onclick pseudocode:
export default function Recipe({ onLabelClick, label }) {
return (
<div onClick={onLabelClick}>
{label}
</div>
);
}
You can then load a Recipe like so:
<Recipe
onLabelClick={() => setQuery("what you want your new query to be")
label={recipe.recipe.label}
/>
When clicked, the query property will be updated and the useEffect will be triggered as a result. This will lead to a new fetch!
[EDIT] The OP asked also for an example on how to filter already loaded recipes:
// Let's assume a recipe has a property "title"
const [recipes, setRecipes] = useState([]);
const [filter, setFilter] = useState("");
const [filteredRecipes, setFilteredRecipes] = useState([]);
useEffect(() => {
if (filter) {
const newFilteredRecipes = recipes.filter(recipe => recipe.title.toLowerCase().includes(filter.toLowerCase()));
setFilteredRecipes(newFilteredRecipes);
}
}, [recipes, filter]);
return (
<>
{filteredRecipes.map((recipe, index) => {
return <Recipe
key={index}
onLabelClick={() => setQuery("what you want your new query to be")
label={recipe.recipe.label}
/>
}
}
</>
);
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.