user3622460
user3622460

Reputation: 1251

Passing state in React from two different components

I have a TopNab bar (component), which contains a SearchBar component. My Main component is rendering out TopNav, the main container component (Profile), and the Footer component. I want my SearchBar component to pass its state to the main container component so that the Profile component can use it.

What I am trying to build: A user types a name into search bar and submits. The profile matching the user name is displayed in the main container component.

Right now I have a component that can render out user profiles. I also have a component thats state updates to the user submitted value. What I need to do is pass this user submitted value to my profile component in order to render out the correct profile.

Is this possible or do I need to rebuild my components so the search is included in the Profile component?

SearchBar

import React, { Component, PropTypes } from 'react';
import Profile from './Profile';


class SearchBar extends Component {
    constructor(props){
        super(props)
            this.state = {
                name: ''
            }
    }

    handleChange(e) {
        this.setState({
            name: e.target.value
        });
    }

    handleSubmit(e) {
            e.preventDefault();
            console.log("searching for NAME " + this.state.name);
            let profileName = this.state.name;
            //PASS STATE TO PROFILE COMPONENT
        }

    render() {
        return (
            <div>
                <form onSubmit={this.handleSubmit.bind(this)}>
                    ARMORY BETA
                    <input type="text" placeholder="Enter Name" 
                    name="name" 
                    value={this.state.name} 
                    onChange={this.handleChange.bind(this)} />
                    <button className="btn btn-success" type="submit">Search</button>
                </form>
            </div>
        )
    }
}

export default SearchBar;

Profile

import React, { Component, PropTypes } from 'react';
import SearchBar from './SearchBar';
import ProfileContainer from '../containers/ProfileContainer';

class Profile extends Component {

    render() {
        return (
            <div className="cols2">
                <div>[IMG]</div>
                <div>
                    <ProfileContainer name={this.props.name}/>
                </div>
            </div>
        )
    }
}

Profile.PropTypes = {
    name: PropTypes.string
}

Profile.defaultProps = {
    name: ''
}

export default Profile;

Main

import React, { Component } from 'react';
import TopNav from './TopNav';
import Footer from './Footer';
import Profile from './Profile';

class Main extends Component {
    render() {
        return (
            <div className="container-fluid">
                <div className="row">
                    //TopNav calls SearchBar
                    <TopNav />
                </div>
                <div className="row">
                    <Profile />
                </div>
                <div className="row">
                    <Footer />
                </div>
            </div>
        )
    }
}


export default Main;

Upvotes: 0

Views: 770

Answers (2)

Jeff McCloud
Jeff McCloud

Reputation: 5927

You'll need to expose a property on SearchBar that accepts a callback that will be called to indicate to its parent that the form was submitted (e.g. onSubmit)...

handleSubmit(e) {
        e.preventDefault();
        console.log("searching for NAME " + this.state.name);
        let profileName = this.state.name;
        //PASS STATE TO PROFILE COMPONENT
        this.props.onSubmit(yourFormData);
    }

...TopNav won't handle onSubmit itself, but just pass it on up to its own parent (perhaps renaming to "onSearchBarSubmit" along the way to make the name clearer from the perspective of TopNav's parent):

class TopNav extends Component {
    render() {
        return (
            <div className="container-fluid">
              <SearchBar onSubmit={this.props.onSearchBarSubmit}
            </div>
        );
    }
}

class Main extends Component {
    render() {
        return (
            <div className="container-fluid">
                <div className="row">
                    <TopNav onSearchBarSubmit={ (criteria) => this.searchForStuff(criteria) } />
                </div>
                <div className="row">
                    <Profile data={this.state.stuffYouGotBackFromSearch} />
                </div>
                <div className="row">
                    <Footer />
                </div>
            </div>
        )
    }
}

...OR, in some cases, it can be desirable to un-nest the components, allowing SearchBar as one of TopNav's props.children. This allows you to handle onSubmit directly within Main, and pass anything it receives onto Profile:

class Main extends Component {
    render() {
        return (
            <div className="container-fluid">
                <div className="row">
                    //TopNav calls SearchBar
                    <TopNav>
                      <SearchBar onSubmit={ (criteria) => this.searchForStuff(criteria) } />
                    </TopNav>
                </div>
                <div className="row">
                    <Profile data={this.state.stuffYouGotBackFromSearch} />
                </div>
                <div className="row">
                    <Footer />
                </div>
            </div>
        )
    }
}

...a side-benefit of un-nesting is that it would allow you to use TopNav and Searchbar independently.

Upvotes: 1

adkelley
adkelley

Reputation: 48

In Main, you should add a prop to <TopNav /> that points to a handler method that will propagate the profileName state change back to Main. This, in turn, will cause Profile to be re-rendered. The handler method takes one argument profileName and is called from the handleSubmit method in TopNav. Here's the code:

SearchBar

class SearchBar extends Component {
    . . .
    handleSubmit(e) {
        e.preventDefault();
        console.log("searching for NAME " + this.state.name);
        let profileName = this.state.name;
        this.props.handleProfileChange(profileName);
    }
    . . .
}
SearchBar.propTypes = {
  handleProfileChange: React.PropTypes.func.isRequired,
}

Main

class Main extends Component {
   constructor(props) {
     super(props);
     this.state = { profileName: '' }
     handleProfileChange = this.handleProfileChange.bind(this);
   }

   handleProfileChange(profileName) {
     // This state change will force Profile component to be re-rendered
     this.setState( { profileName });
   }

   render() {
     return (
        <div className="container-fluid">
            <div className="row">
                //TopNav calls SearchBar
                <TopNav handleProfileChange={this.handleProfileChange} />
            </div>
            <div className="row">
                <Profile profileName={this.state.profileName} />
            </div>
            <div className="row">
                <Footer />
            </div>
        </div>
    )
}

Upvotes: 1

Related Questions