Updating state and backend - javascript

I'm wondering on the best way to do a HTTP PUT with my react application. I have a Post component that fetches data from https://jsonplaceholder.typicode.com/posts/1 and displays the data.
I have another component EditPost that when clicked on button "Edit" displays a dialog where the user can edit the post. The current data is sent to EditPost with a prop.
Questions
Is it best to hold the state in the Post component?
If so, should the update of state and HTTP PUT call be placed in the Post component
How can I update eg. title and not the other attributes of post?
Post component
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import Hello from './Hello';
import axios from "axios";
function Post() {
const [post, setPost] = useState();
useEffect(() => {
const fetchData = async () => {
try {
const result = await axios(
"https://jsonplaceholder.typicode.com/posts/1"
);
setPost(result.data);
} catch (error) {console.log(error)}
};
fetchData();
}, []);
return(
<div>
<h1>{post? post.id: ""}</h1>
<h1>{post? post.title: ""}</h1>
<h1>{post? post.body: ""}</h1>
<EditPost value={post}/>
</div>)
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Post/>, rootElement);
}
Edit Post component:
import React from "react";
import Button from "#material-ui/core/Button";
import TextField from "#material-ui/core/TextField";
import Dialog from "#material-ui/core/Dialog";
import DialogActions from "#material-ui/core/DialogActions";
import DialogContent from "#material-ui/core/DialogContent";
import DialogContentText from "#material-ui/core/DialogContentText";
import DialogTitle from "#material-ui/core/DialogTitle";
export default function EditPost(props) {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
function handleSubmit() {
//Do something
}
return (
<form onSubmit={handleSubmit}>
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Update Post
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Update Post</DialogTitle>
<DialogContent>
<DialogContentText>Update Post</DialogContentText>
<TextField
autoFocus
margin="dense"
id="title"
label="title"
value={props.value.title}
fullWidth
/>
<TextField
autoFocus
margin="dense"
id="body"
label="body"
value={props.value.body}
fullWidth
/>
<TextField
autoFocus
margin="dense"
id="id"
label="id"
value={props.value.id}
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button color="primary" type="submit">
OK
</Button>
</DialogActions>
</Dialog>
</div>
</form>
);
}

Yes it is a good practice to handle the state of your form in the component so to do it that way you will need minor modifications on your code.
You are missing a function to handle the values that you want to modify, so you can update it with your service and have the same state on the backend and on the ui.
First you need a the variables to store your title (and the other props) state, lets make an example with the title.
const [title, setTitle] = useState('');
You also can set the title as the prop of your data that the parent component is requesting:
const [title, setTitle] = useState(props.value.title);
Then you have to create a function that handles the title state.
const handleTitle = ( e ) => {
setTitle(e.target.value);
}
You have to add this function to you TextField component.
<TextField
autoFocus
margin="dense"
id="title"
label="title"
value={title}
onChange={handleTitle}
fullWidth
/>
When you have all yoiur methods that handle the props of your object, in this case is : title, body and id, you will need a method to submit all this data to your service.
const handleSubmit = () => {
const newData = {title: title, id: id, body: body };
//So here you will submit your data , and when the data is successfully submited you will have to update you Parent state to have the same post data in both components, so you will have to pass your 'setPost' method to EditPost Component to be able to do this:
props.setPost(newData)
}
So to pass the method to update the current post you have to do something like this in your EditPost declaration:
<EditPost setPost={setPost} value={post}/>

I think It's best if you had parent component which holds the state of post and http callbacks and 2 child component for view and edit.
const App = props => {
// Post state here
// http callbacks, put get etc.
return(
<PostView {...viewProps}/>
<PostEdit {...editProps}/>
)
}
As for your last question you can use 3rd party form manager such as Formik or react-hooks-form which is helpful mostly because of form validation imo. or you can do it yourself.
If you do it yourself you need to save the inputs state by supplying onChange callback. here's an example:
export default function EditPost(props) {
const [open, setOpen] = React.useStat(false);
const [formState, setFormState] = React.useState({...props.initialValues})
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
function handleSubmit(e) {
e.preventDefault();
// lift formState to <App/>
props.onSubmit(formState.title);
}
const handleChange = e => {
const {name, value} = e.target;
// can do some validation here before saving the state
setFormState(prevState => ({...prevState, [name]: value}))
}
return (
<form onSubmit={handleSubmit}>
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Update Post
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Update Post</DialogTitle>
<DialogContent>
<DialogContentText>Update Post</DialogContentText>
<TextField
autoFocus
margin="dense"
id="title"
label="title"
value={formState.title}
name="title"
onChange={handleChange}
fullWidth
/>
<TextField
autoFocus
margin="dense"
id="body"
label="body"
name="body"
value={formState.body}
fullWidth
/>
<TextField
autoFocus
margin="dense"
id="id"
label="id"
name="id"
value={formState.id}
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button color="primary" type="submit">
OK
</Button>
</DialogActions>
</Dialog>
</div>
</form>
);
}

Related

How to clear a material-ui search input using a button

Working on a tutorial atm that involves react material-ui tables that also has a search input textfield. What I am trying to add to it, is a button that will reset the table report but also clear the search input textfield.
It is the clearing of the search textfield that I am having trouble with.
They are using this code as a separate component library called Controls.input:
import React from 'react'
import { TextField } from '#material-ui/core';
export default function Input(props) {
const { name, label, value,error=null, onChange, ...other } = props;
return (
<TextField
variant="outlined"
label={label}
name={name}
value={value}
onChange={onChange}
{...other}
{...(error && {error:true,helperText:error})}
/>
)
}
The main search code is as follows where I have also added a button
<Controls.Input
id="name"
label="Search Name"
className={classes.searchInput}
InputProps={{
startAdornment: (<InputAdornment position="start">
<Search />
</InputAdornment>)
}}
onChange={handleSearch}
/>
<Button
onClick={handleClear}
className="materialBtn"
>
Clear
</Button>
At this point, I am not sure how to reference/target the search input field as part of the handleClear function, in-order to clear it's contents?
const handleClear = () => {
????
}
Do I need to use useState()?
You are right with having to put the value into state. Based on what you have supplied it seems that your state needs to be in your parent component. So something like this should work
import { useState } from 'react'
const ParentComponent = () => {
const [value, setValue] = useState('')
const handleClear = () => {
setValue('')
}
const handleSearch = (event) => {
setValue(event.target.value)
}
return (
<>
<Controls.Input
id="name"
label="Search Name"
className={classes.searchInput}
value={value}
onChange={handleSearch}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search />
</InputAdornment>
),
}}
/>
<Button onClick={handleClear} className="materialBtn">
Clear
</Button>
</>
)
}

How to sync state in react.js?

I am having difficulty updating my state and my component. After a button is pressed and I change the value of one of the props of the popup component. The value of the props is not updated. I believe that this is one of the side effects of using the setstate. I did some research and saw that there is a way to solve this problem using the useeffect hook but I am unable to receive the result. Here is my code below:
My goal is to get from the form having the prop of Data0 to have a prop of Data1, but the prop does not seem to be updating at all.
I am simulating clicking multiple objects and the result is an update in the value of fromData. Thus, app.js is my parent component. The child component is the popup, whose value should change to Bob and an actual date instead of just string values of the original name and original date.
import React, { useState, useEffect } from 'react';
import './App.css';
import FormDialog from './component/popup'
import Button from '#material-ui/core/Button';
function App() {
const Data0 = { name:'original name', date:'original date' }
const Data1 = { name:'Bob', date:'1939' }
const [formStatus, setformStatus] = React.useState(false);
const [formdata2, setformData2] = useState(Data0)
const [tempform, settempform] = useState(<FormDialog formStatus = {formStatus} handelForm={() => handelForm()} Data0={Data0}/>)
const handelForm = () => {
const tempform = <FormDialog formStatus = {formStatus} handelForm={() => handelForm()} Data0={Data1}/>
settempform(tempform);
};
useEffect(() => {
const tempform = <FormDialog formStatus = {formStatus} handelForm={() => handelForm()} Data0={Data1}/>
settempform(tempform);
setformStatus(!formStatus);
console.log('formdata2 EFFECT', formdata2)
settempform(tempform);
setformStatus(!formStatus);
setformStatus(!formStatus);
}, [formdata2]
);
return (
<div className="App">
<h1 align="center">React-App</h1>
<h4 align='center'>Render Custom Component in Material Table</h4>
<Button variant="outlined" color="primary" onClick={() => handelForm()}>
Vendor row
</Button>
{tempform}
{formdata2.billingVendor}
</div>
);
}
export default App;
export default function FormDialog (props) {
let [Data, setData] = React.useState(props.Data0);
return (
<React.Fragment>
<Dialog
maxWidth='lg'
open={props.formStatus}
aria-labelledby="max-width-dialog-title"
disableBackdropClick= {true}
disableEscapeKeyDown={true}
>
<DialogTitle className={classes.title}>{Data.name}</DialogTitle>
<Divider />
<DialogContent>
<DialogContentText>
Please be cautious when updating the fields below.
</DialogContentText>
<form noValidate>
<FormControl className={classes.formControl} fullWidth= {true}>
<div className={classes.root}>
<TextField
fullWidth
label='Date'
style={{ margin: 8 }}
disabled
value={Data.name}
variant="outlined"
/>
<br/>
<TextField
fullWidth
label='Date'
style={{ margin: 8 }}
disabled
value={Data.name}
variant="outlined"
/>
<br/>
<TextField
fullWidth
style={{ margin: 8 }}
disabled
value={Data.date}
variant="outlined"
/>
<br/>
<TextField
fullWidth
style={{ margin: 8 }}
disabled
value={Data.date}
variant="outlined"
/>
<br/>
</div>
</FormControl>
</form>
</DialogContent>
<DialogActions>
<Button onClick={() => props.handelForm()} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}```
I think the process you are following is not a good one. You shouldn't store a react component in the state rather you should dynamically load the component or pass what prop you need.
import React, { useState, useEffect } from 'react';
import './App.css';
import FormDialog from './component/popup'
import Button from '#material-ui/core/Button';
function App() {
const Data0 = { name:'original name', date:'original date' }
const Data1 = { name:'Bob', date:'1939' }
const [formStatus, setformStatus] = React.useState(false);
const [formdata2, setformData2] = useState(Data0)
const [formData, setFormData] = useState(Data0)
const handelForm = () => {
// here change the state however you want
setFormData(Data0);
};
return (
<div className="App">
<h1 align="center">React-App</h1>
<h4 align='center'>Render Custom Component in Material Table</h4>
<Button variant="outlined" color="primary" onClick={() => handelForm()}>
Vendor row
</Button>
<FormDialog formStatus = {formStatus} handelForm={() => handelForm()} Data0={formData}/>
</div>
);
}
export default App;
In the FormDialog add the useEffect to perform the change
useEffect(() => {
setData(props.Data0)
}, [props.Data0])
This is update the state with the changes
You are creating a new state based on the value of the props in your child component, which is independent to the state in the parent component. So a change in the child cannot be passed back to the parent.
To fix it,
create the state in your parent component by
const [Data0, setData0] = useState({ name:'original name', date:'original date' })
pass the setState function to change the value in the parent component to your children by
const tempform = <FormDialog formStatus = {formStatus} handelForm={() => handelForm()} Data={Data1} setData={setData0}/>
change the value in your child component accordingly
let {Data, setData} = props;
Then the call of setData should be calling the one in your parent component and it should be able to update the value accordingly.

How to pass state from child to parent with Hooks

I am new to react and I am trying to build a website where the user can write a query in a searchbar and get some results back.
I created two components: Searchform (parent) and Searchbar (child).
I know how to update the state of the child:
function Searchbar() {
const [query, setQuery] = useState("");
return(
<TextField
defaultValue=""
placeholder="Search"
label="Search bar"
fullWidth
onChange={(event)=>setQuery(event.target.value)}
/>
)
}
However, I am not sure how to send this new state to the parent.
function Searchform() {
function handleSubmit() {
console.log(query)
}
const [query, setQuery] = useState("");
return(
<form onSubmit={handleSubmit}>
<div className="searchbar">
<Searchbar /> # I think I have to do something here but not sure what.
</div>
<Button variant="contained" color="primary" type="submit">
Submit your search
</Button>
</form>
)
}
You need to maintain the query in the parent and pass value and onChange to child:
function Searchbar({ value, onChange }) {
return (
<TextField
placeholder="Search"
label="Search bar"
fullWidth
value={value}
onChange={onChange}
/>
);
}
function Searchform() {
function handleSubmit() {
console.log(query);
}
const [query, setQuery] = useState('');
//create onChange function that will never
// change during component life cycle
const onChange = useCallback(
e => setQuery(e.target.value),
[]
);
return (
<form onSubmit={handleSubmit}>
<div className="searchbar">
{/* pass value and onChange */}
<Searchbar value={query} onChange={onChange} />
but not sure what.
</div>
<Button
variant="contained"
color="primary"
type="submit"
>
Submit your search
</Button>
</form>
);
}
Hope this helps,
function Searchform() {
function handleSubmit() {
console.log(query)
}
const [query, setQuery] = useState("");
return(
<form onSubmit={handleSubmit}>
<div className="searchbar">
<Searchbar setQuery={setQuery} />
</div>
<Button variant="contained" color="primary" type="submit">
Submit your search
</Button>
</form>
)
}
function Searchbar({setQuery}) {
// const [query, setQuery] = useState("");
return(
<TextField
defaultValue=""
placeholder="Search"
label="Search bar"
fullWidth
onChange={(event)=>setQuery(event.target.value)}
/>
)
}
State should be hoisted to the top-most container that deals with the state, and then passed down through props (or providers for contexts).
To update the state, you must pass down a callback function (through props) from the parent to child components that allow the child to update the parent's state. The change will then propagate to all children during a re-render.
This is a universal React concept, whether using hooks or classes, see the React documentation
A basic example:
const Parent = () => {
const [query, setQuery] = useState(null);
return <Child onQuery={setQuery} />;
}
const Child = ({ onQuery }) => (
<TextField onChange={event => onQuery(event.target.value)} />
);

Storing a value and duplicating a field onClick

I have a field where a user can input there email address, however i also have a button underneath that field that says 'Add more', I am struggling to find a way to store the email value and have the user input another email, I am using React hooks for this process, for instance I understand that i would need to store the value of the email inside a
const [email, setEmail] = useState=();
below is my code relating to the problem
<Grid item xs={12} sm={6}>
<TextField
className={classes.field}
id="contactEmails"
name="contactEmails"
label="Contact Email(s)"
fullWidth
autoComplete="lname"
inputProps={{
maxLength: 250
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Button className={classes.addButton} variant='contained' color='primary'> Add Another Email</Button>
</Grid>
You don't want to have one stateful email but rather emails (an array of emails):
const [emails, setEmails] = useState([]);
To then add an email, take the previous emails and add one:
setEmails([...emails, "new.email#example.com"]);
Replacing an email works the same way:
setEmails(emails.map(prev => prev === "previous#example.com" ? "new#example.com" : prev));
Now you have to render all those emails too, for that you can map the array of emails into an array of components and return that from your component:
return emails.map(email => <p> {email} </p>);
You might want to have a look at useReducer for this instead though, that'll be far more elegant...
Here you have another option already working: https://jsfiddle.net/hpuey7zn/
Explanation:
In the state you store the current input value (see function handleChange) and an array of emails
onClick button adds the current email to the array if it is not included (see handleClick).
class App extends React.Component {
state = {
input: '',
emails: []
}
handleChange = e => {
this.setState({ input: e.target.value });
}
handleClick = ev => {
ev.preventDefault();
const { emails, input } = this.state;
if (!emails.includes(input))
this.setState({ emails: [...emails, input]});
}
render() {
return (
<div>
<input
name="email" label="email" type="email"
onChange={ this.handleChange }
/>
<button onClick={ev => { this.handleClick(ev)}}>
Add Another Email
</button>
<div>EMAILS:</div>
<ul>{this.state.emails.map(email => <li>{email}</li>)}</ul>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById("root"))
And here you have a version with hooks:
import React, { useState } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [input, setInput] = useState('');
const [emails, setEmails] = useState([]);
return (
<div>
<input name="email" label="email" type="email"
onChange={ ev => {setInput(ev.target.value)} }
/>
<button onClick={ev => {
if (!emails.includes(input)) setEmails([...emails, input])
}}>
Add Another Email
</button>
<div>EMAILS:</div>
<ul>{emails.map(e => <li>{e}</li>)}</ul>
</div>
)
}
const rootElement = document.getElementById("root");
if (rootElement) ReactDOM.render(<App />, rootElement);

Using modal to add a new data to mui-datatable

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.

Categories

Resources