A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled - javascript

This is SignIn Component. I am using Firebase concept. Material UI for designing purpose. I am using functional component.
Is it proper way to authenticate the user?I facing an error.
import React, { useState } from "react";
import Avatar from "#material-ui/core/Avatar";
import Button from "#material-ui/core/Button";
import CssBaseline from "#material-ui/core/CssBaseline";
import TextField from "#material-ui/core/TextField";
import LockOutlinedIcon from "#material-ui/icons/LockOutlined";
import Typography from "#material-ui/core/Typography";
import { makeStyles } from "#material-ui/core/styles";
import Container from "#material-ui/core/Container";
import Firebase from "../services/Firebase";
const Signin = () => {
const [values, setValues] = useState({email: "",password: ""})
const useStyles = makeStyles(theme => ({
paper: {
marginTop: theme.spacing(8),
display: "flex",
flexDirection: "column",
alignItems: "center"
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main
},
form: {
width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(1)
},
submit: {
height: 48,
padding: "0 15px",
margin: theme.spacing(7)
}
}));
const classes = useStyles();
const signin = (e) => {
e.preventDefault();
Firebase.auth().signInWithEmailAndPassword(values.email, values.password).then((u) => {
console.log(u)
}).catch((err) => {
console.log(err);
})
}
const signup = (e) => {
e.preventDefault();
Firebase.auth().createUserWithEmailAndPassword(values.email, values.password).then((u) => {
console.log(u)
}).catch((err) => {
console.log(err);
})
}
const handleChange = (e) => {
setValues({
[e.target.name]: e.target.value
})
}
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form className={classes.form} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
placeholder="Enter Email.."
onChange={(e) => handleChange(e)}
value={values.email}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
placeholder="Enter Password.."
type="password"
onChange={(e) => handleChange(e)}
value={values.password}
/>
<Button
type="submit"
margin="normal"
variant="contained"
color="primary"
className={classes.submit}
onClick={(e) => signin(e)}
>
Sign In
</Button>
<Button
type="submit"
margin="normal"
variant="contained"
color="primary"
className={classes.submit}
onClick={(e) => signup(e)}
>
Sign Up
</Button>
</form>
</div>
</Container>
)
}
export default Signin;
This Firebase Component
import firebase from 'firebase';
require('firebase/app')
require('firebase/auth')
const Firebase = firebase.initializeApp({
apiKey: "AIzaSyC_3KRb7H0Xw1-DGfqAzqfxeZaw3W5PaLg",
authDomain: "my-login-page-react.firebaseapp.com",
databaseURL: "https://my-login-page-react.firebaseio.com",
projectId: "my-login-page-react",
storageBucket: "my-login-page-react.appspot.com",
messagingSenderId: "415587749418",
appId: "1:415587749418:web:ee026252bc0a64c1a57d53"
});
export default Firebase;

This will delete the initial object key-pairs making the value={values.*} in TextField uncontrolled.
setValues({
[e.target.name]: e.target.value
})
To override keeping earlier object key pairs use spread operation -
setValues({...values,
[e.target.name]: e.target.value
})

Problem might be caused by the way you keep values. You handle 2 values with 1 hook. When you call
setValues({
[e.target.name]: e.target.value
})
It probably overrides the previous values which had 2 values with 1 value, either e-mail or password so one of them becomes unidentified and
//This is an uncontrolled component
<TextField
variant="outlined"
margin="normal"
required
fullWidth
placeholder="Enter Email.."
onChange={(e) => handleChange(e)}
value={unidentified}
/>
Try to seperate your values as:
[email,setEmail] : useState("");
[password,setPassword] : useState("")

Related

problem of useState hook while doing it with context

i'm trying to register user on some website and save its data in local storage on every page i need to use context for that, so when i tried to do this without context it all worked and i was saving data in local storage but now i do it with context and it broke and in console.log when i type something in input it shows undefined and useState doesn't work and add items to the object
below code is app.js
import "./App.css";
import * as React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { BrowserRouter } from "react-router-dom";
import Question from "./Question";
import Form from "./Form";
import Language from "./Language";
import Photo from "./Photo";
import Fin from "./Fin";
import CustomizedSteppers from "./Step";
import { createContext, useMemo, useState } from "react";
export const StepperContext = createContext({ step: 0, setStep: () => {} });
export const PagesContext = createContext({ info: {}, setInfo: () => {} });
function App() {
const [info, setInfo] = useState({});
const val = useMemo(() => ({ info, setInfo }), [info]);
const [step, setStep] = useState(0);
const value = useMemo(() => ({ step, setStep }), [step]);
return (
<div className="App">
<StepperContext.Provider value={val}>
<StepperContext.Provider value={value}>
<CustomizedSteppers>
<BrowserRouter>
<Routes>
<Route path="/" index element={<Form />} />
<Route path="Question" index element={<Question />} />
<Route path="Language" index element={<Language />} />
<Route path="Photo" index element={<Photo />} />
<Route path="Finish" index element={<Fin />} />
</Routes>
</BrowserRouter>
</CustomizedSteppers>
</StepperContext.Provider>
</StepperContext.Provider>
</div>
);
}
export default App;
this is the form :
import * as React from "react";
import Box from "#mui/material/Box";
import TextField from "#mui/material/TextField";
import { useState, useContext, createContext,useEffect} from "react";
import MaterialUIPickers from "./Datepicker";
import Stack from "#mui/material/Stack";
import Button from "#mui/material/Button";
import CustomizedSteppers from "./Step";
import { PagesContext, StepperContext } from "./App";
import { useNavigate } from "react-router-dom";
function Form() {
const { step, setStep } = useContext(StepperContext);
const navigate = useNavigate();
const { info, setInfo } = useContext(PagesContext);
console.log(info)
console.log(step)
useEffect(() => {
console.log(info)
}, [info])
const next = () => {
localStorage.setItem("info", JSON.stringify(info));
setStep(1);
navigate("Question");
};
return (
<div className="App">
{/* <CustomizedSteppers /> */}
<Box
component="form"
sx={{
"& > :not(style)": { m: 1, width: "25ch" },
}}
noValidate
autoComplete="off"
/>
<div className="input">
<TextField
color="secondary"
id="outlined-basic"
label="First name"
variant="outlined"
value={info ? info.firstName : ""}
onChange={(e) => {
console.log(info.firstName)
setInfo((prev) => ({ ...prev, firstName: e.target.value }));
}}
/>
<TextField
color="secondary"
id="outlined-basic"
label="Last name"
variant="outlined"
value={info ? info.lastName : ""}
onChange={(e) => {
setInfo((prev) => ({ ...prev, lastName: e.target.value }));
}}
/>
<TextField
id="outlined-number"
type="number"
color="secondary"
placeholder="Phone number"
InputLabelProps={{
shrink: true,
}}
value={info ? info.phoneNumber : ""}
onChange={(e) => {
setInfo((prev) => ({ ...prev, phoneNumber: e.target.value }));
}}
/>
<TextField
id="outlined-basic"
label="City"
color="secondary"
variant="outlined"
type="text"
value={info ? info.city : ""}
onChange={(e) => {
setInfo((prev) => ({ ...prev, city: e.target.value }));
}}
/>
<TextField
id="outlined-basic"
label="Email"
color="secondary"
variant="outlined"
type="email"
value={info ? info.email : ""}
onChange={(e) => {
setInfo((prev) => ({ ...prev, email: e.target.value }));
}}
/>
<MaterialUIPickers></MaterialUIPickers>
</div>
<Button
onClick={next}
style={{ width: "700px" }}
color="secondary"
variant="contained"
>
Next
</Button>
</div>
);
}
export default Form;

How to set initial state with most up to date data returned from API?

I'm making a simple form to edit an app, the initial state of the name & description of the app is set using the data returned from the API.
Currently, when submitting the form the initial data seems to be logging as undefined, the name & description is being set as undefined which occurs in the first render (I have commented in the code where the logs are)
How can I make sure the initial state of name & description has the most up to date information?
Is the excessive renders the problem?
Thanks for taking a look, any help would be appreciated.
import React, { useState, useContext, useEffect } from "react";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import Container from "#material-ui/core/Container";
import SaveIcon from "#mui/icons-material/Save";
import CloseIcon from "#mui/icons-material/Close";
import { makeStyles } from "#material-ui/styles";
import TextField from "#material-ui/core/TextField";
import { Grid } from "#mui/material";
import { useDispatch } from "react-redux";
import { updateApp, updateSelectedApp } from "../../services/reducers/apps";
import { EndpointContext } from "../../baxios/EndpointProvider";
import { useParams } from "react-router-dom";
export default function EditApp() {
const { appid } = useParams();
const classes = useStyles();
const dispatch = useDispatch();
const endpoints = useContext(EndpointContext);
const [selectedApp, setSelectedApp] = useState({});
const [isLoaded, setIsLoaded] = useState(false); // <--- Is there anyway I can also remove this useState? without this the default values in the forms dont populate
useEffect(() => {
async function fetchApp() {
await endpoints.appEndpoints.get(appid).then((response) => {
if (response.status === 200) {
setSelectedApp(response.data);
setIsLoaded(true);
}
});
}
fetchApp();
}, []);
useEffect(() => {
console.log(selectedApp);
}, [selectedApp]);
const [name, setName] = useState(selectedApp.name);
const [description, setDescription] = useState(selectedApp.description);
console.log("---", name, selectedApp.name); // <--- The page renders 3 times, each render log looks like this
// 1st render - --- undefined, undefined
// 2nd render - --- undefined, Appname
// 3rd render - --- undefined, Appname
const handleSubmit = (e) => {
e.preventDefault();
console.log("triggered", name, description); // <--- This logs (triggered, undefined, undefined)
if (name && description) {
const body = { name: name, description: description };
endpoints.appEndpoints.put(selectedApp.id, body).then((response) => {
if (response.status === 200) {
dispatch(updateApp(response.data));
setSelectedApp(response.data);
setName(selectedApp.name);
setDescription(selectedApp.description);
}
});
}
};
return (
<div style={{ margin: 100, marginLeft: 350 }}>
{isLoaded ? (
<Container size="sm" style={{ marginTop: 40 }}>
<Typography
variant="h6"
color="textSecondary"
component="h2"
gutterBottom
>
Edit App
</Typography>
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<TextField
className={classes.field}
onChange={(e) => setName(e.target.value)}
label="App Name"
variant="outlined"
color="secondary"
fullWidth
required
size="small"
defaultValue={selectedApp.name}
error={nameError}
/>
<TextField
className={classes.field}
onChange={(e) => setDescription(e.target.value)}
label="Description"
variant="outlined"
color="secondary"
rows={4}
fullWidth
required
size="small"
defaultValue={selectedApp.description}
error={descriptionError}
/>
<Grid container spacing={2}>
<Grid item>
<Button
// onClick={handleSubmit}
type="submit"
color="primary"
variant="contained"
endIcon={<SaveIcon />}
>
Save
</Button>
</Grid>
</Grid>
</form>
</Container>
) : (
<></>
)}
</div>
);
}
When using const [name, setName] = useState(defaultName), if the defaultName is updated in a future render, then the name value will not be updated to the this latest value.
So in your case you can make the following changes :
const [name, setName] = useState();
const [description, setDescription] = useState();
useEffect(() => {
setName(selectedApp.name)
setDescription(selectedApp.description)
}, [selectedApp])
)
Name and Description are undefined
Your selectedApp is initialized as an empty object. Your useEffect fires a request off to retrieve that data, but the page renders once before it gets the response. There are a couple of ways to handle this. You could do anything from displaying a loading icon on the field, to having a default value for the field until your useEffect with [selectedApp] is called. Once that information is retrieved and sent back, your information will be up to date in state, but if you need to store it for later, you'll need to build out a function to save that data.
Default value:
const [name, setName] = useState(selectedApp.name ?? "Your default value here");
const [description, setDescription] = useState(selectedApp.description ?? "Your default value here");
Loading icon:
{selectedApp ? (
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<TextField
className={classes.field}
onChange={(e) => setName(e.target.value)}
label="App Name"
variant="outlined"
color="secondary"
fullWidth
required
size="small"
defaultValue={selectedApp.name}
error={nameError}
/>
<TextField
className={classes.field}
onChange={(e) => setDescription(e.target.value)}
label="Description"
variant="outlined"
color="secondary"
rows={4}
fullWidth
required
size="small"
defaultValue={selectedApp.description}
error={descriptionError}
/>
<Grid container spacing={2}>
<Grid item>
<Button
// onClick={handleSubmit}
type="submit"
color="primary"
variant="contained"
endIcon={<SaveIcon />}
>
Save
</Button>
</Grid>
</Grid>
</form>
) : <LoadingIconComponent/>}

react-hook-form onSubmit not triggered

import React, { useState } from "react";
import FileBase64 from "react-file-base64";
import { useDispatch } from "react-redux";
import { makeStyles } from "#material-ui/core/styles";
import { TextField, Select, Input, MenuItem, Button } from "#material-ui/core";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import * as yup from "yup";
import { updatePost } from "../actions/post";
const useStyles = makeStyles((theme) => ({
textField: {
marginBottom: theme.spacing(2),
},
buttons: {
marginTop: theme.spacing(2),
},
}));
const tags = ["fun", "programming", "health", "science"];
const postSchema = yup.object().shape({
title: yup.string().required(),
subtitle: yup.string().required(),
content: yup.string().min(20).required(),
tag: yup.mixed().oneOf(tags),
});
const EditPostForm = ({ history, post, closeEditMode }) => {
const dispatch = useDispatch();
const [file, setFile] = useState(post?.image);
const { register, handleSubmit, control, errors, reset } = useForm({
resolver: yupResolver(postSchema),
});
const onSubmit = (data) => {
const updatedPost = {
_id: post._id,
...data,
image: file,
};
dispatch(updatePost(post._id, updatedPost));
reset();
setFile(null);
closeEditMode();
};
const classes = useStyles();
return (
<div>
<form noValidate autoComplete="off" onSubmit={handleSubmit(onSubmit)}>
<TextField
id="title"
label="Başlık"
name="title"
variant="outlined"
className={classes.textField}
size="small"
{...register('title')}
error={errors?.title ? true : false}
fullWidth
defaultValue={post?.title}
/>
<TextField
id="subtitle"
label="Alt Başlık"
name="subtitle"
variant="outlined"
className={classes.textField}
size="small"
{...register('subtitle')}
error={errors?.subtitle ? true : false}
fullWidth
defaultValue={post?.subtitle}
/>
<Controller
render={({field}) => (
<Select
{...field}
input={<Input />}
className={classes.textField}
fullWidth
>
{
tags.map((tag, index) => (
<MenuItem {...field} key={index} value={tag}>
{tag}
</MenuItem>
))
}
</Select>
)}
name='tag'
control={control}
error={errors?.tag ? true : false}
defaultValue={tags[0]}
/>
<TextField
id="content"
label="İçerik"
name="content"
multiline
size="small"
{...register('content')}
rows={16}
className={classes.textField}
variant="outlined"
error={errors?.content ? true : false}
fullWidth
defaultValue={post?.content}
/>
<FileBase64 multiple={false} onDone={({ base64 }) => setFile(base64)} />
<div className={classes.buttons}>
<Button color="primary" variant="outlined" onClick={closeEditMode}>
Vazgeç
</Button>{" "}
<Button color="secondary" variant="outlined" type="submit" >
Kaydet
</Button>
</div>
</form>
</div>
);
};
export default EditPostForm;
I have EditPostForm component, component doesn't give any error but when I tried to submit my form onSubmit function is not triggered.
I used react-hook-form to create my form and I used material UI components inside form.
When I Click button which has type submit does not trigger onSubmit function which is called inside of handleSubmit. Why onSubmit is not triggered?
onSubmit isn't triggered because you may have form errors
You can get errors from formState object (const { formState } = useForm(...))
And then use error={formState.errors?.content ? true : false} in your code
https://react-hook-form.com/api/useform/formstate
See an example here
https://codesandbox.io/s/keen-burnell-2yufj?file=/src/App.js
You need to pass onSubmit and onError both.
Like this:
onPress={handleSubmit(onSubmit, onErrors)}
I faced the same error, the problem was that, my register were Boolean and my input was string, and since the value was not required It didn't show errors until I figure out the problem and change register from Boolean to string

React Material TextArea styling is broken

I am following this example for a basic login screen. Very new to React but I found it to be pretty simple so far.
Unfortunately, as you can see in the screenshot below, the TextAreas look really bad! It seems like the inner padding is messed up.
I'm not sure what I'm doing wrong.
The fields should look like this:
Here is the code:
import React from 'react';
import { FunctionComponent, useState } from 'react';
import Paper from '#material-ui/core/Paper';
import TextField from '#material-ui/core/TextField';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import Button from '#material-ui/core/Button';
import Avatar from '#material-ui/core/Avatar';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import IconButton from '#material-ui/core/IconButton';
import InputAdornment from '#material-ui/core/InputAdornment';
import Visibility from '#material-ui/icons/Visibility';
import VisibilityOff from '#material-ui/icons/VisibilityOff';
import { makeStyles } from '#material-ui/core/styles';
export interface ComponentProps {
email: string;
password: string;
name: string;
showPassword: boolean;
}
const useStyles = makeStyles((theme) => ({
root: {
height: '100vh',
},
paper: {
margin: theme.spacing(8, 4),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
sideImage: {
backgroundImage: '',
backgroundRepeat: 'no-repeat',
backgroundColor: theme.palette.grey[900],
backgroundSize: 'cover',
backgroundPosition: 'center',
},
form: {
marginTop: theme.spacing(1),
width: '100%',
},
logo: {
margin: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
export const ReactLoginComponent: FunctionComponent<ComponentProps> = (props) => {
const classes = useStyles();
const [values, setValues] = useState({
email: '',
password: '',
name: 'Davey Jones', // TODO: un hardcode this!
showPassword: false,
});
const handleChange = (prop) => (event: React.ChangeEvent<HTMLInputElement>) => {
setValues({ ...values, [prop]: event.target.value });
};
const handleClickShowPassword = () => {
setValues({ ...values, showPassword: !values.showPassword });
};
const handleMouseDownPassword = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
};
const login = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
alert(`Welcome ${values.name}`);
};
return (
<Grid container component="main" className={classes.root}>
<Grid item xs={12} sm={8} md={7} lg={4} component={Paper} elevation={0}>
<div className={classes.paper}>
<Avatar
src="../../../assets/img/logo/logo-half.png"
alt="Branding"
className={classes.logo}
/>
<Typography component="h1" variant="h6">
Welcome Back {values.name}
</Typography>
<form noValidate className={classes.form}>
{/* Email */}
<TextField
required
fullWidth
autoFocus
id="email"
variant="outlined"
label="Email"
margin="normal"
type="text"
value={values.email}
/>
{/* Password */}
<TextField
required
fullWidth
id="password"
variant="outlined"
label="Password"
margin="normal"
type={values.showPassword ? 'text' : 'password'}
value={values.password}
onChange={handleChange('password')}
autoComplete="current-password"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="Toggle password visibility"
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
>
{values.showPassword ? <Visibility/> : <VisibilityOff/>}
</IconButton>
</InputAdornment>
),
}}
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary"/>}
label="Remember me"
/>
<Button
fullWidth
type="submit"
variant="contained"
size="large"
className={classes.submit}
onClick={login}
>
Sign in
</Button>
</form>
</div>
</Grid>
<Grid item xs={false} sm={4} md={5} lg={8} className={classes.sideImage}>
</Grid>
</Grid>
);
};
Versions:
"react": "^16.12.0"
"#material-ui/core": "^4.10.2"
"#material-ui/icons": "^4.9.1"
"#material-ui/styles": "^4.10.0"
Edit:
I should've mentioned that I'm using React inside an Angular application that is using Angular Material and has global overrides for certain components. This is probably where the issue is coming from. How can I negate those global changes from this React component specifically? Or could I override them in this component?

Textfields not tracking text

I have a forum setup using react-redux and material ui. All of the text fields seem to not track the change of state this is suppose to track..
So when a user attempts to type in the text field nothing happens.
onChange={e =>
onTextFieldChange("workoutCompleted", e.target.value)
}
here is the rest of the forum code im sure its a real simple fix here.
import React from "react"
import { connect } from "react-redux"
import TextField from "#material-ui/core/TextField"
import Button from "#material-ui/core/Button"
import CustomSnackBar from "../Components/customSnackBar"
import { withStyles } from "#material-ui/core/styles"
import { NEW_WORKOUT_FORM_UPDATED } from "../constants"
import { createNewWorkout } from "../action-creators/events"
const styles = theme => ({
input: {
width: "50%",
marginLeft: 16,
marginTop: 16,
marginBottom: 10,
color: "red"
},
button: {
color: "secondary"
}
})
class NewWorkout extends React.Component {
componentDidMount() {
console.log("componentDidMount!!!")
}
render() {
console.log("RENDER!!!")
const { workout, duration, eventDateTime } = this.props.event
const {
isError,
isSaving,
errorMsg,
classes,
onTextFieldChange,
createWorkout,
history
} = this.props
return (
<div style={{ paddingTop: 56 }}>
<form autoComplete="off" onSubmit={createWorkout(history)}>
<TextField
label="Workout"
value={workout}
onChange={e =>
onTextFieldChange("workoutCompleted", e.target.value)
}
margin="normal"
required
className={classes.input}
/>
<TextField
label="Duration"
value={duration}
onChange={e => onTextFieldChange("Duration", e.target.value)}
margin="normal"
required
className={classes.input}
multiline
/>
<TextField
label="Date"
value={eventDateTime}
clickable="false"
margin="normal"
required
className={classes.input}
/>
<div style={{ paddingTop: 50 }}>
<Button
className={classes.button}
color="red400"
variant="contained"
type="submit"
aria-label="add"
>
SUBMIT
</Button>
</div>
</form>
{isError && <CustomSnackBar message={errorMsg} snackType="error" />}
{isSaving && <CustomSnackBar message="Saving..." snackType="info" />}
</div>
)
}
}
const mapStateToProps = state => {
console.log("What is state?", state)
return {
event: state.newWorkout.data,
isError: state.newWorkout.isError,
isSaving: state.newWorkout.isSaving,
errorMsg: state.newWorkout.errorMsg
}
}
const mapActionsToProps = dispatch => {
return {
onTextFieldChange: (key, value) => {
dispatch({ type: NEW_WORKOUT_FORM_UPDATED, payload: { [key]: value } })
},
createWorkout: history => e => {
e.preventDefault()
dispatch(createNewWorkout(history))
}
}
}
const connector = connect(
mapStateToProps,
mapActionsToProps
)
export default connector(withStyles(styles)(NewWorkout))
onChange={e =>
onTextFieldChange("workoutCompleted", e.target.value)
}
should be
onChange= e => {
onTextFieldChange("workoutCompleted", e.target.value)
}
no?
Thanks for the help #UXDart
you are changing "workoutCompleted" but then checking the property
inside event "workout"... anyway IMO use state for that, and send to
redux when submit the form

Categories