React REDUX is updating all state after the update action - reactjs

I've been figuring out this bug since yesterday.
All of the states are working before the update action. I have console log all the states before the update action.
Then after creating a model, the update action is executed.
This is the result when I console log.
I wondered why dataGrid returns an error since I point to all the id in the DataGrid component.
Uncaught Error: MUI: The data grid component requires all rows to have a unique `id` property.
This is my code:
Models Reducer:
import * as actionTypes from 'constants/actionTypes';
export default (models = [], action) => {
switch (action.type) {
case actionTypes.FETCH_MODELS:
return action.payload.result;
case actionTypes.CREATE:
return [...models, action.payload.result];
case actionTypes.UPDATE:
return models.map((model) => (model.model_id === action.payload.result.model_id ? action.payload.result : model));
case actionTypes.DELETE:
return models.filter((model) => model.model_id !== action.payload);
default:
return models;
}
};
In my model component:
import * as actionTypes from 'constants/actionTypes';
export default (models = [], action) => {
switch (action.type) {
case actionTypes.FETCH_MODELS:
return action.payload.result;
case actionTypes.CREATE:
return [...models, action.payload.result];
case actionTypes.UPDATE:
return models.map((model) => (model.model_id === action.payload.result.model_id ? action.payload.result : model));
case actionTypes.DELETE:
return models.filter((model) => model.model_id !== action.payload);
default:
return models;
}
};
My ModelForm:
<Formik
enableReinitialize={true}
initialValues={modelData}
validationSchema={Yup.object().shape({
model_code: Yup.string(4).min(4, 'Minimum value is 4.').max(50, 'Maximum value is 4.').required('Model code is required'),
model_description: Yup.string().max(200, 'Maximum value is 200.'),
model_status: Yup.string().min(5).max(10, 'Maximum value is 10.')
})}
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
try {
if (scriptedRef.current) {
if (currentId === 0) {
// , name: user?.result?.name
dispatch(createModel({ ...values }, setFormVisible));
} else {
dispatch(updateModel(currentId, { ...values }, setFormVisible));
}
setStatus({ success: true });
setSubmitting(false);
}
} catch (err) {
console.error(err);
if (scriptedRef.current) {
setStatus({ success: false });
setErrors({ submit: err.message });
setSubmitting(false);
}
}
}}
>
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, resetForm, values }) => (
<form noValidate onSubmit={handleSubmit}>
<Grid container spacing={1}>
<Grid item lg={4} md={4} sm={12}>
<JTextField
label="Model"
name="model_code"
value={values.model_code}
onBlur={handleBlur}
onChange={handleChange}
touched={touched}
errors={errors}
/>
</Grid>
</Grid>
<Grid container spacing={1} sx={{ mt: 1 }}>
<Grid item lg={4} md={4} sm={12}>
<JTextField
label="Description"
name="model_description"
value={values.model_description}
onBlur={handleBlur}
onChange={handleChange}
touched={touched}
type="multiline"
rows={4}
errors={errors}
/>
</Grid>
</Grid>
{currentId ? (
<Grid container spacing={1} sx={{ mt: 1 }}>
<Grid item lg={4} md={4} sm={12}>
<JSelect
labelId="model_status"
id="model_status"
name="model_status"
value={values.model_status}
label="Status"
onBlur={handleBlur}
onChange={handleChange}
errors={errors}
>
<MenuItem value="ACTIVE">ACTIVE</MenuItem>
<MenuItem value="INACTIVE">INACTIVE</MenuItem>
</JSelect>
</Grid>
</Grid>
) : (
''
)}
<Box sx={{ mt: 2 }}>
<ButtonGroup variant="contained" aria-label="outlined button group">
<Button size="small" disabled={isSubmitting} type="submit">
Save
</Button>
<Button size="small" onClick={resetForm}>
Cancel
</Button>
{currentId ? (
<Button size="small" color="secondary" onClick={handleDelete}>
Delete
</Button>
) : (
''
)}
</ButtonGroup>
</Box>
</form>
)}
</Formik>
Why products, parts or other states are updating too? Since I only update the model create action?
Please check this out: https://www.awesomescreenshot.com/video/11412230?key=a0212021c59aa1097fa9d38917399fe3
I Hope someone could help me figure out this bug. This is only the problem else my CRUD template is good.

Update:
Found out that actions in redux should always be unique or else multiple reducers with the same action name will be triggered.
I have updated my action types to:
// AUTHENTICATION ACTIONS
export const AUTH = 'AUTH';
export const LOGOUT = 'LOGOUT';
// MODEL ACTIONS
export const FETCH_MODELS = 'FETCH_MODELS';
export const CREATE_MODEL = 'CREATE_MODEL';
export const UPDATE_MODEL = 'UPDATE_MODEL';
export const DELETE_MODEL = 'DELETE_MODEL';
// PRODUCTS ACTIONS
export const FETCH_PRODUCTS = 'FETCH_PRODUCTS';
export const CREATE_PRODUCT = 'CREATE_PRODUCT';
export const UPDATE_PRODUCT = 'UPDATE_PRODUCT';
export const DELETE_PRODUCT = 'DELETE_PRODUCT';
// ASSEMBLY ACTIONS
export const FETCH_ASSEMBLY = 'FETCH_ASSEMBLY';
export const CREATE_ASSEMBLY = 'CREATE_ASSEMBLY';
export const UPDATE_ASSEMBLY = 'UPDATE_ASSEMBLY';
export const DELETE_ASSEMBLY = 'DELETE_ASSEMBLY';
// PARTS ACTIONS
export const FETCH_PARTS = 'FETCH_PARTS';
export const CREATE_PART = 'CREATE_PART';
export const UPDATE_PART = 'UPDATE_PART';
export const DELETE_PART = 'DELETE_PART';
Reducers to:
import * as actionTypes from 'constants/actionTypes';
export default (models = [], action) => {
switch (action.type) {
case actionTypes.FETCH_MODELS:
return action.payload.result;
case actionTypes.CREATE_MODEL:
return [...models, action.payload.result];
case actionTypes.UPDATE_MODEL:
console.log(models);
return models.map((model) => (model.model_id === action.payload.result.model_id ? action.payload.result : model));
case actionTypes.DELETE_MODEL:
return models.filter((model) => model.model_id !== action.payload);
default:
return models;
}
};
and Actions to:
import * as actionTypes from 'constants/actionTypes';
import * as api from 'api/index.js';
import Swal from 'sweetalert2';
export const getModels = () => async (dispatch) => {
try {
const { data } = await api.fetchModels();
dispatch({ type: actionTypes.FETCH_MODELS, payload: data });
} catch (error) {
console.log(error);
Swal.fire('Error!', 'Something went wrong', 'error');
}
};
export const createModel = (model, setFormVisible) => async (dispatch) => {
try {
const { data } = await api.createModel(model);
dispatch({ type: actionTypes.CREATE_MODEL, payload: data });
setFormVisible(false);
Swal.fire('Success!', 'Model has been added successfully', 'success');
} catch (error) {
console.log(error);
Swal.fire('Error!', 'Something went wrong', 'error');
}
};
export const updateModel = (id, model, setFormVisible) => async (dispatch) => {
try {
const { data } = await api.updateModel(id, model);
dispatch({ type: actionTypes.UPDATE_MODEL, payload: data });
setFormVisible(false);
Swal.fire('Success!', 'Model updated successfully', 'success');
} catch (error) {
console.log(error);
Swal.fire('Error!', 'Something went wrong', 'error');
}
};
export const deleteModel = (id) => async (dispatch) => {
try {
await await api.deleteModel(id);
dispatch({ type: actionTypes.DELETE_MODEL, payload: id });
Swal.fire('Success!', 'Model deleted successfully', 'success');
} catch (error) {
console.log(error);
Swal.fire('Error!', 'Something went wrong', 'error');
}
};

Related

How do I create a delete/clear button in REACT js?

Hi I'm new to REACT and I have a HW where I need to create a grocery shopping list and I need to create a clear button. The isPurchased key value pair is a boolean though. I need to create a button that when I click Purchased it clears that grocery item off my list. Any help would be appreciated.
class App extends Component {
state = {
grocery: grocery,
item: '',
brand: '',
units: Number,
quantity: Number,
isPurchased: Boolean
}
handleChange = (e) => {
this.setState({ [e.target.id]: e.target.value })
}
handleSubmit = (e) => {
e.preventDefault()
const addGrocery = {
item: this.state.item,
brand: this.state.brand,
units: this.state.units,
quantity: this.state.quantity,
}
this.setState({
grocery: [addGrocery, ...this.state.grocery],
item: '',
brand: '',
units: Number,
quantity: Number,
})
const removeGrocery = {
item: this.state.item
}
}
hey here is a full code for creating a to do list in react (it will be very similar to your problem):
**
Summary
** of the idea of creating a to-do list or shopping list is that each to-do will be an object, when we create a new object we will insert it into an array. once it is in the array by using the array.map() function we will convert each object to an HTML element to make the UI.
if something is unclear I am here to answer
file - App.js:
import React, { useState, useReducer } from "react";
import Todo from "./Todo";
export const ACTIONS = {
ADD_TODO: "add-todo",
TOGGLE_TODO: "toggle-todo",
DELETE_TODO: "delete-todo",
};
function reducer(todos, action) {
switch (action.type) {
case ACTIONS.ADD_TODO:
return [...todos, newTodo(action.payload.name)];
case ACTIONS.TOGGLE_TODO:
return todos.map((todo) => {
if (todo.id === action.payload.id) {
return { ...todo, complete: !todo.complete }; //change to complete if we found to id that toggled
}
return todo;
});
case ACTIONS.DELETE_TODO:
return todos.filter((todo) => todo.id !== action.payload.id);
default:
return todos;
}
}
function newTodo(name) {
return { id: Date.now(), name: name, complete: false };
}
const App = () => {
const [todos, dispatch] = useReducer(reducer, []); //useReducer return the state and the reducer function
const [name, setName] = useState("");
function handleSubmit(e) {
e.preventDefault();
dispatch({ type: ACTIONS.ADD_TODO, payload: { name: name } });
setName("");
}
return (
<>
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</form>
{todos.map((todo) => {
return <Todo key={todo.id} todo={todo} dispatch={dispatch} />;
})}
</>
);
};
export default App;
Another file (component) - Todo.js:
import React from "react";
import { ACTIONS } from "./App";
const Todo = ({ todo, dispatch }) => {
return (
<div>
<span style={{ color: todo.complete ? "#AAA" : "#000" }}>
{todo.name}
</span>
<button
onClick={() =>
dispatch({ type: ACTIONS.TOGGLE_TODO, payload: { id: todo.id } })
}
>
Toggle
</button>
<button
onClick={() =>
dispatch({ type: ACTIONS.DELETE_TODO, payload: { id: todo.id } })
}
>
Delete
</button>
</div>
);
};
export default Todo;

MERN Authentication: React - Redux - Formik signup/login form does not wait for dispatch return to update and show server error after submit

I have this Formik form for authentication which can switch from login (by default) to signup form using switch button. Using Formik and yup validation schema, the form first check the fields' values on change and before submitting. On submit, I am trying to have another value check from the server side and to show error messages on top of each field (at the same place as the client side error messages provided by Formik and Yup). When the 'submit' button is clicked and in case of a server error after validation, the state of my Redux 'authReducer' is updated with 'errors'' object inside 'data' property. Through local state, then I create a variable 'errorServer' which is an object created to store for each input (as key), the related server error's message (as value). Then, using this object, I'm trying to show the error message(s) on top of field(s) concerned (I tried both Formik methods 'setErrors' and 'setStatus' to do so). But, in the best case, this error message is only appearing after a second click on the 'submit' button and I don't know how to solve this problem... Can anyone provide me some help? Many thanks!
Server side
auth.controller.js
const { findUserPerUsername, findUserPerEmail} = require('../queries/users.queries');
const { createUser } = require('../queries/users.queries');
const { check, validationResult } = require('express-validator');
// Field value validation before signup
exports.validate = (method) => {
switch (method) {
case 'signup': {
return [
check('name')
.exists()
.withMessage('Enter your name.'),
check('username')
.exists()
.withMessage('Enter a username.')
.isAlphanumeric()
.withMessage('Username must contain only letters and numbers.')
.custom(value => {
return findUserPerUsername(value).then(user => {
if (user) {
return Promise.reject('Username already in use.');
}
})
}),
check('email')
.exists()
.withMessage('Enter an email address.')
.isEmail()
.withMessage('A valid email address is required.')
.custom(value => {
return findUserPerEmail(value).then(user => {
if (user) {
return Promise.reject('Email address already in use.');
}
})
}),
check('password')
.exists()
.withMessage('Enter a password.')
.custom(value => {
const pswRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[##$!%*?&-])[A-Za-z\d##$!%*?&-]{8,}$/;
if (pswRegex.test(value) === false) {
return Promise.reject('Password must contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character')
} else {
return Promise.resolve();
}
})
]
}
case 'login': {
return [
check('email')
.exists()
.withMessage('Enter an email address.')
.isEmail()
.withMessage('A valid email address is required'),
check('password')
.exists()
.withMessage('Enter a password.')
.custom(value => {
const pswRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[##$!%*?&-])[A-Za-z\d##$!%*?&-]{8,}$/;
if (pswRegex.test(value) === false) {
return Promise.reject('Password must contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character')
} else {
return Promise.resolve();
}
})
]
}
}
}
exports.signup = async (req, res, next) => {
try {
const body = req.body;
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).send({
success: false,
errors: errors.array()
});
next();
} else {
const user = await createUser(body);
req.login(user);
res.status(200).send({
user: user,
success: true,
message: 'User successfully registered.'
})
next();
}
} catch(e) {
next(e);
}
}
exports.login = async (req, res, next) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).send({
success: false,
errors: errors.array()
});
next();
} else {
const { email, password } = req.body;
const user = await findUserPerEmail(email);
if (user) {
const match = await user.comparePassword(password);
if (match) {
req.login(user);
res.status(200).send({
user: user,
success: true,
message: 'User successfully logged in.'
});
next();
} else {
res.status(401).send({
success: false,
errors: {
msg: 'Sorry, that password is not right. Please try again or reset your password.',
param: 'password',
location: 'body'
}
});
next();
}
} else {
res.status(401).send({
success: false,
errors: {
msg: 'Sorry, we could not find an account with that email. Please try again or sign up.',
param: 'email',
location: 'body'
}
});
next();
}
}
} catch(e) {
next(e);
}
}
exports.logout = (req, res, next) => {
req.logout();
res.status(200).send({
success: true,
message: "User successfully logged out."
})
next();
}
Client side
auth.config.js
import axios from 'axios';
const API_URL = 'http://localhost:3001/api/auth/'
const signup = ({ name, username, email, password }) => {
return axios.post(API_URL + 'signup', {
name,
username,
email,
password
});
};
const login = ({ email, password }) => {
return axios
.post(API_URL + 'login', {
email,
password
})
.then((res) => {
return res.data;
});
};
const logout = () => {
return axios.get(API_URL + 'logout').then((res) => {
return res.data;
});
};
const getCurrentUser = () => {
/* return JSON.parse(localStorage.getItem('user')); */
}
const AuthConfig = {
signup,
login,
logout,
getCurrentUser
}
export default AuthConfig;
Redux store:
actions.js
import AuthConfig from '../config/auth.config';
export const SIGNUP_SUBMIT = "signup submit";
export const SIGNUP_SUCCESS = "signup success";
export const SIGNUP_ERROR = "signup error";
export const LOGIN_SUBMIT = "login submit";
export const LOGIN_SUCCESS = "login success";
export const LOGIN_ERROR = "login error";
export const LOGOUT = "logout";
export const signup = (name, username, email, password) => (dispatch) => {
return AuthConfig.signup(name, username, email, password).then(
(response) => {
dispatch({
type: SIGNUP_SUCCESS,
payload: response.data
});
},
(error) => {
dispatch({
type: SIGNUP_ERROR,
error: error.response.data
});
}
);
};
export const login = (email, password) => (dispatch) => {
return AuthConfig.login(email, password).then(
(response) => {
dispatch({
type: LOGIN_SUCCESS,
payload: response.data
});
},
(error) => {
dispatch({
type: LOGIN_ERROR,
error: error.response.data
});
}
);
};
export const logout = () => (dispatch) => {
AuthConfig.logout().then(
(response) => {
dispatch({
type: LOGOUT,
payload: response.message
})
}
);
};
reducers.js
import {
SIGNUP_SUBMIT,
SIGNUP_SUCCESS,
SIGNUP_ERROR,
LOGIN_SUBMIT,
LOGIN_SUCCESS,
LOGIN_ERROR,
LOGOUT
} from "./actions";
const initialState = { isLoggedIn: false, data: {} }
export const authReducer = (state = initialState, action) => {
const { type, payload, error } = action;
switch (type) {
case SIGNUP_SUBMIT:
return {
...state,
isLoggedIn: false,
};
case SIGNUP_SUCCESS:
return {
...state,
isLoggedIn: true,
data: payload
};
case SIGNUP_ERROR:
return {
...state,
isLoggedIn: false,
data: error
};
case LOGIN_SUBMIT:
return {
...state,
isLoggedIn: false,
};
case LOGIN_SUCCESS:
return {
...state,
isLoggedIn: true,
data: payload
};
case LOGIN_ERROR:
return {
...state,
isLoggedIn: false,
data: error
};
case LOGOUT:
return {
...state,
isLoggedIn: false,
data: null
};
default: {
return state;
}
}
};
export default authReducer;
Form.js
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { Formik, useFormikContext } from 'formik';
import * as Yup from 'yup';
/* import { SignupSchema, LoginSchema /* YupSchema * } from './YupSchema'; */
import Input from './Input';
import { signup, login } from '../../store/actions';
import { Eye, EyeSlash, Lock, MedalStar, Personalcard, Sms, User } from 'iconsax-react';
import './Form.css';
const Logger = () => {
const formik = useFormikContext();
useEffect(() => {
console.group("Formik State");
console.log("status", formik.status);
console.log("values", formik.values);
console.log("errors", formik.errors);
console.log("touched", formik.touched);
console.log("isSubmitting", formik.isSubmitting);
console.log("isValidating", formik.isValidating);
console.log("submitCount", formik.submitCount);
console.groupEnd();
}, [
formik.status,
formik.values,
formik.errors,
formik.touched,
formik.isSubmitting,
formik.isValidating,
formik.submitCount
]);
return null;
};
const ServerValidation = () => {
const { values, /* submitForm, */ } = useFormikContext();
useEffect(() => {
if (values.username === 'JDope99' || values.email === 'jdoe#gmail.com') {
/* submitForm(); */
/* console.log('Username or email already in use.'); */
/* alert(JSON.stringify('Username or email already in use.')); */
}
}, [values]);
return null;
};
const Form = () => {
const authServerErrors = useSelector(state => state.authReducer.data.errors);
console.log(authServerErrors);
const [errorServer, setErrorServer] = useState('');
console.log(errorServer);
const [isSignupForm, setIsSignupForm] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const handleShowPassword = () => setShowPassword(!showPassword);
useEffect(() => {
if (authServerErrors === undefined) {
return
} else {
console.log(authServerErrors);
if (Array.isArray(authServerErrors)) {
let fields = authServerErrors.map(el => el.param);
let msgs = authServerErrors.map(el => el.msg);
if (fields !== [] && msgs !== []) {
let errorObj;
fields.reduce(function(obj, key, index) {
obj[key] = msgs[index]
return errorObj = obj
}, {});
setErrorServer(errorObj);
};
} else {
let field = authServerErrors.param;
let msg = authServerErrors.msg;
let error = {};
error[field] = msg;
setErrorServer(error);
}
}
}, [authServerErrors]);
const switchForm = () => {
setIsSignupForm(!isSignupForm);
setShowPassword(false);
}
const initialValues = {
name: '',
username: '',
email: '',
password: ''
}
const dispatch = useDispatch();
const navigate = useNavigate(); // Will be used to redirect user in case of no server error to /Home
const YupSchema = Yup.object().shape({
name: Yup.string().when('isSignupForm', {
is: true,
then: Yup
.string()
.min(2, 'Too short')
.max(20, 'Too long')
.required('Required'),
otherwise: Yup.string().min(2, 'Too short').max(20, 'Too long')
}),
username: Yup.string().when('isSignupForm', {
is: true,
then: Yup
.string()
.min(2, 'Too short')
.max(20, 'Too long')
.required('Required'),
otherwise: Yup.string().min(2, 'Too short').max(20, 'Too long')
}),
email: Yup
.string()
.email('Valid email required')
.required('Required'),
password: Yup
.string()
.matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!##\$%\^&\*])(?=.{8,})/,
"Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character"
)
.required('Required')
})
return (
<div className="container">
<Formik
initialValues={ initialValues }
validationSchema= { YupSchema }
onSubmit={async (values, actions) => {
try {
if (isSignupForm) {
await dispatch(signup(values));
/* errors.setErrors({
name: errorServer.name,
username: errorServer.username,
email: errorServer.email,
password: errorServer.password
})
console.log(errors); */
} else {
await dispatch(login(values));
}
actions.setStatus({
name: errorServer.name || '',
username: errorServer.username || '',
email: errorServer.email || '',
password: errorServer.password || ''
});
} catch (error) {
console.log(error);
}
}}
>
{({ handleChange, handleBlur, handleReset, handleSubmit, values, errors, status, isValid, dirty }) => (
<form
id="auth-form"
className="d-flex flex-column justify-content-start align-items-start py-3"
onChange={ handleChange }
onBlur={ handleBlur }
onReset={ handleReset }
onSubmit= { handleSubmit }
>
<Logger />
{ isSignupForm ? (
<>
<h2>Sign Up</h2>
<p className="text-lg">Welcome to our website! We hope you will enjoy being part of our community.</p>
<div className="input-box mb-3 w-100">
<Input
label="Name"
type="text"
name="name"
className="form-control"
helpText="Please enter your full name."
icon={<Personalcard size="20" color="#666664" variant="Bold" /* className="input-icon" */ />}
errors={errors.name}
value={values.name}
status={ status !== undefined ? status.name : ''}
/>
</div>
<div className="input-box mb-3 w-100">
<Input
label="Username"
type="text"
name="username"
className="form-control"
helpText="Must contain only letters and numbers"
icon={<User size="20" color="#666664" variant="Bold" /* className="input-icon" */ />}
errors={errors.username}
value={values.username}
status={ status !== undefined ? status.username : ''}
/>
</div>
</>
) : (
<>
<h2>Log In</h2>
<p className="text-lg">We are pleased to have you back.</p>
</>
)}
<div className="input-box mb-3 w-100">
<Input
label="Email"
type="text"
name="email"
className="form-control"
helpText="example#email.com"
icon={<Sms size="20" color="#666664" variant="Bold" /* className="input-icon" */ />}
errors={errors.email}
value={values.email}
status={ status !== undefined ? status.email : ''}
/>
</div>
<div className="input-box mb-3 w-100">
<Input
label="Password"
type={ showPassword ? "text" : "password"}
name="password"
/* className="form-control" */
helpText="Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character"
icon={<Lock size="20" color="#666664" variant="Bold" /* className="input-icon" */ />}
toggle={ showPassword ? <Eye size="20" color="#000000" className="toggle-icon" onClick={ handleShowPassword } /> : <EyeSlash size="20" color="#000000" className="toggle-icon" onClick={ handleShowPassword } /> }
errors={errors.password}
value={values.password}
status={ status !== undefined ? status.password : ''}
/>
</div>
{ isSignupForm ? (
<div className="mt-1">
<p>By clicking "Sign Up" you agree to our Customer Terms and Privacy Policy.</p>
</div>
) : null}
<div className="d-flex flex-row mt-1">
<button type="submit" className="btn btn-green me-3" disabled={isSignupForm ? !(isValid && dirty) : null }>
{ isSignupForm ? 'Sign Up' : 'Log In' }
</button>
<button type="reset" className="btn btn-outline-black">
Reset
</button>
</div>
<div className="mt-2">
{ isSignupForm ? (
<p>Already have an account? <button type="button" className="link" onClick={() => { switchForm(); handleReset() } }>Log In</button></p>
)
:
(
<p>New to InkIt? <button type="button" className="link" onClick={() => { switchForm(); handleReset() } }>Sign Up</button></p>
)
}
</div>
<ServerValidation />
</form>
)}
</Formik>
</div>
);
};
export default Form;
Input.js
import React, { useState } from 'react';
import { useField } from 'formik';
import { TickSquare } from 'iconsax-react';
const Input = ({ label, helpText, icon, toggle, errors, status, ...props}) => {
const [field, meta, helpers] = useField(props);
console.log({ field, meta, helpers });
// Show inline feedback if EITHER
// - the input is focused AND value is longer than 2 characters
// - or, the has been visited (touched === true)
const [didFocus, setDidFocus] = useState(false);
const handleFocus = () => setDidFocus(true);
const showInputFeedback = (!!didFocus && field.value.trim().length > 2) || meta.touched;
return (
<div className={`form-control px-3 py-2 ${showInputFeedback ? (meta.error ? 'invalid' : 'valid') : ""}`}>
<div className="label-box d-flex flex-row justify-content-between align-items-center">
<label htmlFor={props.name}>{label}</label>
{showInputFeedback ? (
<div className="feedback text-sm">
{ meta.error ? meta.error : status ? status : <TickSquare size="20" color="#06d6a0" variant="Bold" />}
</div>
)
: null}
</div>
<div className="d-flex flex-row flex-nowrap">
<span className="input-icon icon-left">
{icon}
</span>
<input className={ toggle ? "form-control input-password" : "form-control" } autoComplete="off" onFocus={ handleFocus } {...field} {...props} />
{toggle ? (
<span className="input-icon icon-right">
{toggle}
</span>
)
: null
}
</div>
<div className="help-text text-xs">
{helpText}
</div>
</div>
);
}
export default Input;
Chrome console.log:
enter image description here

React: Set Value in a TextField

Back again with another react question!
UserInfoStep.js (Child)
function UserInfoStep({ PUID, FirstName, handleChangeParent }) {
const { dispatch } = useContext(ContactFormContext);
return (
//some controls
<FormControl>
<Grid container spacing={3}>
<Grid item xs={12}>
<TextField required label="PUID"
style={{ width: '100%' }}
name="PUID"
onChange={e =>
dispatch({ type: "PUID", payload: e.target.value })
}
value={PUID} variant="outlined" />
</Grid>
</Grid>
<Grid item xs={12} md={6}>
<TextField
required
style={{ width: '100%' }}
label="First Name"
name="FirstName"
onChange={e =>
dispatch({ type: "FIRST_NAME_CHANGE", payload: e.target.value })
}
value={FirstName}
variant="outlined"
/>
</Grid>
</FormControl>
);
}
export default UserInfoStep;
ContactForm.js (Parent)
const initialState = {
PUID: "",
FirstName: "",
total: 0
//other params removed
};
function formReducer(state, action) {
switch (action.type) {
case "PUID":
fetch('https://api.npms.io/v2/search?q=react')
.then(result => result.json())
.then(data => {
console.log(data.total);
});
return { ...state, PUID: action.payload };
case "FIRST_NAME_CHANGE":
return { ...state, FirstName: action.payload };
default:
throw new Error();
}
}
function ContactForm() {
const { PUID, FirstName, total } = state;
const steps = [
<UserInfoStep handleChangeParent={handleChange} {...{ PUID, FirstName }} />,
];
return (
<ContactFormContext.Provider value={{ dispatch }}>
//some code removed
</ContactFormContext.Provider>
);
}
export default ContactForm;
I want to set the value returned from the API call (data.total), in the FirstName input box when PUID onChange triggers. The API call can also be moved to the Child (UserInfoStep) if needed.
Edit
I have now moved my API call to UserInfoStep.js
const onPUIDChanged = (event) => {
fetch('https://api.npms.io/v2/search?q=react')
.then(result => result.json())
.then(data => {
console.log(data.total);
});
dispatch({ type: "PUID", payload: event.target.value });
};
<Grid item xs={12}>
<TextField required label="PUID"
style={{width:'100%'}}
name="PUID"
onChange={onPUIDChanged}
value={PUID} variant="outlined"/>
</Grid>
Don't use API call in your reducer, the reducer is just a function that gets an action and returns the state.
The philosophy of using action - reducer - store in redux architecture is to separate the logic.
There are two options for you:
using a third library like redux-saga to control the async API calling and handle the side effects with the power of generators.
call the API in your component/page and then dispatch the proper action for success/failure cases.

React Admin Confirmation Dialogue On Save

I'm trying to show confirmation dialogue on saving in react admin framework but saving functionality started breaking.
Error
--> Converting circular structure to JSON
--> starting at object with constructor 'FiberNode'
| property 'stateNode' -> object with constructor 'HTMLButtonElement'
--- property '__reactInternalInstance$mtamow8fbfp' closes the circle
The dataProvider threw an error. It should return a rejected Promise instead.
I suspect its redirection issue but couldn't figure out.
It works if i don't use Confirmation Dialog and call handleSave in onSave prop of SaveButton
React admin version - 3.4.2
Is this the correct way to do it? please help
Confirm.tsx
/**
* Confirmation dialog
*
* #example
* <Confirm
* isOpen={true}
* title="Delete Item"
* content="Are you sure you want to delete this item?"
* confirm="Yes"
* confirmColor="primary"
* ConfirmIcon=ActionCheck
* CancelIcon=AlertError
* cancel="Cancel"
* onConfirm={() => { // do something }}
* onClose={() => { // do something }}
* />
*/
const Confirm: FC<ConfirmProps> = props => {
const {
isOpen = false,
loading,
title,
content,
confirm,
cancel,
confirmColor,
onClose,
onConfirm,
translateOptions = {}
} = props;
const classes = useStyles(props);
// const translate = useTranslate();
const handleConfirm = useCallback(
e => {
e.stopPropagation();
onConfirm(e);
},
[onConfirm]
);
const handleClick = useCallback(e => {
e.stopPropagation();
}, []);
return (
<Dialog
open={isOpen}
onClose={onClose}
onClick={handleClick}
aria-labelledby="alert-dialog-title"
>
<DialogTitle id="alert-dialog-title">{title}</DialogTitle>
<DialogContent>
<DialogContentText className={classes.contentText}>
{content}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
disabled={loading}
onClick={onClose}
className={classnames("ra-confirm", {
[classes.confirmWarning]: confirmColor === "primary"
})}
>
<CancelIcon className={classes.iconPaddingStyle} />
{cancel}
</Button>
<Button
disabled={loading}
onClick={handleConfirm}
className={classnames("ra-confirm", {
[classes.confirmWarning]: confirmColor === "warning",
[classes.confirmPrimary]: confirmColor === "primary"
})}
autoFocus
>
<ActionCheck className={classes.iconPaddingStyle} />
{confirm}
</Button>
</DialogActions>
</Dialog>
);
};
export default Confirm;
SaveWithConfirmation.tsx*
import React, { useCallback, useState, Fragment } from "react";
import { useFormState } from "react-final-form";
import {
SaveButton,
Toolbar,
useCreate,
useRedirect,
useNotify,
Button
} from "react-admin";
import Confirm from "./Confirm";
const SaveWithConfirmButton = ({ resource, ...props }) => {
const [create, { loading }] = useCreate(resource);
const redirectTo = useRedirect();
const notify = useNotify();
const { basePath } = props;
// get values from the form
const formState = useFormState();
const [open, setOpen] = useState(false);
const handleDialogClick = e => {
setOpen(true);
};
const handleDialogClose = e => {
setOpen(false);
e.stopPropagation();
};
const handleSave = useCallback(
(values, redirect) => {
// call dataProvider.create() manually
// setOpen(true);
create(
{
payload: { data: { ...values } }
},
{
onSuccess: ({ data: newRecord }) => {
notify("ra.notification.created", "info", {
smart_count: 1
});
redirectTo(redirect, basePath, newRecord.id, newRecord);
},
onFailure: error => {
notify(
typeof error === "string"
? error
: error.message || "ra.notification.http_error",
"warning"
);
setOpen(false);
}
}
);
},
[create, notify, redirectTo, basePath, formState]
);
return (
<>
<SaveButton {...props} onSave={handleDialogClick} />
<Confirm
isOpen={open}
loading={loading}
title="Please confirm"
content="Are you sure you want to apply the changes ?"
onConfirm={handleSave}
onClose={handleDialogClose}
/>
</>
);
};
export default SaveWithConfirmButton;
Usage
const DateCreateToolbar = props => (
<Toolbar {...props}>
<SaveWithConfirmButton resource="dates" />
</Toolbar>
);
const DateCreate = props => {
return (
<Create {...props}>
<SimpleForm toolbar={<DateCreateToolbar />} redirect="list">
<DateTimeInput
validate={required()}
label="Start Date"
source="startDate"
/>
<DateTimeInput
validate={required()}
label="End Date"
source="endDate"
/>
</SimpleForm>
</Create>
);
};
export default DateCreate;
<Toolbar {...props}>
<SaveButton
label="Save"
redirect="edit"
submitOnEnter={false}
handleSubmitWithRedirect={
() => {
if(!window.confirm('Are you sure?'))
return false;
return props.handleSubmitWithRedirect();
}
}
/>
</Toolbar>
The payload data in the create, should be called with the form values:
...
create(
{
payload: { data: { ...formState.values } }
},
...
Alternative variant: there is a library called "react-confirm-alert".
Usage example:
import { confirmAlert } from 'react-confirm-alert';
/* In function you may ask confirmation like below */
confirmAlert({
title: 'Question',
message: 'Delete? id:' + id,
buttons: [
{
label: 'Yes',
onClick: () => {
deleteInfo(id)
.then(result => {
if (result.code == 0) {
notify("Deleted");
refreshInfo();
} else {
notify("Error occurred:" + result.msg);
}
}).catch((error) => {
notify('Error occurred:' + error, 'warning');
});
}
},
{
label: 'No',
onClick: () => { }
}
]
});

Interaction with Apollo GraphQL Store not Working

I'm Trying to Learn GraphQL by Developing a Simple To-do List App Using React for the FrontEnd with Material-UI. I Need to Now Update the Information on the Web App in Real-time After the Query Gets Executed. I've Written the Code to Update the Store, But for Some Reason it Doesn't Work. This is the Code for App.js.
const TodosQuery = gql`{
todos {
id
text
complete
}
}`;
const UpdateMutation = gql`mutation($id: ID!, $complete: Boolean!) {
updateTodo(id: $id, complete: $complete)
}`;
const RemoveMutation = gql`mutation($id: ID!) {
removeTodo(id: $id)
}`;
const CreateMutation = gql`mutation($text: String!) {
createTodo(text: $text) {
id
text
complete
}
}`;
class App extends Component {
updateTodo = async todo => {
await this.props.updateTodo({
variables: {
id: todo.id,
complete: !todo.complete,
},
update: (store) => {
const data = store.readQuery({ query: TodosQuery });
data.todos = data.todos.map(existingTodo => existingTodo.id === todo.id ? {
...todo,
complete: !todo.complete,
} : existingTodo);
store.writeQuery({ query: TodosQuery, data })
}
});
};
removeTodo = async todo => {
await this.props.removeTodo({
variables: {
id: todo.id,
},
update: (store) => {
const data = store.readQuery({ query: TodosQuery });
data.todos = data.todos.filter(existingTodo => existingTodo.id !== todo.id);
store.writeQuery({ query: TodosQuery, data })
}
});
};
createTodo = async (text) => {
await this.props.createTodo({
variables: {
text,
},
update: (store, { data: { createTodo } }) => {
const data = store.readQuery({ query: TodosQuery });
data.todos.unshift(createTodo);
store.writeQuery({ query: TodosQuery, data })
},
});
}
render() {
const { data: { loading, error, todos } } = this.props;
if(loading) return <p>Loading...</p>;
if(error) return <p>Error...</p>;
return(
<div style={{ display: 'flex' }}>
<div style={{ margin: 'auto', width: 400 }}>
<Paper elevation={3}>
<Form submit={this.createTodo} />
<List>
{todos.map(todo =>
<ListItem key={todo.id} role={undefined} dense button onClick={() => this.updateTodo(todo)}>
<ListItemIcon>
<Checkbox checked={todo.complete} tabIndex={-1} disableRipple />
</ListItemIcon>
<ListItemText primary={todo.text} />
<ListItemSecondaryAction>
<IconButton onClick={() => this.removeTodo(todo)}>
<CloseIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)}
</List>
</Paper>
</div>
</div>
);
}
}
export default compose(
graphql(CreateMutation, { name: 'createTodo' }),
graphql(UpdateMutation, { name: 'updateTodo' }),
graphql(RemoveMutation, { name: 'removeTodo' }),
graphql(TodosQuery)
)(App);
Also, i Want to Create Some List Items but that Doesn't Work Either. I'm Trying to get the Text Entered in the Input Field in Real-time Using a Handler Function handleOnKeyDown() in onKeyDown of the Input Field. I Pass in a event e as a Parameter to handleOnKeyDown(e) and when i console.log(e) it, instead of logging the Text Entered, it Returns a Weird Object that i Do Not Need. This is the Code that Handles Form Actions:
export default class Form extends React.Component{
state = {
text: '',
}
handleChange = (e) => {
const newText = e.target.value;
this.setState({
text: newText,
});
};
handleKeyDown = (e) => {
console.log(e);
if(e.key === 'enter') {
this.props.submit(this.state.text);
this.setState({ text: '' });
}
};
render() {
const { text } = this.state;
return (<TextField onChange={this.handleChange} onKeyDown={this.handleKeyDown} label="To-Do" margin='normal' value={text} fullWidth />);
}
}
This above Code File Gets Included in my App.js.
I Cannot Figure out the Issues. Please Help.
I was stuck with a similar problem. What resolved it for me was replacing the update with refetchQueries as:
updateTodo = async todo => {
await this.props.updateTodo({
variables: {
id: todo.id,
complete: !todo.complete
},
refetchQueries: [{
query: TodosQuery,
variables: {
id: todo.id,
complete: !todo.complete
}
}]
});
};
For your second problem, try capitalizing the 'e' in 'enter' as 'Enter'.
Hope this helps!

Resources