GraphQL Query Defined here
const countryQuery = gql`
query getQuery($filter: _CountryFilter!) {
Country(filter: $filter) {
_id
name
capital
population
nativeName
}
}
`
pages/search.js
export default function Search() {
const [filter, setFilter] = useState({ name: 'Chile' })
const { data, loading, error } = useQuery(countryQuery, {
variables: { filter },})
if (loading){
return <Loader />;
}
if (error) return <p>Error</p>;
return (
<div className="body">
<h1>
Get Information
<br /> about Countries!
</h1>
<div className="wrapper">
<input
className="search"
type="text"
id="search"
placeholder='Enter a Country'
/>
<button
className="submit"
type="submit"
value=" "
onClick={e => setFilter({ name: e.target.value })}
onBlur={e => setFilter({ name: e.target.value })}
> Search
</button>
<div>
{ data?.Country && <CountryInfo country={data?.Country[0]} /> }
</div>
</div>
</div>
);
}
components/queryResults.js This is were I am getting the error. Is {country.name}, {country.capital} etc. the incorrect way to apply the data here?
import React from 'react'
import { Card, ListGroup, ListGroupItem } from 'react-bootstrap'
const CountryInfo = ({country}) => (
<div>
<Card style={{ width: '18rem' }}>
<Card.Body>
<Card.Title>{country.name}</Card.Title>
</Card.Body>
<ListGroup className="list-group-flush">
<ListGroupItem>Capital: {country.capital} </ListGroupItem> {' '}
<ListGroupItem>Population: {country.population}</ListGroupItem>
<ListGroupItem>Native Name: {country.nativeName}</ListGroupItem>
</ListGroup>
</Card>
</div>
)
export default CountryInfo;
When I type a country and click the search the error is happening on this line: <Card.Title>{country.name}</Card.Title> Why is name, capital, population and nativeName undefined? What am I doing wrong?
I'm looking at this line and would like to note that if data.country is an empty array, it will still be truthy. Add a check to see if there is a value in the first element of the array before rendering CountryInfo
{ data?.Country && <CountryInfo country={data?.Country[0]} /> }
Related
I have a form that uses accordion component.
When I print values using watch() then collapse accordion. the values get deleted from inputs when I open it again.
This behaviour is not happening when I don't use watch()
I would like to know why this is happening ? watch() should only listen to data as I know.
CodeSandbox
CreateTest.ts
import { QuestionCreatingForm } from "./QuestionForm";
import {
AccordionHeader,
AccordionItem,
AccordionPanel,
Accordion
} from "#fluentui/react-components";
import { Button, Form } from "#fluentui/react-northstar";
import { useFieldArray, useForm } from "react-hook-form";
export function CreateTest() {
const methods = useForm();
const { control, register, handleSubmit, watch } = methods;
const { fields, append } = useFieldArray({
control,
name: "questions"
});
const addQuestion = (event: any) => {
event.preventDefault();
append({ name: "" });
};
const onSubmit = (data: any) => {
alert(JSON.stringify(data));
};
return (
<div className="w-8/12 m-auto">
{JSON.stringify(watch())}
<Form onSubmit={handleSubmit(onSubmit)}>
{fields.map((field, index) => (
<Accordion key={field.id} collapsible>
<AccordionItem value="1">
<AccordionHeader>Accordion Header </AccordionHeader>
<AccordionPanel>
<QuestionCreatingForm
fieldId={field.id}
index={index}
{...{ control, register, watch }}
/>
</AccordionPanel>
</AccordionItem>
</Accordion>
))}
<Button
className="my-10"
content="Add question"
primary
fluid
onClick={addQuestion}
/>
<Button
className="my-10"
fluid
content="submit"
primary
type="submit"
/>
</Form>
{/* </FormProvider> */}
</div>
);
}
QuestionForm.ts
import {
Button,
Divider,
FormCheckbox,
FormInput,
TrashCanIcon
} from "#fluentui/react-northstar";
import { SyntheticEvent } from "react";
import {
Control,
FieldValues,
useFieldArray,
UseFormRegister,
UseFormWatch
} from "react-hook-form";
export function QuestionCreatingForm({
index,
fieldId,
control,
register,
watch
}: {
index: number;
fieldId: string;
control: Control<FieldValues, any>;
register: UseFormRegister<FieldValues>;
watch: UseFormWatch<FieldValues>;
}) {
const { fields, append, remove } = useFieldArray({
control,
name: `questions.${index}.responses`
});
const addResponse = (event: SyntheticEvent<HTMLElement, Event>) => {
event.preventDefault();
append({ name: "" });
};
const deleteResponse = (index: number) => {
remove(index);
};
return (
<>
<FormInput
label="Question"
required
fluid
key={index}
{...register(`questions.${index}.name` as const)}
/>
<div className="w-10/12 m-auto">
{fields.map((field, i) => (
<div className="flex w-full">
<FormCheckbox />
<div className="w-full" key={field.id}>
<FormInput
{...register(`questions.${index}.responses.${i}.name` as const)}
defaultValue=""
label={`reponses ${i + 1}`}
required
fluid
/>
</div>
<Button
text
styles={{ color: "red", placeSelf: "end" }}
icon={<TrashCanIcon />}
onClick={(e) => deleteResponse(i)}
iconOnly
/>
</div>
))}
<Button
content="Ajouter une réponse"
tinted
fluid
onClick={addResponse}
/>
</div>
<Divider />
</>
);
}
I have a form with a fields array. At the bottom of the form there is a button that deletes all list items (in fact, it does form.restart).
When deleting fields, the validation of each field is triggered, but the value of the field is undefined. This breaks the validation logic.
In theory, this validation should not exist at all.
How to get rid of this validation on form.restart?
Code:
(https://codesandbox.io/s/festive-water-6u3t69?file=/src/App.js:0-1477)
import React from "react";
import { Form, Field } from "react-final-form";
import { FieldArray } from "react-final-form-arrays";
import arrayMutators from "final-form-arrays";
import "./styles.css";
export default function App() {
const [values] = React.useState({ items: [] });
const renderForm = ({ form }) => {
return (
<div>
<FieldArray name={"items"}>
{({ fields }) => (
<div>
{fields.map((name, index) => (
<div key={name}>
<Field
name={`${name}.title`}
validate={(value) => console.log(value)}
key={name}
>
{({ input }) => (
<input
value={input.value}
onChange={(event) => input.onChange(event.target.value)}
/>
)}
</Field>
</div>
))}
<button onClick={() => fields.push({ title: "111" })}>
add item
</button>
<button onClick={() => form.restart({ items: [] })}>reset</button>
</div>
)}
</FieldArray>
</div>
);
};
return (
<div className="App">
<Form
onSubmit={() => {}}
initialValues={values}
render={renderForm}
mutators={{ ...arrayMutators }}
/>
</div>
);
}
Thank you.
I have a dynamic array of form fields, whose values are fetched via REST API. On the page, there is also a dropdown, that, when changed, shows a different array of fields. I fetch all of these fields/values during the componentDidMount life cycle hook and filter the list to show the relevant data.
The Formik docs mention FieldArrays as a means to handle an array of fields. However, their example shows a static list of objects as its initialValues -- but I don't see how dynamically generated lists. In fact, since I'm fetching initialValues via AJAX, it's initially an empty array -- so nothing is rendered even after getting the data.
This is simplified version of my code:
const MyComponent = class extends Component {
componentDidMount() {
// data structure: [{Name: '', Id: '', Foo: '', Bar: ''}, ...]
axios
.get('/user')
.then((res) => {
this.setState({
userData: res.data
});
});
}
render() {
return (
<div>
<Formik
initialValues={{
users: this.state.userData
}}
render={({values}) => (
<Form>
<FieldArray
name="users"
render={arrayHelpers => (
<ul>
{
values.users.map((user, index) => {
return (
<li key={user.Id}>
<div>{user.Name}</div>
<Field name={`user[${index}].Foo`} type="text" defaultValue={user.Foo} />
<Field name={`user[${index}].Bar`} type="text" defaultValue={user.Bar} />
</li>);
}
}
</ul>
)}
/>
</Form>
)}
/>
</div>
);
}
}
You can do this via setting enableReinitialize true. According to doc it will do this:
Default is false. Control whether Formik should reset the form if initialValues changes (using deep equality).
I created complete codesanbox where your incoming data is async and when you push the data its also async. check this:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { Formik, Field, Form, ErrorMessage, FieldArray } from "formik";
const InviteFriends = () => {
const [initialValues, setInitialValues] = React.useState({
friends: []
});
useEffect(() => {
const initialValues = {
friends: [
{
name: "",
email: ""
}
]
};
const timer = setTimeout(() => {
setInitialValues(initialValues);
}, 1000);
return () => {
timer && clearTimeout(timer);
};
}, []);
return (
<div>
<h1>Invite friends</h1>
<Formik
initialValues={initialValues}
enableReinitialize={true}
onSubmit={async (values) => {
await new Promise((r) => setTimeout(r, 500));
alert(JSON.stringify(values, null, 2));
}}
>
{({ values }) => (
<Form>
<FieldArray name="friends">
{({ insert, remove, push }) => (
<div>
{console.log("Values", values, initialValues)}
{values.friends.length > 0 &&
values.friends.map((friend, index) => (
<div className="row" key={index}>
<div className="col">
<label htmlFor={`friends.${index}.name`}>Name</label>
<Field
name={`friends.${index}.name`}
placeholder="Jane Doe"
type="text"
/>
<ErrorMessage
name={`friends.${index}.name`}
component="div"
className="field-error"
/>
</div>
<div className="col">
<label htmlFor={`friends.${index}.email`}>
Email
</label>
<Field
name={`friends.${index}.email`}
placeholder="jane#acme.com"
type="email"
/>
<ErrorMessage
name={`friends.${index}.name`}
component="div"
className="field-error"
/>
</div>
<div className="col">
<button
type="button"
className="secondary"
onClick={() => remove(index)}
>
X
</button>
</div>
</div>
))}
<button
type="button"
className="secondary"
onClick={async () => {
await new Promise((r) =>
setTimeout(() => {
push({ name: "", email: "" });
r();
}, 500)
);
}}
>
Add Friend
</button>
</div>
)}
</FieldArray>
<button type="submit">Invite</button>
</Form>
)}
</Formik>
</div>
);
};
ReactDOM.render(<InviteFriends />, document.getElementById("root"));
Here is the demo: https://codesandbox.io/s/react-formik-async-l2cc5?file=/index.js
How do i pass a validation value from child component to parents component?
i tried to use props but it didn't work . i tried to pass the 'isValidValue' status
Child Component :
function MilikSendiri({isValidValue}) {
const { register, handleSubmit } = useForm()
function sweetAlertclick(){
Swal.fire({
icon: 'success',
title: 'Data anda sudah tersimpan ',
})
}
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
// validateOnMount
>
{
formik => {
const isValidValue = formik.isValid? ("Data Completed") : ("DData incomplete");
return(
<div>
<div>
Status : {isValidValue}
<label htmlFor="luasTanah"> Luas Tanah </label>
<Field className="formBiodata"
type="text" id="outlined-basic"
placeholder="luasTanah"
fullWidth
id="luasTanah"
name="luasTanah"
margin="normal" variant="outlined"
/>
<ErrorMessage name='luasTanah' component={TextError}/>
</div>
<div>
<label htmlFor="BiayaPBB"> Biaya PBB </label>
<Field className="formBiodata"
type="text" id="outlined-basic"
placeholder="BiayaPBB"
fullWidth
id="BiayaPBB"
name="BiayaPBB"
margin="normal" variant="outlined"
/>
<ErrorMessage name='BiayaPBB' component={TextError}/>
</div>
<Button onClick={sweetAlertclick} type ="submit"
variant="contained" startIcon={<SaveIcon />} color="primary" style={{
marginLeft: '25rem', marginTop: '20px', width: '20rem', height: 45,
fontSize: 22, backgroundColor: '#22689F'}}
disabled={!formik.isDirty && !formik.isValid} >Simpan
</div>
)
}
}
</Formik>
)
}
Parent Component :
function UKTRumah ({isValidValue}) {
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
// validateOnMount
>
{
formik => {
console.log('Formik props', formik)
return(
<div className ="IsiBiodata">
<Accordion square expanded={expanded === 'panel1'} onChange=.
{handleChange('panel1')} style={{marginLeft: '15rem', marginRight:
'15rem', marginTop: '3rem'}}>
<AccordionSummary aria-controls="panel1d-content" id="panel1d-
header">
<PersonIcon/>
<Typography> Data Rumah</Typography>
<Typography}> { isValidValue }
</Typography>
</AccordionSummary>
<AccordionDetails>
<div className ="IsiBiodata">
<Form>
</div>
</Form>
</div>
</AccordionDetails>
</Accordion>
</div>
)}}
</Formik>
)}
Thank you
Your example code seems to be lacking some key lines to answer the question specifically.
However, generally if it is data that Parent should be aware of, but that the child will make use of, it should be a value of state in the parent, then handed to the child as props. Here's a very small example using functional components:
const Child = ({ formik, setIsValid, isValid }) => {
useEffect(() => {
setIsValid(formik.isValid)
}, [formik.isValid]);
return <input />;
}
const Parent = () => {
const [isValid, setIsValid] = useState(true);
return <Child isValid={isValid} setIsValid={setIsValid} />
}
You can hold the value on your parent and pass a function to change it to your child. I can't really show you that with the code you posted, but I can show an example of what I mean. The parent has a state with an update function setIsValid and passes that to the child. The child can call setIsValid and that will update the isValid value on the parent.
parent
function Parent() {
const [isValid, setIsValid] = useState(false);
return <div>
<Child setIsValid={setIsValid} />
IsValid {isValid}
</div>
}
child
function Child({ setIsValid }) {
return <button onClick={() => setIsValid(true)}>Set Valid</button>
}
I'm using Formik to create a generic contact form in react. I am getting data from my api and attempting to call Formik's setStatus to generate a message to show that the form has been submitted successfully.
For whatever reason the Status state never gets updated to reflect what I put in setStatus.
Here's my code:
import { Formik, Form, useField } from "formik";
import * as Yup from "yup";
import axios from "axios";
import Button from "components/shared/button";
import "styles/contact/form.scss";
const handleSubmit = (values, actions) => {
axios.post("http://localhost:5000/sendemail/", values).then(res => {
actions.setSubmitting(false);
actions.setStatus = {
message: res.data.message
};
setTimeout(() => {
actions.resetForm();
}, 3000);
});
};
const FormField = ({ label, tagName, ...props }) => {
const [field, meta] = useField(props);
const errorClass = meta.touched && meta.error ? "error" : "";
const TagName = tagName;
return (
<>
<label htmlFor={props.id || props.name}>
{label}
{meta.touched && meta.error ? (
<span className="error">{meta.error}</span>
) : null}
</label>
<TagName
className={`form-control ${errorClass}`}
{...field}
{...props}
/>
</>
);
};
const ContactForm = () => (
<Formik
initialValues={{ name: "", email: "", msg: "" }}
validationSchema={Yup.object({
name: Yup.string().required("Required"),
email: Yup.string()
.email("Invalid email address")
.required("Required"),
msg: Yup.string().required("Required")
})}
onSubmit={handleSubmit}>
{({ isSubmitting, status }) => (
<Form className="contact-form">
<div className="row form-group">
<div className="col">
<FormField
label="Name"
name="name"
type="text"
tagName="input"
/>
</div>
<div className="col">
<FormField
label="Email"
name="email"
type="text"
tagName="input"
/>
</div>
</div>
<div className="form-group">
<FormField label="Message" name="msg" tagName="textarea" />
</div>
{status && status.message && (
<div className="message">{status.message}</div>
)}
<Button
id="formSubmit"
text="Send Message"
type="submit"
isSubmitting={isSubmitting}
/>
</Form>
)}
</Formik>
);
export default ContactForm;
Just before my submit button, it should show the <div class="message">Success message</div> after submitting the form. When I try to debug the value of Status is always "undefined".
Any one have a clue what I'm doing wrong here?
The reason it wasn't working is because I tried to set the value of setStatus equal to an object. What I should have done was used it as a method and pass the object as a parameter.
Like so:
actions.setStatus({message: res.data.message});
I feel silly for missing this simple mistake for so long.