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
Related
I am working on something else but the below code fragment is similar to the problem I am facing. In the below code, the input field is getting generated dynamically.
I wanted to handle the input onChange event so I took a state variable like normally we do and used that to handle my input state and of course, it isn't working, if I change one input field the others are also getting affected which now seem obvious why it's happening.
import { useState } from "react";
export default function App() {
let [arr, setArr] = useState(["a", "b", "c", "d"]);
const [input, setInput] = useState(0);
return (
<div className="App">
{arr.map((item, idx) => {
return (
<div key={idx}>
<p>{item}</p>
<input
placeholder="enter value"
type="number"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
</div>
);
})}
</div>
);
}
Edit: This is the original problem.
Stocks.jsx
I have a quantity state variable that I am using to manage the state of my input element. But, when there will be more than one input element then it won't work and the issue it creates is that a change in any input field also changes the other input fields.
import {
Box,
Button,
Grid,
GridItem,
Heading,
Image,
Input,
Text,
} from "#chakra-ui/react";
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { getStock } from "../redux/appReducer/action";
import { useDispatch } from "react-redux";
export const Stocks = () => {
const dispatch = useDispatch();
const stocks = useSelector((store) => store.AppReducer.stocks);
/*
Format of the data coming in the stocks
[
{
"id" : 1,
"company_logo": "https://logolook.net/wp-content/uploads/2021/11/HDFC-Bank-Logo.png",
"company_name": "HDFC Bank",
"company_type": "Bank",
"stock_exchange": "NSE",
"total_shares": 50000,
"cost_per_share": 4210,
"price_action": 12,
},
{
"id" : 2,
"company_logo": "https://www.jetspot.in/wp-content/uploads/2018/03/reliance-logo.jpg",
"company_name": "Reliance Industries",
"company_type": "Oil",
"stock_exchange": "BSE",
"total_shares": 20000,
"cost_per_share": 2132,
"price_action": 4,
}
]
*/
const [quantity, setQuantity] = useState(0);
useEffect(() => {
dispatch(getStock());
}, []);
return (
<Grid
templateColumns="repeat(2, 350px)"
templateRows="repeat(2, auto)"
justifyContent="center"
alignItems="center"
gap="20px"
pt="30px"
pb="30px"
>
{stocks?.map((stock, index) => {
return (
<GridItem
boxShadow="rgba(3, 102, 214, 0.3) 0px 0px 0px 3px"
justifyContent="center"
p="20px"
key={stock.id}
>
<Box mb="10px">
<Image h="80px" src={stock?.company_logo} />
</Box>
<Box mb="10px">
<Heading size="md">{stock?.company_name}</Heading>
</Box>
<Box mb="10px">
<Text as="span">EXCHANGE: </Text>
<Text as="span">{(stock?.stock_exchange).toUpperCase()}</Text>
</Box>
<Box mb="10px">
<Text as="span">TYPE: </Text>
<Text as="span">{(stock?.company_type).toUpperCase()}</Text>
</Box>
<Box mb="10px">
<Text as="span">TOTAL SHARES: </Text>
<Text as="span">{stock?.total_shares}</Text>
</Box>
<Box mb="10px">
<Text as="span">COST PER SHARE: </Text>
<Text as="span">{stock?.cost_per_share}</Text>
</Box>
<Box mb="10px">
<Text as="span">PRICE ACTION: </Text>
<Text as="span">{stock?.price_action}</Text>
</Box>
<Box mb="10px">
<Input
value={quantity}
type="number"
placeholder="Quantity"
onChange={(e) => setQuantity(e.target.value)}
/>
</Box>
<Box mb="10px">
<Button colorScheme="green">Buy</Button>
</Box>
</GridItem>
);
})}
</Grid>
);
};
action.js
import * as types from "./actionTypes";
import axios from "axios";
export const getStock = () => (dispatch) => {
dispatch({ type: types.GET_STOCK_REQUEST });
return axios
.get("https://stockbroker.onrender.com/companies")
.then((res) => {
// console.log(res);
dispatch({ type: types.GET_STOCK_SUCCESS, payload: res.data });
})
.catch((e) => dispatch({ type: types.GET_STOCK_FAILURE }));
};
reducer.js
import * as types from "./actionTypes";
const initialState = {
isLoading: false,
isError: false,
stocks: [],
};
export const reducer = (state = initialState, { type, payload }) => {
switch (type) {
case types.GET_STOCK_REQUEST:
return {
...state,
isLoading: true,
};
case types.GET_STOCK_SUCCESS:
return {
...state,
isLoading: false,
stocks: payload,
};
case types.GET_STOCK_FAILURE:
return {
...state,
isLoading: false,
isError: true,
stocks: [],
};
default:
return state;
}
};
if turning arr into an object is an option, you could do just that, and use it as the control state for the inputs:
import React from 'react';
import { useState } from "react";
export function App() {
const [obj, setObj] = useState({
a: 0,
b: 0,
c: 0,
d: 0
});
return (
<div className="App">
{Object.keys(obj).map((item, idx) => {
return (
<div key={idx}>
<p>{item}</p>
<input
placeholder="enter value"
type="number"
value={obj[item]}
onChange={(e) => setObj(prev => ({...prev, [item]: e.target.value}))}
/>
</div>
);
})}
</div>
);
}
I am new to react hook form so this may be a simple issue. I just discovered that Controllers do not have the ability to use value as number. This was aggravating , but I eventually found this github issue #8068 which describes the solution as setting an onChange function like this:
<Controller
- rules={{ valueAsNumber: true }}
render={({ field }) => (
<input
- onChange={field.onChange}
+ onChange={(event) => field.onChange(+event.target.value)}
type="number"
/>
)}
/>
So I did some slight altering and came up with this code
import React, { ChangeEvent } from 'react'
import { Controller } from 'react-hook-form'
import { getPlaceholder } from './getPlaceholder'
import { IInput } from './types'
const NumberInput: React.FC<IInput> = ({ control, name, ...props }) => {
const placeholder = getPlaceholder({ type: "number" });
const numericalOnChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.value === '') return null;
return +event.target.value;
}
return (
<Controller
control={control}
name={name}
render={({ field: { onChange, ...field } }) => (
<input
{...props}
{...field}
type="number"
placeholder={placeholder}
onChange={(event) => {
const value = numericalOnChange(event)
onChange(value)
}}
className="h-[20px] pl-[4px] py-[8px] bg-transparent border-b
border-b-[#646464] focus:border-b-[#3898EC] text-[13px]
text-[#F00] placeholder-[#646464] outline-none m-1 w-full"
/>
)}
/>
)
}
export default NumberInput
which in theory should work, but ends up providing an Invalid Hook Call Error.
I have a MERN react component that is going to show a toast to my user after they create a new group.
Here is my useEffect below
useEffect(() => {
toast(
<Fragment>
New Group Created
</Fragment>
);
}, [successCreate]);
I only want this useEffect to run AFTER my user creates a new group.
It currently runs on first render, and after my user clicks the modal(child) then triggers the successCreate from redux(just creates a new group).
How can I make useEffect run only when successCreate is called.. NOT on first render AND successCreate
Here is my component
import React, { Fragment, useState, useEffect, useContext } from 'react';
import WizardInput from '../auth/wizard/WizardInput';
import {useDispatch, useSelector} from 'react-redux';
import {
Button,
Card,
CardBody,
Form,
Label,
Input,
Media,
Modal,
ModalBody,
ModalHeader
} from 'reactstrap';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import Select from 'react-select';
import { toast } from 'react-toastify';
import makeAnimated from 'react-select/animated';
import {listGroups, createGroup} from '../../actions/index';
//import { isIterableArray } from '../../helpers/utils';
import { CsvUploadContext } from '../../context/Context';
import chroma from 'chroma'
const StepOneForm = ({ register, errors, watch}) => {
const { upload, setUpload } = useContext(CsvUploadContext);
//const { handleInputChange } = useContext(CsvUploadContext);
const [ createGroupModal, setCreateGroupModal ] = useState(false)
const [newGroup, setNewGroup] = useState({
title: ''
})
const dispatch = useDispatch();
const groups = useSelector(state => state.groups)
const groupCreate = useSelector((state) => state.groupCreate)
const {success: successCreate} = groupCreate
const animatedComponents = makeAnimated();
useEffect(() => {
dispatch(listGroups())
}, [successCreate])
useEffect(() => {
toast(
<Fragment>
New Group Created
</Fragment>
);
}, [successCreate]);
const customStyles = {
control: (base, state) => ({
...base,
background: "light",
// match with the menu
borderRadius: state.isFocused ? "3px 3px 0 0" : 3,
// Overwrittes the different states of border
borderColor: state.isFocused ? "primary" : "light",
// Removes weird border around container
boxShadow: state.isFocused ? null : null,
"&:hover": {
// Overwrittes the different states of border
borderColor: state.isFocused ? "blue" : "#2c7be5"
}
}),
menu: base => ({
...base,
// override border radius to match the box
borderRadius: 0,
// kill the gap
marginTop: 0
}),
menuList: base => ({
...base,
// kill the white space on first and last option
padding: 0,
color: 'f9fafd'
}),
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
const color = chroma(data.color);
return {
...styles,
backgroundColor: isDisabled
? null
: isSelected
? data.color
: isFocused,
color: '232e3c',
};
}
};
const toggle = () => { setCreateGroupModal(!createGroupModal)}
const closeBtn = (
<button className="close font-weight-normal" onClick={toggle}>
×
</button>
);
const handleSubmit = (e) => {
e.preventDefault()
dispatch(createGroup(newGroup))
setCreateGroupModal(false)
};
console.log(upload?.group)
const handleChange = e => {
setNewGroup({...newGroup, [e.target.name]: e.target.value})
}
return (
<Fragment>
<Media className="flex-center pb-3 d-block d-md-flex text-center mb-2">
<Media body className="ml-md-4">
</Media>
</Media>
<h4 className="mb-1 text-center">Choose Groups</h4>
<p className=" text-center fs-0">These groups will contain your contacts after import</p>
<Select
name="group"
required={true}
className="mb-3"
styles={customStyles}
components={animatedComponents}
innerRef={register({
required: true
})}
closeMenuOnSelect={true}
options={groups}
getOptionLabel={({title}) => title}
getOptionValue={({_id}) => _id}
onChange={(_id) => setUpload({...upload, group: _id})}
isMulti
placeholder="select group"
isSearchable={true}
errors={errors}
/>
<Button color="light" onClick={(() => setCreateGroupModal(true))} className="rounded-capsule shadow-none fs--1 ml- mb-0" >
<FontAwesomeIcon icon="user-plus" />
{` or create a new group`}
</Button>
<Modal isOpen={createGroupModal} centered toggle={() => setCreateGroupModal(!createGroupModal)}>
<ModalHeader toggle={toggle} className="bg-light d-flex flex-between-center border-bottom-0" close={closeBtn}>
Let's give the group a name
</ModalHeader>
<ModalBody className="p-0">
<Card>
<CardBody className="fs--1 font-weight-normal p-4">
<Card>
<CardBody className="fs--1 font-weight-normal p-4">
<Form onSubmit={handleSubmit}>
<Label for="title">Group Name:</Label>
<Input value={newGroup.title.value} onChange={handleChange} className="mb-3" name="title" id="title"/>
<Button block onClick={handleSubmit} color="primary" className="mb-3">Save</Button>
</Form>
</CardBody>
</Card>
<Button block onClick={() => setCreateGroupModal(false)}>close</Button>
</CardBody>
</Card>
</ModalBody>
</Modal>
<WizardInput
type="textarea"
label="or add number manually seperated by comma"
placeholder="+17209908576, +18165009878, +19138683784"
name="manual-add"
rows="4"
id="manual-add"
innerRef={register({
required: false
})}
errors={errors}
/>
</Fragment>
);
};
export default StepOneForm;
Is successCreate a boolean?
This would work:
useEffect(() => {
if(!successCreate) return;
toast(
<Fragment>
New Group Created
</Fragment>
);
}, [successCreate]);
useEffect is always called when the dependencies change - on first render it is guaranteed to be called because there is nothing previous - this is the equivalent to componentDidMount
useEffect(() => {
console.log('mounted');
},[]);
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
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("")