Search code examples
javascriptreactjscomponentsparent-childstateless

react stateless child components do not update on state change in parent component


I'm building a small tabbed application in which each tab renders a child component.

I wanted each tab to display different content so I created an object contentTypes outside render(){ .... } to map each tab to it's corresponding child component. The child components receive handlers and states via props.

class App extends Component {
    state ={
        contentType: "A",
        title: "Overview",
        searchContent: ""
    }

    tabHandler= (content,event)=>{
       console.log("clicked");
       this.setState({
          title: event.target.name,
          contentType: content
       });
    }
    searchHandler = (event) => {

       // this performs a search on a database and 
       //returns suggestions for the current input

       this.setState({
          searchContent: event.target.value
       });
   }  

    contentTypes = {
        A: <A/>,
        B: <B/>,
        C: <C onchange={this.searchHandler.bind(this)} content={this.state.searchContent}/>
    };
    render() {
      return (
        <div className="row">
          <ul>
           <Tab click={this.tabHandler.bind(this)} id="A" name="A"/>
           <Tab click={this.tabHandler.bind(this)} id="B" name="B"/>
           <Tab click={this.tabHandler.bind(this)} id="C" name="C"/>
         </ul>
        <h1 id="title">{this.state.title}</h1>

        {this.contentTypes[this.state.contentType]}

       </div>
     );
   }
}

The child components are stateless, defined somewhat like this:

import React from 'react'
import Field from './form-builder/field';

const C = (props) => {

return(

    // Field is another stateless component with formatted <input> tags

    <Field type="text" name="search" content={props.content} onchange={props.onchange}/>
  );
 }

export default C;

Field is defined as follows:

import React from 'react';

const field = (props) => {
    <li className="field">
       <input type="text" onChange={props.onchange} value={props.content} />
       <label htmlFor={props.name}>{props.value}</label>
    </li>
}
export default field;

The problem is that when I call a handler from inside Field to update the state in a parent component, (the one with render(){ .... }) The child components don't update with the new state.

Any ideas on how to fix this?


Solution

  • You define contentTypes as class property.
    It's actually same as:

    class App extends Component {
      constructor() {
        this.contentTypes = {
          A: <A />,
          B: <B />,
          C: <C
               onchange={this.searchHandler.bind(this)}
               content={this.state.searchContent}/>
        }
      }
    }
    

    So the element rendered in the constructor and never get any update of props.
    Here is a way to fix this:

    class App extends Component {
      contentTypes = {
        A: () => (<A/>),
        B: () => (<B/>),
        C: () => (
            <C
              onchange={this.searchHandler.bind(this)}
              content={this.state.searchContent} />
           )
      }
    
      // ...
    
      render () {
        const Content = this.contentTypes[this.state.contentType]
    
        return (
          <div>
          {/* ... */}
            <Content />
          </div>
        )
      }
    }