I am trying to create a Formik form in a Gatsby site using the withFormik higher order component (rather than use a render prop).
Here is a simplified version of my code:
import React from 'react'
import { withFormik } from 'formik'
const TestPage = ({ handleChange, values }) => (
<div>
<input
type="email"
name="email"
placeholder="Email"
onChange={handleChange}
value={values.email}
/>
</div>
)
const FormikTest = withFormik({
mapPropsToValues() {
return {
email: 'test#test.com',
}
},
})
export default FormikTest(TestPage)
So far, everything works just as a I want. However, I am hitting a problem when it comes to setting up a conditional argument for the email field in the mapPropsToValues object. You can see what I am trying to do by watching about 1 minute of this tutorial (it's set to the right starting time): https://www.youtube.com/watch?v=yNiJkjEwmpw&feature=youtu.be&t=717
The problem is that I can't figure out how I would send props to the mapPropsToValues using Gatsby. I don't have access to render like in that tutorial.
In other words, in Create React App, you can do something like the following:
const FormikTest = withFormik({
mapPropsToValues({ email }) {
return {
email: email || '',
}
},
})(TestPage)
render(<FormikTest email="test#test.com />, document.getElementById('#root'))
But I don't have access to render in Gatsby or a <FormikTest /> component.
Any idea, therefore, how I could pass in props to mapPropsToValues so that I could conditionally set initial values for the email form using Gatsby?
Thanks.
UPDATE
I have created a simple Codesandbox version which has just one page using Formik. Here is the link: https://codesandbox.io/s/gatsby-starter-default-270gs?fontsize=14
And here is the code for that page:
import React from "react"
import { withFormik } from "formik"
const IndexPage = ({ handleChange, values }) => (
<div>
<input
type="email"
name="email"
placeholder="Email"
onChange={handleChange}
value={values.email}
/>
</div>
)
const FormikTest = withFormik({
mapPropsToValues() {
return {
email: "",
}
},
})
export default FormikTest(IndexPage)
In that tutorial, he's defining everything in one file hence the reason he's using render (it's what react does under the hood when you import components). In your case, the TestPage component would be used as a child in another component like:
render(){
return (<TestPage email = "test#example.com"/>);
}
mapPropsToValues would get the props passed in as a parameter and "email" would be a member of those props
You have access to any props passed down to a component in mapPropsToValues. Try passing your base component through withFormik straight away while returning a function as an export - that will allow you to set any desired props.
const FormikTest = withFormik({
mapPropsToValues(props) {
console.log(props) // { rad: "yas" }
return {
email: "",
}
},
})(IndexPage)
export default () => <FormikTest rad="yas" />
Related
I am using react-select
I have a form that creates a new contact. In my redux store I have groups that are already created. When I click the Select to show the options I would like to load the options from my redux store.
The "groups" from redux store has only one value, that is title: String in the GroupModel in the backend.
I understand that react-select needs to have a label: '', value: ''
If I create an array myself and pass the values in it works fine. But with redux nothing is working. I cant find any answers anywhere online which seems trivial to me....
Here is my component below
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import Select from 'react-select';
import { createContact } from '../../actions/index';
import { Button, Form, FormGroup, Input, Label } from 'reactstrap';
import Divider from '../common/Divider';
const ContactForm = ({ hasLabel }) => {
const dispatch = useDispatch()
// State
const [contact, setContact] = useState({
group: '',
})
// Submit Handler
const handleSubmit = e => {
e.preventDefault()
dispatch(createContact(contact))
};
// Change Handler
const handleChange = e => {
setContact({...contact, [e.target.name]: e.target.value})
};
// bringing in our state from redux
const groups = useSelector((state) => state.groups)
return (
<>
<Form onSubmit={handleSubmit}>
<div>
<FormGroup>
<Label>
Choose Group/List</Label>
<Select
name="group"
options={groups}
getOptionlabel={({title}) => title}
getOptionValue={({_id}) => _id }
onChange={() => {}}
isMulti
/>
</FormGroup>
</div>
</Form>
</>
);
};
ContactForm.propTypes = {
hasLabel: PropTypes.bool
};
ContactForm.defaultProps = {
layout: 'basic',
hasLabel: false
};
export default ContactForm;
Ok.... So my solution above was pretty much correct. The issue I had was the getOptionlabel needed to be getOptionLabel <--- notice I forgot to capitalize the L in label....
I hope someone who needs to use react-select with redux finds this post and it helps.
So basically just bring in your redux state with useSelector or connect,
then make sure to use the props below in your Select component
getOptionLabel={({title}) => title}
getOptionValue={({_id}) => _id}
I created a custom input called <FormInput> and applied useField() and useFormikContext() to it:
const [field, meta] = useField(props);
const { setFieldValue } = useFormikContext();
<FormInput> is part of a library called UI. I'm importing the library and trying to create a very simple form to test, a single field, the only validation being that it's required:
import React, { useContext } from "react";
import { DataContext } from "../../context/DataContext";
import * as UI from "#tui/uilibrary";
import { composeThemeFromProps } from "#css-modules-theme/react";
import styles from "./EnrollStep5.module.scss";
import { Formik, Form } from "formik";
import * as Yup from "yup";
const EnrollStep5 = (props) => {
const context = useContext(DataContext);
const theme = composeThemeFromProps(styles, [context, props]);
return (
<Formik
initialValues={{
name: "",
}}
validationSchema={Yup.object().shape({
name: Yup.string().required("Required"),
})}
>
{(props) => {
<Form className={theme.EnrollStep2}>
<UI.FormInput type={"text"} name={"name"} label={"Name"} />
</Form>;
}}
</Formik>
);
};
export default EnrollStep5;
This comes up blank. The Formik object appears in the Component browser, but shows as if it has no children. I have the feeling this is just due to inexperience and that I'm close. What am I doing wrong?
In short:
Wrap the <Form>...</Form> in (parantheses) instead of {curly braces}.
Little more detailed:
So basically what is happening in your code with the curly braces is you are entering the callback function. So the <Form>...</Form> is just floating around somewhere in the function body. What you intend to do is to return it.
So you can either add a return statement (return <Form>...</Form>;) inside the curly braces or directly return the value without entering the function body. E.g. like (props) => <Form>...</Form> or (props) => (<Form>...</Form>), whichever you prefer.
Objective
Setup a dynamic form controlled by the user using react-redux and revalidate to run validation checks on my form.
Problem:
Since my form is dynamic I need to make dynamic validations. In order to do this my form data needs to be passed in as props to my component, which can be used as a second argument in the validate function from revalidate
My approach
To do this I am waiting until the component is mounted, building the form, passing it to redux, and then will map state to props. As the user adds more rows I will update state and then the component will render. I will use shouldComponentUpdate() to avoid any render loops.
The Error
My error is regarding the dispatch. When I try to run the dispatch (which will pass the form into redux) I get Dispatch is not a function error.
I am not supper comfortable with the connect() as I have to wrap redux with it as well as firebase. This syntax really confuses me.
Question
I believe the issue with with how I am exporting the component where I am using HOC like withFirebase, Redux, and Connect. Somewhere along the way I am losing scope to the connect. Can someone shed light into what it is I am doing wrong?
Component
import React, { Component } from "react";
import { reduxForm, Field } from "redux-form";
import { Container, Form, Col, Button } from "react-bootstrap";
import MaterialIcon from '../../material-icon/materialIcon';
import { withFirestore } from "react-redux-firebase";
import { connect } from "react-redux";
import TextInput from "../../forms/textInput";
import { combineValidators, isRequired } from "revalidate";
import { setupStudentForm } from '../../../store/actions/students';
const validate = (values, ownprops) => {
// Will be passing in validation rules, once form is apssed in with props via mapStateToProps.
}
export class CreateStudentsForm extends Component {
// Using constrcutor so componentDidMount() can render() cann access variables
constructor(props) {
super(props);
this.state = {
rows: 2,
}
this.formArray = [];
this.form = null;
}
componentDidMount() {
// Once component is rendered, setup form and send to redux
for (let i = 1; i !== this.state.rows + 1; i++) {
let firstNameField = {
fieldName: `firstName${i}`,
label: 'First Name',
required: true,
type: "text",
}
let lastNameField = {
fieldName: `lastName${i}`,
label: 'Last Name',
required: true,
type: "text",
}
this.formArray.push([firstNameField, lastNameField]);
}
this.props.setupStudentFormHandler(this.formArray);
}
// Ensure we do not get stuck in render loop
shouldComponentUpdate(nextProps, nextState){
if(nextProps !== this.props){
return true
} else {
return false
}
}
render() {
// Allows user to add another row
const addRow = () => {
this.setState({
rows: this.state.rows + 1
})
}
// Map through form array and create template
if (this.formArray) {
let form = this.formArray.map((field, index) => {
return (
<Form.Row key={index} className="animated fadeIn">
<Col xs={5}>
<Form.Group className="mb-0 noValidate">
<Field
label={field[0].label}
attempt={this.props.attempt}
name={field[0].fieldName}
type={field[0].type}
component={TextInput}
/>
</Form.Group>
</Col>
<Col xs={5}>
<Form.Group className="mb-0 noValidate">
<Field
label={field[1].label}
attempt={this.props.attempt}
name={field[1].fieldName}
type={field[1].type}
component={TextInput}
/>
</Form.Group>
</Col>
<Col xs={2}>
<MaterialIcon icon="delete" className="mt-4" />
</Col>
</Form.Row>
)
})
}
return (
<Container>
{this.form}
<Button variant="outline-success" onClick={addRow}>Add Another Student</Button>
</Container>
)
}
}
const mapStateToProps = state => {
return {
// Get access to student state which will have form
studentForm: state.students
};
};
const mapDispatchToProps = dispatch => {
return {
//Send formArray to redux
setupStudentFormHandler: (form) => dispatch(setupStudentForm(form))
};
};
export default withFirestore(
reduxForm({
form: "createStudents", validate
})(connect(
mapDispatchToProps,
mapStateToProps
)(CreateStudentsForm)
)
);
mapStateToProps is the first argument of connect, mapDispatchToProps is the second. Try swapping the order:
connect(
mapStateToProps,
mapDispatchToProps
)(CreateStudentsForm)
So I'm trying to load a Redux Form pre populated with values from my store. However I'm unable to get anything back other than null. Been at this for several hours now and been reading over several SO examples trying different things and think I'm just at a wall on this.
Following this Redux Form Initializing from State example
Redux: 3.6.0
React-Redux: 5.0.3
Redux-Form: 6.6.3
React: 15.4.2
There is a parent component that is rendering a child component which is the form. For sake of brevity going to put in the bare minimum of code and make names as generic as possible. Everything loads fine but I think the issue relies in not properly connecting to my store. Or rather I should just be loading the data on a componentWillMount?
Parent Component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions/usersActions';
import ChildForm from './ChildForm.jsx'
#connect((store) => {
return{
user: store.users.user
}
})
export default class Parent extends Component{
componentWillMount(){
this.props.dispatch(fetchUser(this.props.match.params.id))
}
submit = (values) => {//do some stuff}
render(){
return(
<ChildForm onSubmit={this.submit} />
);
}
}
ChildForm:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { user } from './reducers/usersReducer.js';
class ChildForm extends Component{
render(){
console.log('FORM STATE >>>>>>>>>>', this.state); //Returns NULL
const { handleSubmit } = this.props;
return(
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="firstName">First Name</label>
<Field name="first_name" component="input" type="text"/>
</div>
<button type="submit">Submit</button>
</form>
);
}
}
ChildForm = reduxForm({
form: 'childForm',
enableReinitialize : true // I found this in another SO Example
})(ChildForm);
ChildForm = connect(
state => ({
user: state.user,
initialValues: state.user
}),
{ fetchUser }
)(ChildForm)
export default ChildForm;
enableReinitialize SO
usersReducer.js
export default function reducer(state={
user: {},
}, action){
switch (action.type){
case "FETCH_USER_FULFILLED":{
return{
...state,
user: action.payload
}
}
}
return state;
}
So this is where I'm at currently. Can get the page, form, and submit all work. However I can't seem to figure out how to get my Store values out and into the form fields. Any help would be greatly appreciated.
Looks like everything is wired up correctly but I wasn't pulling in the correct object in the store.
ChildForm = connect(
state => ({
initialValues: state.users.user
}),
{ fetchUser }
)(ChildForm)
...Always something little
I am confused with props or state to use here. If I use state in #connect I get error and does not work. when I use props it does not work with onchange handler to set new props. Please help how should I make input working with state or props. I am retrieving initial data from api.
import React, {PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import { asyncConnect } from 'redux-async-connect';
import {load, isLoaded} from 'redux/modules/overview';
#asyncConnect([{
promise: ({ store: { dispatch, getState }, params: { id }, }) => {
const promises = [];
if (!isLoaded(getState())) {
promises.push(dispatch(load(id)));
}
return Promise.all(promises);
}
}])
#connect(
state => ({
overview: state.overview.data
}),
dispatch => bindActionCreators({load}, dispatch))
export default class Overview extends React.Component {
changeinput1(e) {
this.props.overview.title = e.target.value;
// changing value does not work here
}
constructor (props) {
super();
this.state = {
overview: null,
}
}
render() {
return (
<div>
<label>Input 1</label>
<input type="text" className="form-control" id="title" name="title" maxlength="35" value={this.props.overview.title} onChange={this.changeinput1.bind(this)}/>
</div>
)
}
}
I also want to do validation and want to save input value on onBlur so I dont want to use form.
if you want change reducer's(here suppose to be 'overview') value, you should define an action then dispatch it, not change it directly, the state get from store is readonly in the component