BonifatiusK
BonifatiusK

Reputation: 2331

How to set TextField changes in to state using MaterialUI and Redux?

Using React / Redux and MaterialUI I set up a component which needs to check your location or get the lat/lng based upon the address you put in.

When clicking on a button I would like the address, typed in to a TextField to be checked on a webservice.

However I can not seem to get the value of the TextField to the webservice using the button.

So I tried to set the value immediately in the props (of the state). This works, but I do get an error:

Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa).

This code is like this:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Field, reduxForm } from 'redux-form'
import TextField from 'material-ui/TextField';
import IconButton from 'material-ui/IconButton';
import PlaceIcon from 'material-ui-icons/Place';
import SearchIcon from 'material-ui-icons/Search';

import { getLocationByAddress, getLocationByLatLng } from '../actions';


/**
 * Location Search, Lets the user search for his location and coordinates
 * @class LocationSearch
 * @extends React.Component
 */
class LocationSearch extends Component {

    /**
     * getLocationByLatLng: uses navigator.geolocation to get the current position of the user <br/>
     * then requests the information from a back-end service to retrieve full location data.
     */
    getLocationByLatLng () {

        let options = {
            enableHighAccuracy: true,
            timeout: 5000,
            maximumAge: 0
        };

        navigator.geolocation.getCurrentPosition(function (pos) {
            console.log(pos.coords);

        }, function(err){
            console.warn(`ERROR(${err.code}): ${err.message}`);
        }, options)
    }

    /**
     * getLocationByAddress: uses address to request full location data from back-end service
     */
    getLocationByAddress = (e) => {
        /*
        console.log(e);
        e.preventDefault();
         */

        this.props.getLocationByAddress(this.props.location.address);
    };

    keepVal = ({target}) => {

        this.props.location.address = target.value;

    };

    render () {

        const { location } = this.props;

        return (
            <div className={'locationSearch'}>

                <IconButton onClick={this.getLocationByLatLng} color="primary" className={'locationSearch_locationBtn'} aria-label="">
                    <PlaceIcon fontSize={35} />
                </IconButton>              
                  <TextField
                      value={location.address}
                      placeholder={'Voer uw locatie in'}
                      margin={'normal'}
                      className={'locationSearch_input'}
                      onChange={this.keepVal}
                  />
                  <IconButton onClick={this.getLocationByAddress} color="primary" className={'locationSearch_searchBtn'} aria-label="">
                      <SearchIcon fontSize={35} />
                  </IconButton>
            </div>
        );
    }

}

function mapStateToProps (state) {
    return {
        location: state.location
    }
}

export default connect(mapStateToProps, {getLocationByAddress, getLocationByLatLng})(LocationSearch)

So because I do not want any errors I also looked into changing the state just for the location address. So I created a new action setLocationAddress and that should go to the reducer and change the state just for the address, but thats silly to do on every change, as the value is already there... no need to change it.

Thus I used a form to do this:

getLocationByAddress = (e) => {
    e.preventDefault();
    const { target } = e;
    this.props.getLocationByAddress(target['locationInput'].value);
};

render () {

    const { location } = this.props;

    return (
        <div className={'locationSearch'}>

            <IconButton onClick={this.getLocationByLatLng} color="primary" className={'locationSearch_locationBtn'} aria-label="">
                <PlaceIcon fontSize={35} />
            </IconButton>
            <form onSubmit={this.getLocationByAddress}>
                <TextField
                    name={'locationInput'}
                    value={location.address}
                    placeholder={'Voer uw locatie in'}
                    margin={'normal'}
                    className={'locationSearch_input'}
                />
                <IconButton color="primary" className={'locationSearch_searchBtn'} aria-label="">
                    <SearchIcon fontSize={35} />
                </IconButton>
            </form>
        </div>
    );
}
But again this naggy message:

Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa)

So how do I do this? Any help would be appreciated.

Upvotes: 2

Views: 4839

Answers (1)

Ken Gregory
Ken Gregory

Reputation: 7390

See the code for the controlled component in the TextField demo. In the code, the value prop is pulled from state and onChange is set to a function that updates state.

   <div>
    <TextField id="standard-name" label="Name" value={name} onChange={handleChange} />
    <TextField id="standard-uncontrolled" label="Uncontrolled" defaultValue="foo" />
  </div>

You're actually pretty close. You've mapped state to your location prop and you've set value={location.address}, but onChange is assigned to this.keepVal which attempts to update props, which is no good.

Since you're using Redux, you need to dispatch an action in keepVal that will update state.location.address with event.target.value. This will ensure that the component value is populated from state and that changes from fired onChange events update the store.

Upvotes: 5

Related Questions