Skip to content

Instantly share code, notes, and snippets.

@Zamoud
Forked from rockchalkwushock/BookingForm.js
Created December 21, 2018 09:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Zamoud/12467abca28bb16dd34cac29eca8c5c8 to your computer and use it in GitHub Desktop.
Save Zamoud/12467abca28bb16dd34cac29eca8c5c8 to your computer and use it in GitHub Desktop.
Using redux-form with next.js
import axios from 'axios'
// Redux Constants
export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS '
export const FETCH_DATA_ERROR = 'FETCH_DATA_ERROR'
// Redux Success Action
const fetchData = data => ({
type: FETCH_DATA_SUCCESS,
payload: data // Returns { message: 'Your email was sent!' }
})
// Redux Error Action
const fetchError = data => ({
type: FETCH_DATA_ERROR,
payload: data // Returns { error: 'There was a problem with your request, please try back later.' }
})
// FIXME: I don't want this exposed
// 1) Need to figure out how to properly get it injected at runtime.
// 2) How to get CircleCi to see it as well.
const url =
process.env.NODE_ENV === 'production'
? 'https://productionURL.now.sh'
: 'http://localhost:4000'
// Function for querying the api.
const mailClient = async input => {
try {
// POST to my backend.
const { data } = await axios.post(url, input)
return data
} catch (e) {
throw e
}
}
// Redux Async-Action-Creator
export const getBookingInfo = values => async dispatch => {
// Await the backend Promise of success or error.
const data = await mailClient(values)
// If an error let redux handle the proper UI message to be rendered.
if (data.error) return dispatch(fetchError(data))
// Dispatch to the redux store.
return dispatch(fetchData(data))
}
import React, { Component } from 'react'
import { Field, reduxForm } from 'redux-form'
import PropType from 'prop-types'
import { messages } from '../../lib' // ignore pertains to `react-intl`
import validate from './validate'
import Input from './Input'
import TextArea from './TextArea'
class BookingForm extends Component {
static propTypes = {
intl: PropType.object.isRequired
}
state = {
error: false,
isFetched: false
}
submitForm = async values => {
// Receive `getBookingInfo` from `<FormContainer />`
// Receive `reset` from reduxForm HOC.
const { getBookingInfo, reset } = this.props
return getBookingInfo(values).then(({ payload }) => {
if (payload.message) {
// Set state for successful server response.
this.setState(state => ({
...state,
isFetched: true
}))
} else if (payload.error) {
// Set state for failed server response.
this.setState(state => ({
...state,
error: true,
isFetched: true
}))
}
// Clear the form after submit.
reset()
})
}
// Render the appropriate response to the user on server response.
renderResponse = () => {
const { error, isFetched } = this.state
if (isFetched && error) {
return <strong>{this.props.intl.formatMessage(messages.errorRes)}</strong>
} else if (!error && isFetched) {
return (
<strong>{this.props.intl.formatMessage(messages.successRes)}</strong>
)
}
}
render() {
// Receive `handleSubmit` & `valid` from reduxForm HOC
// `intl` is from `react-intl` pay no attention.
const { handleSubmit, intl, valid } = this.props
// Pass `handleSubmit()` our declared function for handling the form submit event.
return (
<form onSubmit={handleSubmit(this.submitForm)}>
<div>
<Field
component={Input}
label={intl.formatMessage(messages.firstName)}
name="firstName"
type="text"
/>
</div>
<div>
<Field
component={Input}
label={intl.formatMessage(messages.lastName)}
name="lastName"
type="text"
/>
</div>
<div>
<Field
component={Input}
label={intl.formatMessage(messages.email)}
name="email"
type="email"
/>
</div>
<div>
<Field
component={Input}
label={intl.formatMessage(messages.phone)}
name="phone"
type="tel"
/>
</div>
<div>
<div>
<Field
component={TextArea}
label={intl.formatMessage(messages.message)}
name="message"
/>
</div>
</div>
<div>
{this.renderResponse()}
<button disabled={!valid} type="submit">
{intl.formatMessage(messages.book)}
</button>
</div>
</form>
)
}
}
// Wrap your form in the reduxForm HOC and pass options object as argument.
export default reduxForm({
form: 'booking',
validate // form level validation
})(BookingForm)
import { connect } from 'react-redux'
import { BookingForm } from '../components'
import { getBookingInfo } from '../lib'
// Just like a normal configuration of `react-redux`
// No need to tell the child component any state so we pass `null`
// as the first argument and then our actionCreator.
export default connect(null, {
getBookingInfo
})(BookingForm)
// pages/index.js
import { WithData } from '../lib'
import { FormContainer } from './containers'
// `next` will render this as localhost:XXXX/ and you will see your form
// again ignore `intl`
const Index = ({ intl }) => (
<FormContainer intl={intl} />
)
export default WithData(Index)
import React from 'react'
// Custom component to pass to <Field />
const Input = ({ label, input, type, meta: { error, touched } }) => (
<div>
<label htmlFor={label} />
<div>
<input {...input} placeholder={label} type={type} />
{touched && (error && <span>{error}</span>)}
</div>
</div>
)
export default Input
import { combineReducers, compose, createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { reducer as formReducer } from 'redux-form'
let reduxStore = null
// Create Root Reducer.
const rootReducer = combineReducers({
form: formReducer
})
const isProd = process.env.NODE_ENV === 'production'
const createMiddleware = () => {
const universalMiddleware = applyMiddleware(thunk)
// Don't use devTools in 'production', also don't exectue until
// `next` is running client side code! No access to `window`!
if (process.browser && window.devToolsExtension && !isProd) {
return compose(universalMiddleware, window.devToolsExtension())
}
return universalMiddleware
}
const getStore = initialState => {
let store
// I not client side or no reduxStore yet
if (!process.browser || !reduxStore) {
const middleware = createMiddleware()
store = createStore(rootReducer, initialState, middleware)
// If still not client side
if (!process.browser) {
return store
}
reduxStore = store
}
return reduxStore
}
export default getStore
import { Component } from 'react'
import { Provider } from 'react-redux'
import PropTypes from 'prop-types'
import { loadGetInitialProps } from 'next/dist/lib/utils'
import 'isomorphic-fetch'
import getStore from '../redux'
const getInitialState = reduxStore => ({
...reduxStore.getState()
})
// WithData(App)
export default ComposedComponent =>
class WithData extends Component {
static propTypes = {
initialState: PropTypes.object.isRequired
}
// All of this is executed server-side just like React's componentWillUnmount, hence you don't
// have access to things like `window` or `document` inside this function.
static async getInitialProps(ctx) {
const subProps = await loadGetInitialProps(ComposedComponent, ctx)
// Populate the reduxStore server-side.
const reduxStore = getStore({})
const props = { ...subProps }
// Return the initialState of the application and other props.
return {
initialState: getInitialState(reduxStore),
...props
}
}
constructor(props) {
super(props)
// Use the initialState created server-side to populate
// the reduxStore in the browser.
const reduxStore = getStore(this.props.initialState)
this.reduxStore = reduxStore
}
render() {
// Use <Provider /> just like you normally would with `redux`.
return (
<Provider store={this.reduxStore}>
<ComposedComponent {...this.props} />
</Provider>
)
}
}
@Zamoud
Copy link
Author

Zamoud commented Dec 21, 2018

hi
can you give me the path of each file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment