React | Formik Async Validatin, cannot read .then of undefined - reactjs

I am using React and Formik to handle my forms. In a component, I have a simple input text. That text is handled for error if being empty by formik. The problem is, I also want to asynchronously handle server validation (if the input.target.value already exist in the database).
Formik provides that functionality, but I am obviously doing something wrong because I get the following error.
Cannot read property .then of undefined
Code Sandbox here: https://codesandbox.io/s/nkm2zyy4z0
So far, I have done the following. According to formik documentation:
const asyncValidation = values =>
listItems.then(data => {
const errors ={};
if (data.includes(values.name)) {
errors.name = 'Username already exist';
}
if (!data.include(values.name) {
throw errors;
}
});
I also tried to create another iteration of the asyncValidation promise, as you see below:
const asyncValidation = values =>
new Promise((resolve, reject) => {
const errors = {};
if (listGroups.includes(values.name)) {
console.log(errors)
errors.email = 'Required';
}
if (!listGroups.includes(values.name)) {
console.log(errors)
reject(errors);
} else {
resolve();
}
});
But still, I get an error:
index.jsx:21 Uncaught (in promise) TypeError: Cannot read property 'name' of undefined
Not sure what to do. Name if a property of the values object. If I print on the console, the results, it will print this:
{name: "generalGroup1", description: ""}
description: ""
name: "generalGroup1"
__proto__: Object
Not sure what is wrong here...
Here, formik uses promises to handle this async functionality. I am a little perplex, on what error should be thrown. Maybe I made the mistake here, since I want to basically say. If the value matches something in the database, say that it already exists. If not don't throw any error.
Promises need to throw errors, so what should I do there, and how should I solve my console error.
I am also using a built in service to call the list of items from the API, in order to check against the value being on the input.
export const listItems = () => {
const options = {
method: httpMethod.GET,
url: endpoint.LIST_ITEMS
};
return Instance(options);
};
Below is the part of the component relevant to the input field:
class ItemDetailsForm extends React.Component {
static propTypes = {
...formPropTypes,
data: PropTypes.object
};
handleSubmit = values => {
const { id, onSubmit } = this.props;
onSubmit(id, values);
asyncValidation();
};
render() {
const { data } = this.props;
return (
<Formik
initialValues={{ ...data }}
onSubmit={this.handleSubmit}
validationSchema={validationSchema}
render={({ values, touched, errors, handleChange, handleBlur, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-md-3">
<div className="form-group">
<label htmlFor="itemName">
Item name <span className="text-danger">*</span>
</label>
<input
type="text"
onChange={handleChange}
onBlur={handleBlur}
value={values.name}
name="name"
className={classNames('form-control', {
'is-invalid': errors.name && touched.name
})}
id="itemsName"
placeholder="Some Item"
/>
{!!errors.name && touched.name && (
<div className="text-danger">{errors.name}</div>
)}
</div>
<button className="btn btn-primary" type="submit">
Submit
</button>
</div>
</div>
</form>
)}
/>
);
}
}
export default ItemDetailsForm;
I have followed the formik docs, almost to the teeth, but something is obviously wrong. Can you help a bit. I am relatively new to programming, so if you could explain my mistake it would be great.

Related

Type 'FormikValues' is missing the following properties from type 'Exact<{

I have the following form:
import { Field, Form, Formik, FormikProps, FormikValues } from 'formik'
import { NextPage } from 'next'
import React from 'react'
import { useCreateUserMutation } from '../generated/graphql'
const Register: NextPage = () => {
const formikRef = useRef<FormikProps<FormikValues>>(null)
React.useEffect(() => {
nameInit = localStorage.getItem('name') ?? ''
console.log(nameInit)
if (formikRef.current) {
formikRef.current.setFieldValue('name', nameInit)
}
}, [])
const [register, { data, error, loading }] = useCreateUserMutation()
return (
<Formik
innerRef={formikRef}
initialValues={{
name: nameInit,
}}
onSubmit={async (values) => {
console.log(values)
const response = await register({ variables: values })
console.log(response)
}}
>
{() => (
<Form className="space-y-8 divide-y divide-gray-200">
<div className="sm:col-span-2">
<label
htmlFor="name"
>
First name
</label>
<Field
type="text"
name="name"
id="name"
autoComplete="given-name"
/>
</div>
<div className="flex justify-end">
<button
type="submit"
>
Submit
</button>
</div>
</Form>
)}
</Formik>
)
}
Everything was working fine until I introduced the lines:
const formikRef = useRef<FormikProps<FormikValues>>(null)
and
innerRef={formikRef}
Now variables in the line const response = await register({ variables: values }) is red underlined with the message: Type 'FormikValues' is missing the following properties from type 'Exact<{ name: string; }>': name ts(2739)
When I run the code, the website works perfectly fine though and sends the data to the server as expcted with no errors.
How can I fix this warning?
Edit:
I checked the definition of the FormikValues type:
/**
* Values of fields in the form
*/
export interface FormikValues {
[field: string]: any;
}
When I add the line innerRef={formikRef} the type of variables changes from a dictionary to FormikValues
The problem
FormikValues type doesn't match signature of register function from useCreateUserMutation
Solution 1
Replace FormikValues with { name: string }. Should work as expected.
Solution 2
Why do we even need this useRef? As I understand the only reason to keep formik ref is to be able to use setFieldValue. Maybe you should take a look at useFormik which exports formik API directly or useFormikContext

SOLVED : why Django admin list is not updating the edit from the frontend React js form?

i am trying to make a CRUD app in DRF-Reactjs by following Tania rascia's example
i have successfully implemented add, delete, list view. but i am trying to edit a specific row which is not updating in DRF backend. but the edited row is shown in the frontend list. why it is not updating in django admin list?
in DRF side views.py:
#api_view(['POST'])
def TodoUpdate(request, pk):
todo = Todo.objects.get(id=pk)
serializer = TodoSerializer(instance=todo, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
i am using cors header to interface between frontend to backend. here is the frontend code for edit:
App.js:
import React,{Fragment, useState,useEffect} from 'react'
import EditList from './components/EditList';
import axios from 'axios'
export default function App() {
const initialTodoSate = { id: null, title: "", body: "" };
const [todos, setTodos] = useState([]);
const [todoList, setTodolist] = useState(initialTodoSate);
const [editing, setEditing] = useState(false);
useEffect(()=>{
axios.get("http://localhost:8000/api/todo-list",{})
.then(res=>{
setTodos(res.data)
}).catch(err=>{
console.log(err)
})
},[])
const addTodoList = (todo) => {
axios
.post("http://localhost:8000/api/todo-create/",todo)
.then((res) => {
console.log(res.data);
todo.id = todos.length + 1;
setTodos([todo, ...todos]);
})
.catch((err) => {
console.log(err);
});
};
const deleteTodo = (id) => {
setEditing(false);
axios.delete(`http://localhost:8000/api/todo-delete/${id}/`)
.then(res=>{
setTodos(todos.filter((todo) => todo.id !== id));
}).catch(err=>{
console.log(err)
})
};
const updateTodo = ( id,updatedTodo) => {
axios
.post(`http://localhost:8000/api/todo-update/${id}/`, id)
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.log(err);
});
setEditing(false);
setTodos(todos.map((todo) => (todo.id === id ? updatedTodo : todo)));
};
const editRow = (todo) => {
setEditing(true);
setTodolist({
id: todo.id,
title: todo.title,
description: todo.description,
});
};
return (
<div className="container">
<h1>Django-based Todo with React Hooks</h1>
{editing ? (
<Fragment>
<h3>Edit Task</h3>
<EditList
editing={editing}
setEditing={setEditing}
todoList={todoList}
updateTodo={updateTodo}
/>
</Fragment>
) : (
<Fragment>
<CreateTodo addTodoList={addTodoList} />
<hr />
</Fragment>
)}
<div className="flex-row">
<div className="flex-large">
<TodoList todos={todos} editRow={editRow} deleteTodo={deleteTodo} />
</div>
</div>
</div>
);
}
and EditList.js:
import React, { useState,useEffect } from "react";
export default function EditList({ todoList, setEditing, updateTodo }) {
const [todo, setTodo] = useState([todoList]);
useEffect(() => {
setTodo(todoList);
}, [todoList]);
const handleChange = (e) => {
const { name, value } = e.target;
setTodo({ ...todo, [name]: value });
};
return (
<form
onSubmit={(e) => {
e.preventDefault();
updateTodo(todo.id, todo);
}}
>
<label>Title:</label>
<br />
<input
type="text"
name="title"
value={todo.title}
onChange={handleChange}
/>
<br />
<label>Description:</label>
<br />
<input
type="text"
name="body"
value={todo.body}
onChange={handleChange}
/>
<br />
<button>Update Task</button>
<button onClick={() => setEditing(false)} className="button muted-button">
Cancel
</button>
</form>
);
}
when i try to edit one row with title and body, it is edited and after pressing the update button, the updated row included in the list. but the problem is when i look into the django admin it has not been updated and when i check the development tools, i found an error:
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components
at input
at form
at EditList (http://localhost:3000/static/js/main.chunk.js:511:3)
at div
at App (http://localhost:3000/static/js/main.chunk.js:70:83)
console. # vendors~main.chunk.js:31671
where am i having the mistake?
can anyone help me please? please let me know if you need any additional codes or information.
Trying to update something should be done in a put request, not a post request. This is a REST API convention, but a discrepancy may have some consequence down the line.
In this case, the error in your development tools is telling you that one of your components has an onChange/onSubmit etc property that is changing over the course of one mount from null to a function. This is not what's causing your issue, but I suspect it can be fixed by declaring the code in a handleSubmit function and then putting that into your onSubmit.
I think the error that's actually causing your problem is that the updatedTodo is not being sent to the backend. All that is being sent is the id (second parameter of axios.post). So if you pause the backend during execution, you would see that request.data = the id only, when it should be TodoSerializer's readable fields.
PS:
You can add a "debugger;" statement in the code after the updateToDo async request error to see what the error actually is (read more on the development tools debugging - browser dependent).
Don't abuse fragments - in this case, it would make for a more accessibility-friendly experience if you use divs in most of these components. Wouldn't it make more sense if the heading of some content was grouped with the content? https://developers.google.com/web/fundamentals/accessibility/focus/dom-order-matters

form checkbox animations are not working but form does work

I'm having an odd issue with React and Formik(2.1).
I have a group of checkboxes on my page that the user can check on or off.
The behind-the-scenes part is working...the checkbox values that the user selects are being sent to the API backend and I see the selected values when I write them out to the browser console.
However, the actual checkbox that appears or disappeared inside the little checkbox square never shows.
I'm not sure why either. I tried updating to the latest version of Formik and React, but it doesn't change.
Since the actual values are still being passed, I can't figure out a way to debug it.
Here is the React component that generates the checkboxes:
const PlayerList = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://localhost:44376/api/players',
);
setData(result.data);
};
fetchData();
}, []);
return (
<>
{data.map((item, index) => (
<label key={index}>
<Field id={item.id} key={index} type="checkbox" name="gamePlayers" value={item.id} />
{item.name}
</label>
))}
</>
);
}
export default PlayerList;
This component will generate form inputs that look like this:
<input name="gamePlayers" id="1" type="checkbox" value="1">
And here is the React component with the formik form:
<Formik
initialValues={{
gamePlayers: [],
email: "",
name: "",
phone: ""
}}
onSubmit={async values => {
await new Promise(resolve => setTimeout(resolve, 500));
axios({
method: "POST",
url: "https://localhost:44376/api/gameentry",
data: JSON.stringify(values)
});
console.log(JSON.stringify(values, null, 2));
}}
>
{props => {
const {
values,
touched,
isSubmitting,
handleSubmit,
handleReset,
setFieldValue
} = props;
return (
<form onSubmit={handleSubmit}>
<div id="gameSection">Game Information</div>
<div id="players">
<label htmlFor="gamePlayers">
Game Player Type (check all that apply)
</label>
<PlayerList />
</div>
);
}}
</Formik>
</div>
);
So everything looks ok, the form looks good. It's just the checkbox animations are not working and I can't think of a way to debug because, technically, it's working.
Has anyone ever seen something like this?
Here is a link to a code sandbox. It's a bit different because I had to modify it to work within the sandbox enviroment.
https://codesandbox.io/s/inspiring-hodgkin-exufn
Thanks!
Implementing a checkbox in Formik can be tricky. you see you have to pass the JSX of input along with its label into the component prop.
Please find my work below.
https://codesandbox.io/s/lingering-https-yw0jl

Formik <FieldArray> initialValue undefined if using componentDidMount/setState

I've never used Formik before, but hearing positive things on the net it sounds like this is a great solution compared to rolling own...which I usually try to do.
The docs for Formik have a simple, single instance of some of their attributes, like initialValues and validationSchema. However, I need to make this reusable, since I have 2 versions of this in my app. So i want to pass in the fields as props, then create the initialValues as a form of state. This is OK, yes?
However, nothing renders...the value, errors params shown in the documents, always shows as undefined. Why is this? The state is updated and therefore, I assume the initialValues will update the values object. Scope inside the render method does not allow me to be able to use this.state.initial for example...
Errors object remains undefined, but I thought it should at least exist?
Basically, I have a Parent component that is rending inner components, one of which is a form group. So I am passing the array of fields, and a schema object to the container of Formik component like so:
const newCompanyFields = ["name", "address", "revenue", "phone"];
<Card
headerText="Create New Company"
id="new-company"
renderWith={() => (
<React.Fragment>
<div className="header">Add Company</div>
<Forms fields={newCompanyFields} schema={NewCompanySchema} />
</React.Fragment>
)}
/>
Then, inside the <Forms> component, we will create instance of Formik like so:
class Forms extends Component {
state = {
initial: {}
};
componentDidMount() {
// we need to get the list of fields and setState to be used by Formik below
if (this.props.fields) {
let initialItems = {};
this.props.fields.forEach(item => {
return (initialItems[item] = "");
});
this.setState({ initial: initialItems });
}
}
render() {
return (
<StyledForms>
<Formik
initialValues={this.state.initial}
validationSchema={this.props.schema}
onSubmit={values => {
console.log(values, " submitted");
}}
render={({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
isSubmitting
}) => (
<Form>
<FieldArray
name="field-company"
render={() => (
<div>
{values &&
Object.keys(values).map((field, index) => (
<div key={index}>
<Field name={field} />
{errors[field] && touched[field] ? (
<div>{errors[field]}</div>
) : null}
</div>
))}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</div>
)}
/>
</Form>
)}
/>
</StyledForms>
);
}
Link to Console screenshot: https://screencast.com/t/Pt7YOxU1Oq57
Thank you for clarification.
UPDATE
If I update my initialValues attribute to NOT rely on component state, it works.
// assume ComponentDidMount from above is removed now
const getInitialValues = passedFields => {
let initialItems = {};
passedFields.forEach(item => {
return (initialItems[item] = "");
});
return initialItems;
};
<Formik
initialValues={getInitialValues(this.props.fields)}
...
/>
Is this expected?
You need to use render() in your Formik component.
Something like:
<Formik render={({ values }) => {
<Form>
<FieldArray>
... use values
</FieldArray>
</Form>
}} />

How to test a simple async React action with Jest?

I am trying to test a Redux action and need assistance with testing an action with side-effects.
Here is my action :
export function login(email, password) {
return dispatch => {
dispatch(setLoginSuccess(false));
loginApi(email, password, error => {
dispatch(setLoginPending(false));
if (!error) {
dispatch(setLoginSuccess(true));
} else {
dispatch(setLoginError(error));
}
});
}
}
Below is the loginApi function to authenticate user :
export function loginApi(email, password, callback) {
if (email === 'test#test.com' && password == '123') {
return callback(null);
} else {
return callback(new Error('Please provide valid email and password'));
}
};
Additionally, I am facing an issue while simulating a form submit in my component with Enzyme and Jest.
Here is the code for the same :
render() {
let {email, password, emailValid} = this.state;
let {isLoginPending, isLoginSuccess, loginError} = this.props;
return (
<div className="col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2 col-xs-10 col-xs-offset-1">
<h3 className="text-center">Login</h3>
<form className="login-form" onSubmit={this.handleSubmit.bind(this)}>
<div className={emailValid? "form-group has-success" : (emailValid == undefined)? "form-group": "form-group has-error"}>
<label>Email address</label>
<input type="email" name="email" className="form-control" ref="userEmail"
placeholder="Enter your email" onChange={this.handleChange.bind(this)}/>
</div>
{/* Checking if email valid or not */}
{this.props.emailValid? "" : (this.props.emailValid == undefined)? "" : <p className="text-danger">Please provide a valid email!</p>}
<div className="form-group">
<label>Password</label>
<input type="password" name="password" className="form-control" ref="userPassword"
placeholder="Enter your password" onChange={this.handleChange.bind(this)}/>
</div>
<button type ="submit" className="btn btn-primary btn-block" disabled={!this.props.emailValid}>Get Started</button>
{/* Displaying error messages */}
{ loginError && <div className="auth-error-msg"><p className="text-danger">{loginError.message}</p></div> }
</form>
</div>
);
};
Here is the code for the handleSubmit event:
handleSubmit(e){
e.preventDefault();
this.props.login(this.refs.userEmail.value, this.refs.userPassword.value);
this.setState({
email: '',
password: ''
});
}
I am trying to simulate the Submit event in this way :
it('should render 1 error block on submitting invalid form', () => {
// Render a checkbox with label in the document
const spy = jest.fn();
const component = shallow(<Login login={spy}/>);
const form = component.find('form').simulate('submit');
});
But it currently throws an error as it cannot find preventDefault. How do I test this event?
I would recommend you to split the testing. Submitting the form and testing the actions are two separate things. For testing the action with jest, you need to dispatch the action to a mock store, and see which is the final state of the store. Something like this:
describe('actions', () => {
let store
beforeEach(() => {
store = mockStore({})
})
it('should dispatch the correct actions', () => {
const expectedActions = [
{ type: 'action1', ...arguments },
{ type: 'action2', ...arguments }
]
store.dispatch(login('user', 'password'))
expect(store.getActions()).toEqual(expectedActions)
})
})
you can do multiple test cases, adapting the expected actions to what you passed as parameters.
For creating a mock store, there are multiple packages that can do the job. Here is one example with support for thunks:
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
export default mockStore
I personally would not spend too much effort testing that the form submits. At the end, that is a standard html thing, so I would instead focus on the components that you have built yourself.
And another tip: If you have gone through all the trouble of using redux, don't fall back to normal state. That setState that you have would be much more easily implemented, and tested, by using a normal reducer and getting that into your state as well.

Resources