React Hooks And Redux Unable To Edit Form - javascript

I am trying to create an edit form using react and data stored in redux. The form helps a user update their profile but my form seems to be read only. The values i pass to the form are displaying well, but nothing happens when i change the input and try to update the state. Any recommendations/assistance on how to update the previous values stored will be appreciated.
My code :
export default function UpdateProfileSection() {
const dispatch = useDispatch();
const [name, setName] = React.useState("");
const [username, setUserName] = React.useState("");
const [email, setEmail] = React.useState("");
const { user: currentUser } = useSelector((state) => state.auth);
if(!currentUser) {
return <Redirect to="/login-page" />;
}
let nme = currentUser.name;
let em = currentUser.email;
let uname = currentUser.username;
let id = currentUser.id;
const handleProfileUpdate = (e) => {
setshowloader(true)
dispatch(update_profile(name, username, email, role, id))
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error)
})
}
return (
<div>
<Label as="a" color="red" size="huge" ribbon style={{ marginBottom: 50 }}>
Update Profile
</Label>
<CustomInput
id="first"
formControlProps={{
fullWidth: true
}}
inputProps={{
value: nme,
type: "text",
onChange: event => {
const value = event.target.value;
setName(value);
}
}}
/>
<CustomInput
labelText="User Name..."
id="user"
formControlProps={{
fullWidth: true
}}
inputProps={{
value: uname,
type: "text",
onChange: event => {
const value = event.target.value;
setUserName(value);
}
}}
/>
<CustomInput
labelText="Email..."
id="Email"
formControlProps={{
fullWidth: true
}}
inputProps={{
value: em,
type: "text",
onChange: event => {
const value = event.target.value;
setEmail(value);
}
}}
/>
<GridItem style={{ textAlign: "center", marginTop: 10 }}>
<Button
color="danger"
size="lg"
onClick={handleProfileUpdate}>
Save Changes
</Button>
</GridItem>
</div>
);
}

The code use nme as value, and this value is the current user in the store. When you write on the input, the value doesn't change, it still being the current user info.
You can try to make an initialization by using useEffect hook, as the following:
I am trying to create an edit form using react and data stored in redux. The form helps a user update their profile but my form seems to be read only. The values i pass to the form are displaying well, but nothing happens when i change the input and try to update the state. Any recommendations/assistance on how to update the previous values stored will be appreciated.
My code :
export default function UpdateProfileSection() {
const dispatch = useDispatch();
const [name, setName] = React.useState("");
const [username, setUserName] = React.useState("");
const [email, setEmail] = React.useState("");
const { user: currentUser } = useSelector((state) => state.auth);
if(!currentUser) {
return <Redirect to="/login-page" />;
}
useEffect( () => {
setName(currentUser.name);
setEmail(currentUser.email);
setUserName(currentUser.username);
}, [])
let id = currentUser.id;
const handleProfileUpdate = (e) => {
setshowloader(true)
dispatch(update_profile(name, username, email, role, id))
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error)
})
}
return (
<div>
<Label as="a" color="red" size="huge" ribbon style={{ marginBottom: 50
}}>
Update Profile
</Label>
<CustomInput
id="first"
formControlProps={{
fullWidth: true
}}
inputProps={{
value: name,
type: "text",
onChange: event => {
const value = event.target.value;
setName(value);
}
}}
/>
<CustomInput
labelText="User Name..."
id="user"
formControlProps={{
fullWidth: true
}}
inputProps={{
value: username,
type: "text",
onChange: event => {
const value = event.target.value;
setUserName(value);
}
}}
/>
<CustomInput
labelText="Email..."
id="Email"
formControlProps={{
fullWidth: true
}}
inputProps={{
value: email,
type: "text",
onChange: event => {
const value = event.target.value;
setEmail(value);
}
}}
/>
<GridItem style={{ textAlign: "center", marginTop: 10 }}>
<Button
color="danger"
size="lg"
onClick={handleProfileUpdate}>
Save Changes
</Button>
</GridItem>
</div>
);
}
Note that the useEffect is executed only one time when the component is mounted, and use the state values to put in the inputs. This can fix the error

Related

Material UI form validation issues

Im using Material UI to create a new user..the nonprofit is optional. But I'm having an issue with validating the form then breaking out of the validation so I can send the users data to axios POST request. any help would be appreciated.
Im not sure how to refactor this code to allow me to do this. I tried a custom hook and made a new file but I just got all confused.
const useStyles = makeStyles((theme) => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(1),
width: 288,
borderRadius: 8,
},
},
button: {
width: 288,
borderRadius: 8,
},
}));
// eslint-disable-next-line react/prop-types
export default function CreateAccountMuiForm() {
const classes = useStyles();
const [emailError, setEmailError] = useState('');
const [passwordError, setPasswordError] = useState('');
// const [nonProfitError, setNonProfitError] = useState('');
const [showPassword, setShowPassword] = useState(false);
const handleClickShowPassword = () => setShowPassword(!showPassword);
const handleMouseDownPassword = () => setShowPassword(!showPassword);
const [values, setValues] = useState({
email: '',
password: '',
nonProfit: '',
});
const handleChange = (e) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value,
});
};
const handleSubmit = async (e) => {
e.preventDefault();
// sets intial state to false
setEmailError(false);
setPasswordError(false);
// // form validation happens here after submited
// // this checks to make sure a valid email is input
if (values.email === '') {
setEmailError(true);
} else if (!/\S+#\S+\.\S+/.test(values.email)) {
setEmailError(true);
}
// this checks to make sure password is greater than 5
if (values.password === '') {
setPasswordError(true);
} else if (values.password.length < 5) {
setPasswordError(true);
}
// this checks to make sure both validations are true
if (values.email && values.password) {
// eslint-disable-next-line no-console
console.log(values);
}
return
};
return (
<FormControl>
<form className={classes.root} noValidate autoComplete="off" onSubmit={handleSubmit}>
<div>
<TextField
name="email"
id="outlined-password-input"
placeholder="Email"
type="email"
autoComplete="current-email"
variant="outlined"
required
error={emailError}
value={values.email}
onChange={handleChange}
// onChange={(e) => setEmail(e.target.value)}
/>
<TextField
name="password"
id="password"
placeholder="password"
type={showPassword ? 'text' : 'password'}
autoComplete="current-password"
variant="outlined"
required
error={passwordError}
value={values.password}
onChange={handleChange}
// onChange={(e) => setPassword(e.target.value)}
InputProps={{ // <-- This is where the toggle button is added.
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
>
{showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
),
}}
/>
<TextField
name="nonProfit"
id="nonProfit"
placeholder="Select you nonprofit (optional)"
type="text"
autoComplete="none"
variant="outlined"
// onChange={(e) => setNonProfit(e.target.value)}
value={values.nonProfit}
onChange={handleChange}
// error={nonProfitError}
/>
<Button
variant="contained"
type="submit"
className={classes.button}
>
Create an account
</Button>
<p className="terms-of-use">
By creating an account you agree to our
<Link to="/termsofuse" style={{ textDecoration: 'none', color: '#909090' }}>
<strong> Terms of use</strong>
</Link>
</p>
</div>
</form>
</FormControl>
);
}
TL; DR: Follow low coupling and high cohesion principles: separate the state values and pair them with their error states so you can validate via hooks.
NL; PR: Excellent question, this is often overlooked in tutorials. In this pastebin you can observe a way to do so. These are the relevant snippets:
Don't
High coupling: unnecessary dependency among state values:
const [values, setValues] = useState({
email: '',
password: '',
nonProfit: '',
});
Low cohesion: error state is derived from another value:
const [emailError, setEmailError] = useState('');
Do
Low coupling: separate state handling for each value, high cohesion: email value and error are handled in one place (a hook).
const useEmailValidator = () => {
const [value, setValue] = useState("");
const [emailError, setEmailError] = useState(false);
const handleAndValidateEmail = useCallback((value) => {
setEmailError(!(value && /\S+#\S+\.\S+/.test(value)));
setValue(value);
}, []);
return [value, handleAndValidateEmail, emailError];
};
Try refactoring the other state values and errors using validator hooks.
Finally, if you are feeling adventurous, consider the DRY principle and try form management libraries.

Getting undefined values from Input Value in ReactJS

I am unable to get the values of email and password from the FormControl Input values. I am using the BaseWeb ReactUI framework for the field UI.
Need help stuck on this issue.
import { FormControl } from 'baseui/form-control';
import { Input } from 'baseui/input';
import { useStyletron } from 'baseui';
import { Alert } from 'baseui/icon';
import { Button } from 'baseui/button';
import { TiUserAddOutline } from "react-icons/ti";
import Axios from "axios";
import { useHistory, Link } from 'react-router-dom';
import { validate as validateEmail } from 'email-validator'; // add this package to your repo with `$ yarn add email-validator`
import { Select } from 'baseui/select';
import { Checkbox } from 'baseui/checkbox';
function SelectAtStart(props) {
const [css] = useStyletron();
return (
<div className={css({ display: 'flex' })}>
<div className={css({ width: '200px', paddingRight: '8px' })}>
<Select
options={props.options}
labelKey="id"
valueKey="gender"
onChange={({ value }) => props.onSelectChange(value)}
value={props.selectValue}
id={props.id}
/>
</div>
<Input
onChange={e => props.onInputChange(e.target.value)}
value={props.inputValue}
/>
</div>
);
}
function Negative() {
const [css, theme] = useStyletron();
return (
<div
className={css({
display: 'flex',
alignItems: 'center',
paddingRight: theme.sizing.scale500,
color: theme.colors.negative400,
})}
>
<Alert size="18px" />
</div>
);
}
export function RegisterFields() {
const history = useHistory();
const [css, theme] = useStyletron();
const [email, setEmail] = React.useState('');
const [password, setPassword] = React.useState('');
const [checked, setChecked] = React.useState(true);
const [startInputValue, setStartInputValue] = React.useState('');
const [startSelectValue, setStartSelectValue] = React.useState(
[],
);
const [isValid, setIsValid] = React.useState(false);
const [isVisited, setIsVisited] = React.useState(false);
const shouldShowError = !isValid && isVisited;
const onChangeEmail = ({ target: { email } }) => {
setIsValid(validateEmail(email));
setEmail(email);
};
const onChangePassword = ({ target: { password } }) => {
setIsValid(true);
setPassword(password);
}
const handleSubmit = (event) => {
event.preventDefault();
console.log(email, password)
Axios.defaults.headers.common = {
"Content-Type": "application/json"
}
Axios.post("http://localhost:5000/api/signup", {
email: email,
password: password,
firstName: startInputValue,
lastname: startInputValue,
agreement: checked
}).then((res) => {
if (res.status === 200) {
const path = '/dashboard'
history.push(path)
console.log(res)
}
else {
console.log("Unable to create account")
}
})
}
return (
<form
onSubmit={handleSubmit}
className={css({
marginTop: theme.sizing.scale1000,
})}
>
<FormControl
label="Your Email"
error={
shouldShowError
? 'Please input a valid email address'
: null
}
>
<Input
id="email"
value={email}
onChange={onChangeEmail}
onBlur={() => setIsVisited(true)}
error={shouldShowError}
overrides={shouldShowError ? { After: Negative } : {}}
type="email"
required
/>
</FormControl>
<FormControl
label="Your Password"
error={
shouldShowError
? 'Your password is incorrect'
: null
}
>
<Input
id="password"
value={password}
onChange={onChangePassword}
onBlur={() => setIsVisited(true)}
error={shouldShowError}
overrides={shouldShowError ? { After: Negative } : {}}
type="password"
required
/>
</FormControl>
<FormControl
label="Your Full Name"
>
<SelectAtStart
inputValue={startInputValue}
onInputChange={v => setStartInputValue(v)}
selectValue={startSelectValue}
onSelectChange={v => setStartSelectValue(v)}
options={[
{ id: 'Mr', gender: 'Male' },
{ id: 'Mrs', gender: 'Women' },
{ id: 'Ms', gender: 'Female' },
{ id: 'None', gender: 'Dont Say' },
]}
id="start-id"
/>
</FormControl>
<FormControl>
<Checkbox
checked={checked}
onChange={() => setChecked(!checked)}
>
<div className={css({
color:'grey'
})}
>I hereby agree to the terms and conditons of the platform.</div>
</Checkbox>
</FormControl>
<Button type="submit">
<TiUserAddOutline style={{ marginRight: '10px' }} />
Create Account
</Button>
<div>
<p style={{ marginTop: '10px', color: 'grey' }} >To go back to login, please <Link to='/'>click here</Link></p>
</div>
</form>
);
}
I believe it's because you keep setting it to the email/password value of target
which is "undefined".
e.target.email = undefined
e.target.password = undefined
Try with this approach:
const onChangeEmail = e =>
{
setIsValid(validateEmail(e.target.value));
setEmail(e.target.value);
};
const onChangePassword = e =>
{
setIsValid(true);
setEmail(e.target.value);
};
Here you can see that all text inputs are stored in a value of targeted event ( event.target.value )

Not getting pre-filled state value from redux when user try to edit the form?

I am trying to get the state from redux store and trying to fill the input field from state. If user in edit mode. In edit mode, we normally show the prefilled value in input field. But what is wrong with the below approach?
I am able to store single user successfully in reducer but in component i am not getting. Sometimes i get the value. Overall, it's very inconsistent.
import React, { useState, useEffect } from "react";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
import Button from "#material-ui/core/Button";
import { useSelector, useDispatch } from "react-redux";
import { useHistory, useParams } from "react-router-dom";
import { addUser, getSingleUser, updateUser } from "../redux/actions";
const useStyles = makeStyles((theme) => ({
root: {
marginTop: 100,
"& > *": {
margin: theme.spacing(1),
width: "45ch",
},
},
}));
const initialState = {
name: "",
address: "",
contact: "",
email: "",
};
const EditUser = () => {
let { id } = useParams();
const { user } = useSelector((state) => state.users);
console.log("user", user);
const [state, setState] = useState(user);
const [error, setError] = useState("");
const { name, address, email, contact } = state;
const classes = useStyles();
const history = useHistory();
let dispatch = useDispatch();
const onInputChange = (e) => {
let { name, value } = e.target;
setState({ ...state, [name]: value });
};
useEffect(() => {
dispatch(getSingleUser(id));
}, []);
const handlSubmit = (e) => {
e.preventDefault();
console.log("name", name);
if (!name || !email || !address || !contact) {
setError("Please fill all Input Field");
} else {
dispatch(updateUser(state, id));
setError("");
history.push("/");
}
};
return (
<>
<Button
style={{ width: "100px", marginTop: "20px" }}
variant="contained"
type="submit"
color="secondary"
onClick={() => history.push("/")}
>
Go Back
</Button>
<h2>Edit user</h2>
{error && <h3 style={{ color: "red" }}>{error}</h3>}
<form
className={classes.root}
noValidate
autoComplete="off"
onSubmit={handlSubmit}
>
<TextField
id="standard-basic"
label="Name"
value={name}
name="name"
onChange={onInputChange}
type="text"
/>
<br />
<TextField
id="standard-basic"
value={email}
name="email"
label="Email"
type="email"
onChange={onInputChange}
/>
<br />
<TextField
id="standard-basic"
value={contact}
name="contact"
label="Contact"
type="number"
onChange={onInputChange}
/>
<br />
<TextField
id="standard-basic"
label="Address"
value={address}
name="address"
type="text "
onChange={onInputChange}
/>
<br />
<Button
style={{ width: "100px" }}
variant="contained"
type="submit"
color="primary"
>
Update
</Button>
</form>
</>
);
};
export default EditUser;
Below is redux actions logic to get the single user and dispatching an action to store single user value in reducer.
export const getSingleUser = (id) => {
return function (dispatch) {
axios
.get(`${process.env.REACT_APP_API}/${id}`)
.then((resp) => {
console.log("resp", resp);
dispatch(singleUser(resp.data));
})
.catch((error) => console.log(error));
};
};

React component not re-rendering when useEffect dependency changes

PROBLEM:
I am currently creating a react app that allows you to checkout a book to a professor. In this app it has a couple of things that need to obviously update when a user checks out a book.
So first off there is the number of totalBooks that is checked out, or just when the entire book object changes then the component should re-render.
I have a useEffect function that is making an api call to a mongodb database and is accessing a document that will yield a book object in the response to the react app. Here is that useEffect function:
useEffect(() => {
const getBook = async () => {
// console.log(book)
await api.getBookById(props.id).then(async book => {
setBook({...book.data.data})
setCopies([...book.data.data.copies])
var num = 0;
await book.data.data.copies.map(async (copy, index) => {
if(copy.checkedOut){
num++;
}
})
setNumCheckedOut(num)
}).catch(e => {console.log(e)})
}
getBook();
}, [book])
I have even subbed out the book object dependency for something like book.checkedOutCopies. Which should return a number and if that number is different from the last then it should re-render the component. This is however, not the case. No matter what I try I am unable to re-render the component when this document changes. I even created a number called reRender and updated it when the api call to checkout a book finished its call. This would be undesired even if it worked because it would not change for someone who was already on the page, but not on the same computer as the person that clicked the checkout button.
I simply just want this component to re-render when the book object in the mongo db database has changed. Which from my understanding the right way to do it is above. The problem is that even after I successfully checkout a book the state never updates. The number of checked out books on the screen stays static:
Here is a screen shot of what the page looks like:
The green books should turn to red when the update button is clicked and a success status is responded. The Total Checked Out should also change. Neither of these happen.
Here is the book object:
const Book = new Schema(
{
bookName: { type: String, required: true },
bookDesc: { type: String, required: false},
numCheckedOut: { type: Number, required: false },
copiesAvail: {type: Number},
whosChecked: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'prof'
}],
copies: [Copy],
ISBN: { type: Number, required: true },
nextDue: {type: String},
nextProf: {type: Schema.Types.ObjectId, ref: 'prof'}
},
{ timestamps: true },
)
I don't understand why it isn't updating would appreciate any help here is the file in its entirety:
import React, { useState, useEffect, useRef } from 'react'
import api from '../api'
import { InputGroup, Form, FormControl, Container, Button, Col, Row, Toast } from 'react-bootstrap'
import { FaBook } from 'react-icons/fa'
import BookIconFunc from '../components/helperComponents/bookIconFunc'
import Select from 'react-dropdown-select';
import './bookslist.css'
import Axios from "axios";
import BookIcoContext from '../context/BookIconContext';
import DatePicker from "react-datepicker";
import ColoredLine from '../components/helperComponents/ColoredLine'
import CheckoutBookHistroy from '../components/CheckoutBookHistory/CheckoutBookHistory'
import { useHistory } from 'react-router-dom';
const handleCheckout = async (e) => {
e.preventDefault();
}
const topLeftBook = {
marginTop: "1.0rem",
display: "flex",
width:"fit-content",
height: "fit-content",
justifyContent: "left",
flexDirection: "column",
alignItems: "center",
borderRadius: "0px",
border: "2px solid #73AD21",
padding: "0.5rem 2.5rem",
}
const booksRows = {
// marginTop: "4.0rem",
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
padding: "1.0rem",
justifyContent: "left",
// border: "2px solid black",
width: "50%",
marginLeft: "1.0rem"
}
const bottomForm = {
flexGrow: "1"
}
const indBook = {
margin: "0.5rem",
color: "green"
}
const updateButtonStyle = {
display: "flex",
width: "100%",
justifyContent:"center"
}
const topOfPage = {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
verticalAlign: "middle"
}
const bookIcon = {
width: "10.0rem",
}
const bottomOfPage = {
display: "flex",
flexDirection: "row",
marginBottom: "1.0rem",
verticalAlign: "middle",
flexGrow: "1",
marginLeft: "1.0rem"
}
const topForm = {
width: "100%",
marginLeft: "2.0rem",
verticalAlign: "middle",
alignItems: "center",
justifyContent: "center"
}
export default function BooksUpdate(props) {
/* TOP FORM VARIABLES */
const [book, setBook] = useState(null)
const [bookName, setBookName] = useState()
const [desc, setDesc] = useState()
const [ISBN, setISBN] = useState()
const [copies, setCopies] = useState()
const history = useHistory();
/* BOOK ICON CONTEXT STATE */
const [iconState, setIconState] = useState(false)
/* re render state when */
const [reRender, setReRender] = useState(0);
/* BOTTOM FORM VARIABLES */
const [newCopies, setNewCopies] = useState()
const [checkoutCopiesNum, setCheckoutCopiesNum] = useState(0)
const [numCheckedOut, setNumCheckedOut] = useState(0)
const [allProfs, setAllProfs] = useState()
const [profChosen, setProfChosen] = useState()
const [bookicoref, setIcoRefs] = useState([]);
const [dueDate, setDueDate] = useState(new Date());
var anotherRender = 0;
const submitCheckout = async (e) => {
e.preventDefault();
try {
const checkoutBookData = {book, checkoutCopiesNum, profChosen, dueDate};
const checkoutBookRes = await Axios.post("http://localhost:8174/api/book/checkout/" + props.id, checkoutBookData)
if(checkoutBookRes.statusText === 'OK'){
setShowCheckoutToast(!showCheckoutToast)
}
/* Display toast that confirms the book was checked out */
setReRender(reRender+1)
anotherRender = anotherRender + 1
// history.push("/Books/Update/" + props.id)
}
catch (err) {
alert(err)
}
}
const handleSetBook = (bookData) => {
setBook({...bookData})
}
useEffect(() => {
const getBook = async () => {
// console.log(book)
await api.getBookById(props.id).then(async book => {
await handleSetBook(book.data.data)
// setBook(book.data.data)
setCopies([...book.data.data.copies])
var num = 0;
await book.data.data.copies.map(async (copy, index) => {
if(copy.checkedOut){
num++;
}
})
setNumCheckedOut(num)
}).catch(e => {console.log(e)})
}
getBook();
}, [book])
useEffect( () => {
const getProfs = async () => {
await Axios.get('http://localhost:8174/user/professors').then((ps) => {
var array = []
ps.data.data.map((prof, index) => {
array.push({label: prof.name, value: prof, key: prof._id})
})
setAllProfs(array)
})
}
getProfs()
}, [])
/* EFFECT TO CREATE REFS FOR EACH BOOK ICON */
useEffect(() => {
// add or remove refs
copies &&
setIcoRefs(bookicorefs => (
Array(copies.length).fill().map((_, i) => bookicorefs[i] || React.createRef())
))
}, [copies]);
const handleUpdate = () => {
console.log("handling update")
}
const findCheckedOut = (array) => {
var numChecked = 0;
array.filter(arr => {
if(arr.checkedOut){
numChecked = numChecked + 1
}
})
return numChecked
}
const [showCheckoutToast, setShowCheckoutToast] = useState(false)
const toggleCheckToast = () => {
setShowCheckoutToast(!showCheckoutToast)
}
/* EFFECT TO VALIDATE THE INFORMATION INSIDE THE CHECKOUT BOOKS FIELD */
useEffect(() => {
if(!copies){
return
}
if(checkoutCopiesNum > copies.length){
alert("There isn't that much of this book available")
return;
}
// console.log(numCheckedOut)
if(checkoutCopiesNum > (copies.length - numCheckedOut)){
setCheckoutCopiesNum(0)
alert('You cannot checkout that many copies as there is already to many checked out')
return;
}
// for(var i = 0; i < checkoutCopiesNum; i++){
// }
},[checkoutCopiesNum, numCheckedOut])
return (
book ?
copies ?
<div>
<Container>
{/* Show the book icon with the title of the book underneath */}
<div style={topOfPage}>
<div style={topLeftBook}>
<FaBook style={bookIcon} size={200}/>
<h4 className="">{book.bookName}</h4>
</div>
<Form style={topForm}>
<Row>
<Col className="pl-0">
<InputGroup className="mb-3 mt-3">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Book Name</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setBookName(e.target.value)}
defaultValue={book.bookName}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<Form.Group controlId="exampleForm.ControlTextarea1">
<Form.Control as="textarea" rows={5} defaultValue={book.bookDesc}/>
</Form.Group>
</Col>
<Col className="m-0 pr-0">
<InputGroup className="mb-4 mt-3">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">ISBN</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setISBN(e.target.value)}
defaultValue={book.ISBN}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<InputGroup className="mb-4 mt-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Total Copies</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setNewCopies(e.target.value)}
aria-label="Name"
aria-describedby="basic-addon1"
defaultValue={copies.length}
/>
</InputGroup>
<InputGroup className="mb-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon1">Total Checked Out</InputGroup.Text>
</InputGroup.Prepend>
<FormControl onChange={(e) => setNewCopies(e.target.value)}
aria-label="Name"
aria-describedby="basic-addon1"
defaultValue={findCheckedOut(book.copies)}
/>
</InputGroup>
</Col>
<Button style={updateButtonStyle} onClick={handleUpdate}>Update</Button>
</Row>
</Form>
</div>
<Row style={{justifyContent: "space-between", verticalAlign: "middle"}}>
<Toast
show={showCheckoutToast}
onClose={toggleCheckToast}
style={{
position: 'absolute',
top: 0,
right: 0,
}}
>
<Toast.Header>
<img
src="holder.js/20x20?text=%20"
className="rounded mr-2"
alt=""
/>
<strong className="mr-auto">Success!</strong>
</Toast.Header>
<Toast.Body>Successfully Checked out a Book</Toast.Body>
</Toast>
<div style={bottomOfPage}>
<Form style={bottomForm} onSubmit={submitCheckout}>
<InputGroup className="mt-4">
<InputGroup.Prepend>
<InputGroup.Text id="basic-addon4">Checkout Out Copies:</InputGroup.Text>
</InputGroup.Prepend>
<FormControl
onChange={(e) => setCheckoutCopiesNum(e.target.value)}
placeholder={checkoutCopiesNum}
aria-label="Name"
aria-describedby="basic-addon1"
/>
</InputGroup>
<Select
className="mt-4"
style={{width: "100%"}}
name="Select"
required
// loading
searchable
placeholder="To:"
options={allProfs}
onChange={(values) => {setProfChosen(values[0].value)}}
/>
<DatePicker className="mt-4" selected={dueDate} onChange={date => setDueDate(date)} />
<Button type="submit" className="mt-3 w-100">Checkout</Button>
</Form>
</div>
<BookIcoContext.Provider value={{iconState, setIconState}}>
<div style={booksRows} onClick={() => setIconState(true)} onMouseUp={() => setIconState(false)}>
{
copies ? copies.map((copy, index) => {
return <div
key={index}
>
<BookIconFunc
checkedOut={copy.checkedOut}
ref={bookicoref[index]}
>
</BookIconFunc>
</div>
})
:
<div>none</div>
}
</div>
</BookIcoContext.Provider>
</Row>
</Container>
<ColoredLine color="grey" m={20} height={1}/>
<Container>
{/* {book.whosChecked.map(prof => {
// console.log(prof)
// <Col>{prof}</Col>
})} */}
<CheckoutBookHistroy book_id={props.id} book={book} reRender={reRender}></CheckoutBookHistroy>
</Container>
</div>
:
<div>no data</div>
:
<div>no data</div>
)
}

Passing props to modal passes every object

I'm not 100% sure what's going on here. I've got a display component that displays a bunch of cards, using a map based on my database - On the card is an edit button that pops a modal up, passing props over to the edit form.. Here's kinda how it looks:
import React, { useState } from 'react'
import { useQuery, useMutation } from '#apollo/client'
import { GET_ALL_PROJECTS, REMOVE_PROJECT } from '../helpers/queries'
import { makeStyles } from '#material-ui/core/styles'
import DeleteIcon from '#material-ui/icons/Delete'
import EditIcon from '#material-ui/icons/Edit'
import AddForm from './AddForm'
import EditForm from './EditForm'
import AlertMessage from '../Alerts/AlertMessage'
import { Grid, Typography, Card, CardActionArea, CardActions, CardContent, CardMedia, Button, Modal, Backdrop, Fade } from '#material-ui/core'
const useStyles = makeStyles((theme) => ({
modal: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
paper: {
backgroundColor: theme.palette.background.paper,
border: '2px solid #000',
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
}));
const DisplayProjects = () => {
const styles = useStyles()
const [deleteItem] = useMutation(REMOVE_PROJECT)
const { loading, error, data } = useQuery(GET_ALL_PROJECTS)
const [status, setStatusBase] = useState('')
const [resultMessage, setResultMessage] = useState('')
const [addOpen, setAddOpen] = useState(false)
const [editOpen, setEditOpen] = useState(false)
const onDelete = (id, e) => {
e.preventDefault()
deleteItem({
variables: { id },
refetchQueries: [{ query: GET_ALL_PROJECTS }]
}).then(
res => handleSuccess(res),
err => handleError(err)
)
}
// Handles Result of the Delete Operation
const handleSuccess = (res) => {
console.log(res.data.deleteProject.proj_name)
// console.log('success!');
setResultMessage(res.data.deleteProject.proj_name)
setStatusBase({
msg: `Successfully Deleted ${resultMessage}`,
key: Math.random()
})
}
const handleError = (err) => {
console.log('error')
}
//Handles the Modal for Add Project
const handleAddOpen = () => {
setAddOpen(true);
};
const handleAddClose = () => {
setAddOpen(false);
};
//Handles the Modal for Edit Project
const handleEditOpen = () => {
setEditOpen(true);
};
const handleEditClose = () => {
setEditOpen(false);
};
if (loading) return '...Loading'
if (error) return `Error: ${error.message}`
return (
<div>
<div style={{ marginTop: 20, padding: 30 }}>
<Grid container spacing={8} justify='center' alignItems='center'>
{data.projects.map(p => {
return (
<Grid item key={p._id}>
<Card >
<CardActionArea>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<CardMedia
style={{ width: 400, height: 100, paddingTop: 10, }}
component='img'
alt='Project Image'
height='140'
image={require('../../images/html-css-javascript-lg.jpg')}
/>
</div>
<CardContent >
<Typography gutterBottom variant='h5' component="h2">
{p.proj_name}
</Typography>
<Typography component='p'>
{p.description}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button>
<DeleteIcon onClick={e => onDelete(p._id, e)} />
</Button>
<Button onClick={handleEditOpen}>
<Modal
open={editOpen}
onClose={handleEditClose}
closeAfterTransition
BackdropComponent={Backdrop}
className={styles.modal}
>
<Fade in={editOpen}>
<div className={styles.paper}>
<EditForm
id={p._id}
close={handleEditClose}
name={p.proj_name}
desc={p.description}
gh={p.gh_link}
live={p.live_link}
img={p.image_url}
/>
</div>
</Fade>
</Modal>
<EditIcon />
</Button>
</CardActions>
</Card>
{ status ? <AlertMessage key={status.key} message={status.msg} /> : null}
</Grid>
)
}
)}
</Grid>
<Button type='button' onClick={handleAddOpen}>Add Project</Button>
<Modal
open={addOpen}
onClose={handleAddClose}
closeAfterTransition
BackdropComponent={Backdrop}
className={styles.modal}
>
<Fade in={addOpen}>
<div className={styles.paper}>
<AddForm close={handleAddClose} />
</div>
</Fade>
</Modal>
</div>
</div >
)
}
export default DisplayProjects
And here's the form. I've destructured out the props into variables and placed them into a state object called details, so they can be overwritten and submitted to the database..
import React, { useState } from 'react'
import { useParams } from 'react-router-dom'
import { useMutation, useQuery } from '#apollo/client'
import { EDIT_PROJECT, GET_ALL_PROJECTS, GET_PROJECT_BY_ID} from '../helpers/queries'
const AddForm = (props) => {
const params = useParams()
const id = params.toString()
// console.log(id);
const [editProjectItem] = useMutation(EDIT_PROJECT)
const {loading, data, error} = useQuery(GET_PROJECT_BY_ID, {
variables: {
id
},
})
const [details, setDetails] = useState({})
if (loading) return '...Loading';
if (error) return <p>ERROR: {error.message}</p>;
if (!data) return <p>Not found</p>;
setDetails(data.projectById)
console.log(data.projectById)
const submitForm = e => {
e.preventDefault()
try {
editProjectItem({
variables: { id, proj_name, description, gh_link, live_link, image_url},
refetchQueries: [{query: GET_ALL_PROJECTS}]
})
}
catch (err) {
console.log('You Goofed')
}
// setDetails({
// proj_name: '',
// description: '',
// gh_link: '',
// live_link: '',
// image_url: ''
// })
props.close()
}
const changeDetails = (e) => {
setDetails({
...details,
[e.target.name]: e.target.value
})
}
const {_id, proj_name, description, gh_link, live_link, image_url} = details
return (
<div key = {_id}>
<h2>Edit {proj_name}</h2>
<form onSubmit = {submitForm} >
<label>
Project Name:
<input
name = 'proj_name'
value = {proj_name}
onChange = {changeDetails}
/>
</label>
<label>Description</label>
<input
name = 'description'
value = {description}
onChange = {changeDetails}
/>
<label>GitHub Link</label>
<input
name = 'gh_link'
value = {gh_link}
onChange = {changeDetails}
/>
<label>Live Link</label>
<input
name = 'live_link'
value = {live_link}
onChange = {changeDetails}
/>
<label>Preview Image</label>
<input
name = 'image_url'
value = {image_url}
onChange = {changeDetails}
/>
<button type = 'submit'>Submit</button>
</form>
</div>
)
}
export default AddForm
The problem I'm running into, is that when I access the modal, the props are sent from literally EVERY Object, instead of the one, and displays the data for the last record instead of the one I want to edit You can see what happens here (I logged props.id in order to test) https://imgur.com/a/pcEKl89
What did I miss? (Disclaimer: I am still a student, and learning the craft.. be gentle on my code please)
EDIT: I just realized that I didn't indicate that this is the final form of the EditForm component. I haven't added the logic in to make the updates yet, I just wanted to get the data showing properly first.
EDIT2: I made some changes to how the ID is passed over, I was already using React-Router, so I went ahead and made a route to /edit/:id and then using useParams(), I got the ID that way. It seems to be working, however now I'm getting a Too many re-renders message. Updated the AddForm code above to reflect the changes..
I figured out the re-render issue.. it was as simple as dropping the setDetails function into a useEffect Hook:
useEffect(()=> {
if(data){
setDetails(data.projectById)
}
},[data])

Categories

Resources