0

I've created a container called siteEdit.js. It handles creating & editing "sites".

I've setup actionCreators that handles taking the form data and submitting it to the API. This works perfectly.

But when you visit the container using a route that contains an ID it will run an actionCreator that will fetch for the "Site" data based on the id param.

This all works as expected, but since I'm using redux, I'm setting the Input value with the props. for example, this.props.title

I'm trying to stay away from using the redux-form package for now.

Container:

import React, {Component} from 'react';
import { connect } from 'react-redux';

import {createSite, getSite} from '../../actions/siteActions';

class SiteEdit extends Component {
  constructor(props) {
    super(props)
    this.state = {
      title: '',
      url: '',
      description: '',
      approvedUsers: []
    }
    this.handleSubmit = this.handleSubmit.bind(this)
    this.handleInputChange = this.handleInputChange.bind(this)
  }

  componentWillMount() {
    if(this.props.params.id) {
      this.props.dispatch(getSite(this.props.params.id))
    }
  }



  handleInputChange(e) {
    const target = e.target
    const value = target.type === 'checkbox' ? target.checked : target.value
    const name = target.name

    this.setState({
      [name]: value
    })
  }

  handleSubmit(e) {
    e.preventDefault()
    this.props.dispatch(createSite(this.state))
  }

  render() {

    const {title, url, description, approvedUsers} = this.props

    return (
      <div className="SiteEdit">
        <h1>NEW SITE</h1>
        <form onSubmit={this.handleSubmit}>

          <div className="block">
            <label>Site Name</label>
            <input 
              className="input" 
              type="text"
              value={title ? title : this.state.title}
              onChange={this.handleInputChange} 
              name="title" />
          </div>
          <div className="block">
            <label>Site URL</label>
            <input 
              className="input" 
              type="text"
              value={this.state.url} 
              onChange={this.handleInputChange}
              name="url" />
          </div>
          <div className="block">
            <label>Description</label>
            <input
              className="textarea"
              type="textarea"
              value={this.state.description}
              onChange={this.handleInputChange}
              name="description" />
          </div>
          <div className="block">
            <label>Approved Users</label>
            <input
              className="input"
              type="text"
              value={this.state.approvedUsers}
              onChange={this.handleInputChange}
              name="approvedUsers" />
          </div>

          <button className="button--action">Create</button>
        </form>
      </div>
    )
  }
}

const mapStateToProps = (state) => ({
  title: state.sites.showSite.title,
  url: state.sites.showSite.url,
  description: state.sites.showSite.description,
  approvedUsers: state.sites.showSite.approvedUsers
})

SiteEdit = connect(mapStateToProps)(SiteEdit)

export default SiteEdit

ActionCreators:

import config from '../config'
import { push } from 'react-router-redux'


const apiUrl = config.api.url

// List all sites
export const LIST_SITES_START = 'LIST_SITES_START'
export const LIST_SITES_SUCCESS = 'LIST_SITES_SUCCES'
export const LIST_SITES_ERROR = 'LIST_SITES_ERROR'

export function sitesListStart(data) {
  return { type: LIST_SITES_START, data }
}

export function sitesListSuccess(data) {
  return { type: LIST_SITES_SUCCESS, data }
}

export function sitesListError(data) {
  return { type: LIST_SITES_ERROR, data }
}

export function listSites() {
  return (dispatch) => {
    dispatch(sitesListStart())
    fetch(`${apiUrl}/listSites`)
    .then(res => res.json())
    .then(json => {
      dispatch(sitesListSuccess(json))
    })
    .catch(error => {
      dispatch(sitesListError)
    })
  }
}

// Create & Edit Sites
export const CREATE_SITE_START = 'CREATE_SITE_START'
export const CREATE_SITE_SUCESS = 'CREATE_SITE_SUCCESS'
export const CREATE_SITE_ERROR = 'CREATE_SITE_ERROR'

export function siteCreateStart(data) {
  return { type: CREATE_SITE_START, data}
}

export function siteCreateSuccess(data) {
  return { type: CREATE_SITE_SUCCESS, data}
}

export function siteCreateError(error) {
  return { type: CREATE_SITE_ERROR, error}
}

export function createSite(data) {
  return (dispatch) => {
    dispatch(siteCreateStart())
    fetch(`${apiUrl}/createSite`, {
      method: 'post',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    })
    .then(res => res.json())
    .then(json => {
      dispatch(push('/'))
      dispatch(siteCreateSuccess())
    })
    .catch(error => {
      dispatch(siteCreateError())
    })

  }
}

// Get Single Site
export const GET_SITE_START = 'GET_SITE_START'
export const GET_SITE_SUCCESS = 'GET_SITE_SUCCESS'
export const GET_SITE_ERROR = 'GET_SITE_ERROR'

export function getSiteStart(data) {
  return { type: GET_SITE_START, data}
}

export function getSiteSuccess(data) {
  return { type: GET_SITE_SUCCESS, data}
}

export function getSiteError(error) {
  return { type: GET_SITE_ERROR, error}
}

export function getSite(id) {
  return (dispatch) => {
    dispatch(getSiteStart())
    fetch(`${apiUrl}/getSite/${id}`)
    .then(res => res.json())
    .then(json => {
      dispatch(getSiteSuccess(json))
    })
    .catch(error => {
      dispatch(getSiteError())
    })
  }
}

Reducers:

import {push} from 'react-router-redux'
import {
  LIST_SITES_START,
  LIST_SITES_SUCCESS,
  LIST_SITES_ERROR,
  GET_SITE_START,
  GET_SITE_SUCCESS,
  GET_SITE_ERROR
} from '../actions/siteActions'

const initialState = {
  sitesList: {
    sites: [],
    error: null,
    loading: true
  },
  showSite: {
    title: '',
    url: '',
    description: '',
    approvedUsers: [],
    loading: true
  }
}

export default function (state = initialState, action) {
  switch (action.type) {
    // List Sites
    case LIST_SITES_START:
      return Object.assign({}, state, {
        sitesList: Object.assign({}, state.sitesList, {
          loading: true
        })
      })
    case LIST_SITES_SUCCESS:
      return Object.assign({}, state, {
        sitesList: Object.assign({}, state.sitesList, {
          sites: action.data,
          loading: false
        })
      })
    case LIST_SITES_ERROR:
      return Object.assign({}, state, {
        error: action.error,
        loading: false
      })

    case GET_SITE_START:
      return Object.assign({}, state, {
        showSite: Object.assign({}, state.showSite, {
          loading: true
        })
      })
    case GET_SITE_SUCCESS:
      return Object.assign({}, state, {
        showSite: Object.assign({}, state.showSite, {
          ...action.data,
          loading: false
        })
      })
    case GET_SITE_ERROR:
      return Object.assign({}, state, {
        showSite: Object.assign({}, state.showSite, {
          error: action.error,
          loading: false
        })
      })

    default:
      return state
  }
}
3
  • Can you clarify what the problem is you are having, I can't really tell what you are asking here? Maybe if you remove some of the code that is not relevant to the problem it would help too. Commented Dec 4, 2017 at 14:44
  • @ajmajmajma The issue is with this line of code here. <input className="input" type="text" value={title ? title : this.state.title} onChange={this.handleInputChange} name="title" /> the value being this.props.title and not letting me update the state. I think I need to set the state from the props? Commented Dec 4, 2017 at 15:08
  • I added an answer below. For future - you do not need to add so much code when your issue is just with a few line. Good luck ~ Commented Dec 4, 2017 at 15:40

1 Answer 1

1

You are setting the ternary for the value with the props.title taking precedent, like so just to re-iterate -

 const { title } = this.props;
 ...
 value={title ? title : this.state.title}

Your onChange logic is correct and is probably updating your components local state correctly, however you still have this.props.title, so it will take precedent in that ternary.

There are a bunch of ways you could handle this, it will depend on order of operations really (that is when props.title will be truthy or not). Assuming you have the title when the component mounts you can do something in the constructor like:

 constructor(props) {
    super(props)
    this.state = {
      title: props.title,   << set default here
      url: '',
      description: '',
      approvedUsers: []
    }

then in the input you only need to set the value to the state title

value={this.state.title}

This will depend on when the props.title value comes into your component of course, if it is not there for mount, this will not work as intended.

You could also pass a function to evaluate all of this for the value of the input as well - inside of which you would more verbosely check the props.title vs the state.title and decide which one to return as your value.

 <input value={this.returnTitleValue} ..  << something like so

Hope this helps!

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the detailed answer. This helped a lot. The issue now, I have to goto the route twice for it to fill the state. Does that make sense, have you experienced that before?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.