0

I have the following two components App and it's child Toolbar.

Toolbar contains a form and and input, and I would like to pass the value from the input up to the App component's state -- but only when the use has confirmed it (on submit or enter etc).

My current attempt is not working as I only get a proxy and an event object back (which does makes sense). How would I do this?

I am not using Redux or anything like it.

App.js

import React, {Component} from 'react'
import {render} from 'react-dom'
import {} from './styles/base.scss'

import ImageContainer from './components/Images'
import Toolbar from './components/Toolbar'

export default class App extends Component {
    constructor(props) {
        super(props);
        // http://www.cloudypoint.com/Tutorials/discussion/javascript-how-to-update-parents-state-in-react/
        this.setFolderPathHandler = this.setFolderPathHandler.bind(this);
    }

    setFolderPathHandler(e) {
        e.preventDefault();
        console.log(arguments);
        // this.setState({
        //     xyz: input
        // });
    }

    render() {
        return (
            <div>
                <Toolbar setFolderPathHandler={this.setFolderPathHandler} />
                <ImageContainer />
            </div>
        )
    }
}

Toolbar/index.js

import React, { Component } from 'react';
import path from 'path';

class Toolbar extends Component {
    render() {
        return (
            <nav className="toolbar">
                <div className="column">
                    {/* START used to be it's own component */}
                    <form onSubmit={this.props.setFolderPathHandler}>
                        <div className="form-field">
                            <input type="text" className="folder-path" ref={(input) => this.input = input} />
                            <button><i className="fa fa-fw fa-2x fa-folder-open" aria-hidden="true"></i></button>
                        </div>
                    </form>
                    {/* END used to be it's own component */}
                </div>
                <div className="column">
                    <button><i className="fa fa-fw fa-2x fa-chevron-left" aria-hidden="true"></i></button>
                    <button><i className="fa fa-fw fa-2x fa-chevron-right" aria-hidden="true"></i></button>
                </div>
            </nav>
        );
    }
}

export default Toolbar;

I did have the input as a separate component but it was too confusing to me (I am new to React).

2
  • Is .folder-path the input you're concerned with? If we reduced the entire component to that one element (and maybe a wrapper) would it be the same question? It makes it a little easier to approach (and reproduce) Commented Apr 19, 2017 at 19:56
  • I think you should change the onSummit method like this: onSubmit={() => this.props.setFolderPathHandler()}. The idea would be to execute parent's method Commented Apr 19, 2017 at 19:57

2 Answers 2

2

App.js

import React, {Component} from 'react'
import {render} from 'react-dom'
import {} from './styles/base.scss'

import ImageContainer from './components/Images'
import Toolbar from './components/Toolbar'

export default class App extends Component {
    constructor(props) {
        super(props);
        // http://www.cloudypoint.com/Tutorials/discussion/javascript-how-to-update-parents-state-in-react/
        this.setFolderPathHandler = this.setFolderPathHandler.bind(this);
    }

    setFolderPathHandler(inputValue) {
        // this.setState({
        //     xyz: inputValue
        // });
    }

    render() {
        return (
            <div>
                <Toolbar setFolderPathHandler={this.setFolderPathHandler} />
                <ImageContainer />
            </div>
        )
    }
}

Toolbar/index.js

import React, { Component } from 'react';
import path from 'path';

class Toolbar extends Component {

    constructor(props) {
        super(props);
        this.submit = this.submit.bind(this);
    }

    submit(event) {
        event.preventDefault();
        this.props.setFolderPathHandler(this.input.value);
    }

    render() {
        return (
            <nav className="toolbar">
                <div className="column">
                    {/* START used to be it's own component */}
                    <form onSubmit={this.submit.bind(this)}>
                        <div className="form-field">
                            <input type="text" className="folder-path" ref={(input) => this.input = input} />
                            <button><i className="fa fa-fw fa-2x fa-folder-open" aria-hidden="true"></i></button>
                        </div>
                    </form>
                    {/* END used to be it's own component */}
                </div>
                <div className="column">
                    <button><i className="fa fa-fw fa-2x fa-chevron-left" aria-hidden="true"></i></button>
                    <button><i className="fa fa-fw fa-2x fa-chevron-right" aria-hidden="true"></i></button>
                </div>
            </nav>
        );
    }
}

export default Toolbar;
Sign up to request clarification or add additional context in comments.

1 Comment

This worked! I had to make a few amendments to it: there needed to be a constructor with super(props) and this.submit = this.submit.bind(this);. Also, I had to change this.input.val() to this.input.value. If you can make those amends too I shall mark this as the answer :)
1

First, if you want to track input value in toolbar, you should save it into state:

class Toolbar extends Component {
    constructor(...args) {
        super(...args)

        this.state = {text: ''};
    }

    onTextChange(event) {
       const text = event.target.value;
       this.setState({text});
    } 

    onSubmit(event) {
       event.preventDefault();

       this.props.setFolderPathHandler(this.state.text);
    }          

    render() {
        return (
            <nav className="toolbar">
                <div className="column">
                    <form onSubmit={this.onSubmit.bind(this)}>
                        <div className="form-field">
                            <input type="text" className="folder-path" value={this.state.text} onChange={this.onTextChange.bind(this)} />
                            <button><i className="fa fa-fw fa-2x fa-folder-open" aria-hidden="true"></i></button>
                        </div>
                    </form>
                </div>
                ...
            </nav>
        );
    }
}

export default Toolbar;

Note that we are keeping internally the value of the input and we are not passing the native events up to the parent controller.

Then in the parent controller:

setFolderPathHandler(input) {
    // do something with the input
}

This solution has the problem that you cannot reset the input value from the parent component unless the component is removed from DOM and added again.

This is usually solved by putting the state to the parent component. The parent can then pass text and onChange handlers to the child that contains the <input> or, the parent can create the <input> and pass it to the child component as a property. Consider:

class Toolbar extends Component {
    render() {
        return (
            <nav className="toolbar">
                <div className="column">
                    <form onSubmit={this.props.onSubmit}>
                        <div className="form-field">
                            {this.props.textInput}
                            <button><i className="fa fa-fw fa-2x fa-folder-open" aria-hidden="true"></i></button>
                        </div>
                    </form>
                </div>
                ...
            </nav>
        );
    }
}

and App:

export default class App extends Component {
    ...

    render() {
        const textInput = (
           <input ... value={this.state.text} onChange={this.onTextChange} />
        );

        return (
            <div>
                <Toolbar onSubmit={this.onSubmit} textInput={textInput} />
                <ImageContainer />
            </div>
        )
    }
}

This allows to separate components that handle the business logic (have state, smart components) and the components that only present things (stateless, dumb components).

Comments

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.