useReducer state change is one character delay at onChange - javascript

When I try to use useReducer as state management but the problem arises when I use onChange with the reducer it is one character delay. There is a useEffect that depends on the character changes to turn the validity value true or false.
But it works fine with the onBlur.
What should be the cause of this delay?
How can I solve this issue?
import React, { useState, useEffect, useReducer, useContext } from "react";
import AuthContext from "../store/auth-context";
import Button from "../UI/Button";
import Card from "../UI/Card";
import Input from "../UI/Input";
const reducerFn = (state, action) => {
if (action.type === "Email") {
return {
...state,
emailState: action.value,
};
} else if (action.type === "Email_IsValid") {
return {
...state,
isEmailValid: true,
};
} else if (action.type === "Email_IsInValid") {
return {
...state,
isEmailValid: false,
};
} else if (action.type === "Password") {
return {
...state,
passwordState: action.value,
};
} else if (action.type === "Password_IsValid") {
return {
...state,
isPasswordIsValid: true,
};
} else if (action.type === "Password_IsInValid") {
return {
...state,
isPasswordIsValid: false,
};
}
return { emailState: "", isEmailValid: false, passwordState: "", isPasswordIsValid: false };
};
const Login = () => {
const [formState, dispatchFn] = useReducer(reducerFn, { emailState: "", isEmailValid: false, passwordState: "", isPasswordIsValid: false });
const { emailState, isEmailValid, passwordState, isPasswordIsValid } = formState;
const [isEmailValidated, setIsEmailValidated] = useState(null);
const [isPasswordIsValidated, setIsPasswordIsValidated] = useState(null);
const authCtx = useContext(AuthContext);
useEffect(() => {
if (isEmailValid) setIsEmailValidated(true);
if (isPasswordIsValid) setIsPasswordIsValidated(true);
}, [isEmailValid, isPasswordIsValid]);
const onEmailChangeHandler = ({ target }) => {
console.log(isEmailValid, isEmailValidated);
dispatchFn({ type: "Email", value: target.value });
if (emailState.includes("#")) {
dispatchFn({ type: "Email_IsValid" });
} else {
dispatchFn({ type: "Email_IsInValid" });
setIsEmailValidated(false);
}
};
const onEmailBlurHandler = () => {
if (emailState.includes("#")) {
dispatchFn({ type: "Email_IsValid" });
} else {
dispatchFn({ type: "Email_IsInValid" });
setIsEmailValidated(false);
}
};
const onPasswordChangeHandler = ({ target }) => {
dispatchFn({ type: "Password", value: target.value });
if (passwordState.length > 7) {
dispatchFn({ type: "Password_IsValid" });
} else {
dispatchFn({ type: "Password_IsInValid" });
setIsPasswordIsValidated(false);
}
console.log(isPasswordIsValid);
};
const onPasswordBlurHandler = () => {
if (passwordState.length > 7) {
dispatchFn({ type: "Password_IsValid" });
} else {
dispatchFn({ type: "Password_IsInValid" });
setIsPasswordIsValidated(false);
}
};
const onFormSubmit = (e) => {
e.preventDefault();
if (isEmailValid === false) setIsEmailValidated(false);
else if (isPasswordIsValid === false) setIsPasswordIsValidated(false);
else if (isEmailValid && isPasswordIsValid) authCtx.onLogin(emailState, passwordState);
};
return (
<Card>
<form>
<Input
id="email"
label="E-Mail"
type="email"
onChange={onEmailChangeHandler}
onBlur={onEmailBlurHandler}
value={emailState}
isValidated={isEmailValidated}
warningText="Please enter a valid email; must contains '#'"
/>
<Input
id="password"
label="Password"
type="password"
onChange={onPasswordChangeHandler}
onBlur={onPasswordBlurHandler}
value={passwordState}
isValidated={isPasswordIsValidated}
warningText="Password must have 8 characters long"
/>
<Button label="Login" onClick={onFormSubmit} classes={`bgRed bgWider`}></Button>
</form>
</Card>
);
};
export default Login;

You'd have a much simpler time not using reducers here at all, like so:
import React from "react";
const Login = () => {
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const [shouldValidateEmail, setShouldValidateEmail] = React.useState(false);
const [shouldValidatePassword, setShouldValidatePassword] = React.useState(false);
const emailIsValid = email.includes("#");
const passwordIsValid = password.length > 7;
const errors = [
shouldValidateEmail && !emailIsValid ? "Email invalid" : null,
shouldValidatePassword && !passwordIsValid ? "Password invalid" : null,
]
.filter(Boolean)
.join(", ");
return (
<form>
Email
<input
id="email"
type="email"
onChange={(e) => setEmail(e.target.value)}
onBlur={() => setShouldValidateEmail(true)}
value={email}
/>
<br />
Password
<input
id="password"
type="password"
onChange={(e) => setPassword(e.target.value)}
onBlur={(e) => setShouldValidatePassword(true)}
value={password}
/>
<br />
{errors ? <>Errors: {errors}</> : null}
</form>
);
};
export default function App() {
const [resetKey, setResetKey] = React.useState(0);
return (
<div className="App">
<Login key={resetKey} />
<hr />
<button onClick={() => setResetKey((k) => k + 1)}>Reset</button>
</div>
);
}
However, if you really do want to use a reducer, just couple the validation state to when the state is changed:
function validateEmail(email) {
return email.includes("#");
}
function validatePassword(password) {
return password.length > 7;
}
const initialState = {
emailState: "",
isEmailValid: false,
passwordState: "",
isPasswordValid: false,
};
const reducerFn = (state, action) => {
if (action.type === "Email") {
return {
...state,
emailState: action.value,
isEmailValid: validateEmail(action.value),
};
} else if (action.type === "Password") {
return {
...state,
passwordState: action.value,
isPasswordValid: validatePassword(action.value),
};
}
return initialState;
};
const Login = () => {
const [{ emailState, isEmailValid, passwordState, isPasswordValid }, dispatchFn] = React.useReducer(
reducerFn,
initialState,
);
const errors = [
emailState && !isEmailValid ? "Email invalid" : null,
passwordState && !isPasswordValid ? "Password invalid" : null,
]
.filter(Boolean)
.join(", ");
return (
<form>
Email
<input
id="email"
type="email"
onChange={(e) => dispatchFn({ type: "Email", value: e.target.value })}
value={emailState}
/>
<br />
Password
<input
id="password"
type="password"
onChange={(e) => dispatchFn({ type: "Password", value: e.target.value })}
value={passwordState}
/>
<br />
{errors ? <>Errors: {errors}</> : null}
</form>
);
};

Related

why useReducer is returning undefined?

I am using useReducer to mange my states in a form and everything is working perfectly fine until I submit the fine on form submission I am logging all my state values in the console but it returns all values as undefined as null except for the last one
Console Output:
{product_id: undefined, product_name: undefined, produuct_price: '55'}
product_id: undefined
product_name: undefined
produuct_price: "55"
[[Prototype]]: Object
I dont know why its happening because all state binding is working perfectly fine and if there is some problem why it is returning last value correctly:
import "./ProductForm.css";
import { useReducer } from "react";
const ProductForm = () => {
const initialState = {
product_id: "",
product_name: "",
product_quantity: 0,
product_description: "",
product_type: "",
product_valid: "false",
product_price: 0,
product_title: "",
product_image: "",
};
const reducer = (state, action) => {
if (action.type === "id") {
return { product_id: action.value };
} else if (action.type === "name") {
return { product_name: action.value };
} else if (action.type === "title") {
return { product_title: action.value };
} else if (action.type === "price") {
return { product_price: action.value };
} else if (action.type === "image") {
return { product_image: action.value };
} else if (action.type === "description") {
return { product_discription: action.value };
} else if (action.type === "type") {
return { product_type: action.value };
}
};
const [state, dispatch] = useReducer(reducer, initialState);
const submitHandler = (e) => {
e.preventDefault();
const obj = {
product_id: state.product_id,
product_name: state.product_name,
produuct_price: state.product_price,
};
};
return (
<form onSubmit={submitHandler}>
<div className="form-group">
<label>Product id</label>
<input
type="text"
className="form-control"
value={state.product_id}
onChange={(e) => dispatch({ type: "id", value: e.target.value })}
/>
</div>
<div className="form-group">
<label>Product Name</label>
<input
type="text"
className="form-control"
value={state.product_name}
onChange={(e) => dispatch({ type: "name", value: e.target.value })}
/>
</div>
<div className="form-group">
<label>Product Price</label>
<input
type="text"
className="form-control"
value={state.product_price}
onChange={(e) => dispatch({ type: "price", value: e.target.value })}
/>
</div>
<button type="submit" className="btn btn-primary mt-4">
Submit
</button>
</form>
);
};
export default ProductForm;
you have to return other field values too from the state, each time as below
const reducer = (state, action) => {
if (action.type === "id") {
return { ...state, product_id: action.value };
} else if (action.type === "name") {
return { ...state, product_name: action.value };
}
// likewise for all
};
so it returns all the fields in the state with the updated field value, other wise the state will always be the last updated value which in your case is the produuct_price...
you can check by logging the state from your current code and will have is just this below, as it is the last updated value ...
{produuct_price: "55"}

Having Issues with Form Validation for a SignUp component

I have a Sign Up Component in my current project, and I'm trying to implement validation for the email and phone number.
Code:
export default function Form() {
// States for registration
const [firstname, setFirstName] = useState('');
const [lastname, setLastName] = useState('');
const [email, setEmail] = useState('');
const [phonenumber, setPhoneNumber] = useState('');
// States for checking the errors
const [submitted, setSubmitted] = useState(false);
const [error, setError] = useState(false);
// Handling the email change
const handleEmail = (e) => {
setEmail(e.target.value);
setSubmitted(false);
};
// Handling the phonenumber change
const handlePhoneNumber = (e) => {
setPhoneNumber(e.target.value);
setSubmitted(false);
};
// Handling the form submission
const handleSubmit = (e) => {
e.preventDefault();
if (email === '' || phonenumber === '') {
setError(true);
} else {
setSubmitted(true);
setError(false);
}
};
// Showing error message if error is true
const errorMessage = () => {
return (
<div
className="error"
style={{
display: error ? '' : 'none',
}}>
<h1>Please enter all the fields</h1>
</div>
);
};
return (
<div className="form">
<div className="messages">
{errorMessage()}
{successMessage()}
</div>
<div className='inputval'>
<div className="d-flex justify-content-center flex-column">
<label className="label">Email</label>
<input onChange={handleEmail} className="input"
value={email} type="email" />
<label className="label">Phone Number</label>
<input onChange={handlePhoneNumber} className="input"
value={phonenumber} type="email" />
</div>
<div className="d-inline-block justify-content-center align-items-center">
<button className="btn" onClick={handleSubmit} type="submit">
Submit
</button>
</div>
</div>
</div>
);
}
For the most part, I tried implementing /^(([^<>()[\]\.,;:\s#\"]+(\.[^<>()[\]\.,;:\s#\"]+)*)|(\".+\"))#(([^<>()[\]\.,;:\s#\"]+\.)+[^<>()[\]\.,;:\s#\"]{2,})$/i for the format constant in my email but I had no luck. I have a useState hook that checks if the boxes are empty, but if I could get some assistance on this, it would be much appreciated!
There are a lot of form validation npm tools that will help alot. But if you want to do everything custom and understand about how it will work, here is a quick project demonstrating how to go about it. I would recommend putting some of the helper functions in different files so they can be used everywhere in your app. CodeSandbox: https://codesandbox.io/s/simple-form-validation-jqfvpy?file=/src/Input.js:0-325
export default function App() {
const [form, setForm] = useState({ name: "", email: "", phone: "" });
const [errors, setErrors] = useState({ name: [], email: [], phone: [] });
const checkRules = (input, rules) => {
let errors = [];
let value = input;
if (typeof value === "string") value = input.trim();
if (rules.required) {
if (value === "") errors.push("*This field is required.");
}
if (rules.phone) {
let phoneno = new RegExp(/^\(?(\d{3})\)?[-. ]?(\d{3})[-. ]?(\d{4})$/);
if (!phoneno.test(value))
errors.push("*Please Enter valid phone number XXX-XXX-XXXX");
}
if (rules.email) {
let pattern = new RegExp(
/^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(#((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(#\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i
);
if (!pattern.test(value)) errors.push("*Please enter a valid email.");
}
return errors;
};
const checkFormValidation = (f) => {
const errors = {};
errors.name = checkRules(f.name, { required: true });
errors.phone = checkRules(f.phone, { phone: true });
errors.email = checkRules(f.email, { email: true });
for (const [, value] of Object.entries(errors)) {
if (value.length > 0) return { noErrors: false, errors };
}
return { noErrors: true, errors };
};
const handleSubmit = (f) => {
const { errors, noErrors } = checkFormValidation(f);
setErrors(errors);
if (noErrors) {
alert(JSON.stringify(f));
}
};
return (
<div className="App">
<div style={{ display: "grid", placeItems: "center" }}>
<Input
name="Name"
value={form.name}
errors={errors.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
/>
<Input
name="Email"
value={form.email}
errors={errors.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
/>
<Input
name="Phone"
value={form.phone}
errors={errors.phone}
onChange={(e) => setForm({ ...form, phone: e.target.value })}
/>
<button onClick={() => handleSubmit(form)}>Submit</button>
</div>
</div>
);
}
export const Input = ({ name, value, onChange, errors }) => {
return (
<>
<input type="text" placeholder={name} value={value} onChange={onChange} />
{errors.length > 0
? errors.map((e) => (
<p style={{ fontSize: "9px", color: "red" }}>{e}</p>
))
: null}
</>
);
};

React useState and onSubmit form doesn't update the state immediately - validation best practice

I'm developing a custom Form validation for my React project with Typescript.
I'm facing an issue with the useState that is not updating immediately the state containing the errors when I submit a form.
Let me provide you an example.
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
When I'm checking for the errors in the handleSubmit, there are no errors, even if errors should be present there:
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
In general, I'm wondering what is the best practice in order to avoid these kinds of issues with the react state not updating immediately the state?
I already tried with useEffect, listening on the validationErrors changes but nothing changes actually, the behavior is pretty the same.
I'm sure I'm missing something.
useState is asynchronous, so state changes (setValidationErrors) are not applied immediately. Therefore, you cannot get the latest state of validationErrors in the next line.
We can do validation and set state separately. In that case, you can leverage the latest value (not the latest state) to check values validity.
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
//get all invalid values
const invalidValues = returnInvalidValues();
//update state
setValidationErrors(prev => [...prev, ...invalidValues])
if (invalidValues.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
};
const returnInvalidValues = () => {
return Object.entries(formValues).filter(([inputName, value]) => formValues[inputName] === ''). map(invalidValue => `${inputName} is not valid`);
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
You also can try useEffect
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
//introduce useEffect here
useEffect(() => {
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
}, [validationErrors]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
If it does not work for your case, you can delay using the latest state with setTimeout. With this approach, it will put the task to get the latest state to the end of the call stack queue (you can check this document)
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
setTimeout(() => {
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
})
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;

Unable to get the updated state in ReactJS login page

I am new to React JS and have stuck on a problem. I am building a login page where I want to display some error when the user enters invalid credentials. When I enter the correct credentials I am able to login but when I enter the invalid credentials then also I am able to login. On debugging I have found that in mapPropsToState although I get isLoggedIn parameter as false but it is not mapped to props. props still get true here.
My Login Page:
const required = (value) => {
if (!value) {
return (
<div className="alert alert-danger" role="alert">
This field is required!
</div>
);
}
};
const Login = (props) => {
const form = useRef();
const checkBtn = useRef();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const { isLoggedIn } = useSelector(state => state.auth);
const { message } = useSelector(state => state.message);
const dispatch = useDispatch();
const onChangeUsername = (e) => {
const username = e.target.value;
setUsername(username);
};
const onChangePassword = (e) => {
const password = e.target.value;
setPassword(password);
};
const handleLogin = (e) => {
e.preventDefault();
setLoading(true);
form.current.validateAll();
if (checkBtn.current.context._errors.length === 0) {
dispatch(login(username, password))
.then(() => {
if (props !=null && props.isAuthenticated) {
props.history.push("/home");
}
})
.catch(() => {
setLoading(false);
});
} else {
setLoading(false);
}
};
if (isLoggedIn) {
// return <Redirect to="/home" />;
}
return (
<div className="col-md-12">
<div className="card card-container">
<img
src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
alt="profile-img"
className="profile-img-card"
/>
<Form onSubmit={handleLogin} ref={form}>
<div className="form-group">
<label htmlFor="username">Username</label>
<Input
type="text"
className="form-control"
name="username"
value={username}
onChange={onChangeUsername}
validations={[required]}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Input
type="password"
className="form-control"
name="password"
value={password}
onChange={onChangePassword}
validations={[required]}
/>
</div>
<div className="form-group">
<button className="btn btn-primary btn-block" disabled={loading}>
{loading && (
<span className="spinner-border spinner-border-sm"></span>
)}
<span>Login</span>
</button>
</div>
{message && (
<div className="form-group">
<div className="alert alert-danger" role="alert">
{message}
</div>
</div>
)}
<CheckButton style={{ display: "none" }} ref={checkBtn} />
</Form>
</div>
</div>
);
};
Login.propTypes = {
login: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool.isRequired
}
const mapStateToProps = (state) => ({
isAuthenticated: state.auth.isLoggedIn
})
export default connect(mapStateToProps, { login })(Login);
my login and logout function in action:
export const login = (username, password) => (dispatch) => {
return AuthServicee.login(username, password).then(
(data) => {
if(data.success) {
userService.getUserDetails(username).then((data) => {
localStorage.setItem("user", JSON.stringify(data.data));
dispatch({
type: LOGIN_SUCCESS,
payload: { user: data },
});
return Promise.resolve();
},(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: LOGIN_FAIL,
});
dispatch({
type: SET_MESSAGE,
payload: message,
});
return Promise.reject();
}).catch(err => {
dispatch({
type: LOGIN_FAIL,
});
});;
} else {
dispatch({
type: LOGIN_FAIL,
});
dispatch({
type: SET_MESSAGE,
payload: data.error,
});
}
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: LOGIN_FAIL,
});
dispatch({
type: SET_MESSAGE,
payload: message,
});
return Promise.reject();
}
);
};
export const logout = () => (dispatch) => {
AuthServicee.logout();
dispatch({
type: LOGOUT,
});
};
reducer class:
const user = JSON.parse(localStorage.getItem("user"));
const initialState = user
? { isLoggedIn: true, user }
: { isLoggedIn: false, user: null };
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case LOGIN_SUCCESS:
return {
...state,
isLoggedIn: true,
user: payload.user,
};
case LOGIN_FAIL:
return {
...state,
isLoggedIn: false,
user: null,
};
case LOGOUT:
return {
...state,
isLoggedIn: false,
user: null,
};
default:
return state;
}
}
can someone please help me here?
Edit: I get correct values in my state variable in mapStateToProps but somehow when I try to use it in then function of my dispatch call, I still get props.isAuthenticated as true. Although it should have become false as I have updated it in mapStateToProps.
If you're being redirected to the /home I would look into your validation functions first, because as you say the pass invalid values. What does this function do if you have invalid inputs form.current.validateAll();?

Uncaught TypeError: Cannot read property 'then' of undefined React/Redux

I am trying to do a put request using axios to update a given Recipe by the authenticated user in the database basic rest API. But i am having an error in my code saying the 'then' is undefined.
`
// My updateRecipe.jsx Action file
import axios from 'axios';
export const RECIPE_UPDATED = "RECIPE_UPDATED"
const recipeUpdated = (recipe) => {
//console.log(recipe)
return {
type: RECIPE_UPDATED,
recipe
}
}
const updateRecipe = (data) => {
return dispatch => {
// console.log(data)
// console.log(dataid)
axios.put(`http://localhost:8009/api/v1/recipes/${data.id}`, data)
.then(() => dispatch(
//console.log(data),
recipeUpdated(data)
)).catch( error => {
//console.log(error.message)
error} )
}
}
export {
updateRecipe,
recipeUpdated,
}`
This is my Reducer file:
import { SET_RECIPES } from '../action/recipeAction.jsx';
import { RECIPE_FETCH } from '../action/RecipeFetch.jsx'
import { RECIPE_UPDATED } from '../action/updateRecipe.jsx'
export default function recipes(state = [], action = {}) {
switch(action.type) {
case RECIPE_UPDATED:
return state.map(item => {
//console.log(item)
if(item.id === action.recipe.id) return action.recipe;
return item;
})
case RECIPE_FETCH:
const index = state.findIndex(item => item.id === action.recipe.id);
if(index > -1){
return state.map(item => {
if(item.id === action.recipe.id) return action.recipe;
return item;
});
}else{
return [
...state,
action.recipe
];
}
case SET_RECIPES: return action.recipes;
default: return state;
}
}
This is my component file this is where i am having the problem, in my if(id) block the then just underneath the this.props.updatedRecipe.
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { Alert } from 'reactstrap';
import { saveRecipe, fetchRecipe, updateRecipe } from
'../../action/index.jsx'
import styles from './style.js'
class RecipeForm extends React.Component {
constructor (props) {
super(props)
this.state = {
id: this.props.recipe ? this.props.recipe.id : null,
title: this.props.recipe ? this.props.recipe.title : '',
description: this.props.recipe ? this.props.recipe.description :
'',
imageUrl: this.props.recipe ? this.props.recipe.imageUrl : '',
errors:{},
loading: false,
done: false,
}
}
componentWillReceiveProps (nextProps){
//console.log(nextProps.recipe)
this.setState({
id: nextProps.recipe.id,
title: nextProps.recipe.title,
description: nextProps.recipe.description,
imageUrl: nextProps.recipe.imageUrl,
})
}
componentDidMount () {
if(this.props.match.params.id){
//console.log(this.props.match.params.id);
this.props.fetchRecipe(this.props.match.params.id)
}
}
handleChange(event) {
// this.setState({ [event.target.name] : event.target.value });
// console.log('updatedRecipe: ' + event.target.id + ' == '+
event.target.value )
if (!!this.state.errors[event.target.name]){
let errors = Object.assign({}, this.state.errors);
delete errors[event.target.name];
// console.log(errors)
this.setState({
[event.target.name]: event.target.value,
errors
})
}else{
//console.log(this.state)
this.setState({
[event.target.name]: event.target.value,
// let handleChange = Object.assign({}, this.state);
// handleChange[event.target.id] = event.target.value;
// this.setState({
// recipes: handleChange,
})
}
}
handleSubmit(e){
e.preventDefault();
let errors = {};
if (this.state.title === '') errors.title = "Can't be empty";
if (this.state.description === '') errors.description = "Can't be
empty";
if (this.state.imageUrl === '') errors.imageUrl = "Can't be empty";
this.setState({
errors
})
const isValid = Object.keys(errors).length === 0
if(isValid){
const { id, title, description, imageUrl } = this.state;
//console.log(this.state)
this.setState({ loading: true });
if(id){
this.props.updateRecipe({ id, title, description, imageUrl })
// this is where the BUG is COMING FROM
.then(
()=>{
console.log("see me")
this.setState({ done : true, loading: true})},
(err) => {
err.response.json()
.then(({errors}) => {
console.log(errors)
this.setState({
errors})
}
)
}
)
}else{
this.props.saveRecipe({ title, description, imageUrl, })
.then(
()=>{
this.setState({ done : true, loading: true})},
(err) => {
err.response.json()
.then(({errors}) => {
console.log(errors)
this.setState({
errors})
}
)
}
)
}
}
}
render() {
const form = (
<div className="d-flex justify-content-center align-items-center
container">
<form className={classnames({ loading: this.state.loading })}>
<h1 style={styles.header}>Add New Recipe</h1>
{!!this.state.errors.form && <Alert color="danger">
{this.state.errors.form }</Alert>}
<div className="form-group row">
<div className="form-group col-md-12 col-md-offset-8 text-
right">
<div className={classnames('field', { error:
!!this.state.errors.title})}>
<label htmlFor="title">Recipe Title</label>
<input
name="title"
value={this.state.title}
onChange={this.handleChange.bind(this)}
className="form-control"
id="title"
placeholder="title"/>
<span style={{color: "#ae5856"}} >{this.state.errors.title}
</span>
</div>
<div className={classnames('field', { error:
!!this.state.errors.description})}>
<label htmlFor="title">Recipe Description</label>
<input
name="description"
value={this.state.description}
onChange={this.handleChange.bind(this)}
className="form-control"
id="description"
placeholder="description"/>
<span style={{color: "#ae5856"}}>
{this.state.errors.description}</span>
</div>
<div className={classnames('field', { error:
!!this.state.errors.imageUrl})}>
<label htmlFor="imageUrl">Recipe Image</label>
<input
name="imageUrl"
value={this.state.imageUrl}
onChange={this.handleChange.bind(this)}
className="form-control"
id="imageUrl"
placeholder="Image"/>
<span style={{color: "#ae5856"}}>{this.state.errors.imageUrl}
</span>
</div>
<div>
{this.state.imageUrl !== '' && <img src=
{this.state.imageUrl} alt="" className="img-rounded"/>}
</div>
<button onClick={this.handleSubmit.bind(this)} type="submit"
className="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
);
return (
<div>
{this.state.done ? <Redirect to="/recipes"/> : form}
</div>
)
}
}
RecipeForm.propTypes = {
saveRecipe: PropTypes.func.isRequired,
}
function mapStateToProps(state, props){
//console.log(props.match.recipe)
if(props.match.params.id){
//console.log(props.match.params.id)
let recipe = {}
const recipes = state.recipes.filter(item => {
//console.log(item)
//console.log(state.recipes)
if (item.id == props.match.params.id){
recipe = item
}
})
//console.log(recipe)
return {
recipe
}
//console.log(recipe);
}
return { recipe: null };
}
export default connect(mapStateToProps, { saveRecipe, fetchRecipe })
(RecipeForm);
It should just be updateRecipe, not this.props.updateRecipe. You're importing updateRecipe() (as a function) at the top of the <RecipeForm/> component. So it's not a property that's being passed to the component, it's just a function you've imported in that file.

Categories

Resources