Search code examples
javascriptreactjsecmascript-6lodashecmascript-2017

How to get a particular property of one JSON using one common property with other JSON objects?


I am new to react.js and facing problems while performing the below task.

When running following the code, it shows setState can't be used and the app breaks. I have these two objects, obj2 generates a table 3 columns. I need to make each cell of definition column which are not null clickable which then land me to the team_wiki link which is present in obj1.

obj1: {
    providers : [
         {team_name : "XYZ", team_wiki : "https://example.com", team_id : 1},
         {team_name : "ABC", team_wiki : "null", team_id : 2},
         {team_name : "LMN", team_wiki : "https://example2.com", team_id : 3},
         {team_name : "MNO", team_wiki : "https://example3.com", team_id : 4}
    ]
}

obj2: {
    products : [
       {team_name : "XYZ", definition : "this team handles XYZ products", metric_id : 101},
       {team_name : "ABC", definition : "this team handles ABC products", metric_id : 201},
       {team_name : "LMN", definition : "this team handles LMN products", metric_id : 301},
       {team_name : "MNO", definition : "this team handles MNO products", metric_id : 401}
    ]
}

Code:

state = {
    API_DATA : {},
    TEAM_WIKI : ''
}

componentDidMount(){
   // fetch the obj1 data and sets it to the state called API_DATA
}

wikiLink = (TEAM_NAME) {
   // getting TEAM_NAME from a component inside the render method
   const wiki = this.state.API_DATA.map(provider => {
       if (provider.team_name = TEAM_NAME && provider.team_wiki !== null) {
           return provider.team_wiki
       }
   })
   .map(link => {
       if (link !== undefined) {
           return link
       }
   })

   this.setState({
      // TEAM_WIKI is a state { its a string } where i want to store the 
      //team_wiki at last
      TEAM_WIKI : wiki
   })
}

render() {

   return (
         // i want to use it something like this
        < href="this.state.TEAM_WIKI" onClick={this.wikiLink(TEAM_NAME)} />
   ) 
}

Solution

  • It feels to me that when the first ever render happens, this.wikiLink is being called, not passed as reference. Why do I think so? Because I see round brackets there. The body of this method contains a call to this.setState, which means the state of the component is being swapped on render time, and it is a violation of component lifecycle.

    Consider a case:

    class MyComponent extends Component {
      constructor(props) {
        super(props);
        this.state = {
          buttonClicked: false,
        };
        this.handleClickButton = this.handleClickButton.bind(this);
      }
    
      handleClickButton() {
        this.setState({
          buttonClicked: true,
        });
      }
    
      render() {
        return (
          <button onClick={this.handleClickButton()}>click me</button>
        );
      }
    }
    

    There is a problem! The expression this.handleClickButton() is a function call. Within it, there is a state change instruction. When the code is trying to do that, React shows a warning:

    Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.

    What we really want instead is to call the function this.handleClickButton on button click. To do so, we need to pass a reference to this function to the onClick prop, like this:

      render() {
        return (
          <button onClick={this.handleClickButton}>click me</button>
        );
      }
    

    notice the absence of round brackets.

    So what should you do then?

    There are two options:

    Option 1: anonymous function inside onClick

    Instead of writing

    render() {
       return (
         // i want to use it something like this
         <a href="this.state.TEAM_WIKI" onClick={this.wikiLink(TEAM_NAME)} />
       );
    }
    

    do

    render() {
       return (
         // i want to use it something like this
         <a href="this.state.TEAM_WIKI" onClick={() => this.wikiLink(TEAM_NAME)} />
       );
    }
    

    This way, a reference to an anonymous function declared in-place is passed per each call to render method of the component instance. This function is then going to be called at some unpredictable point in time when the user decides to click, that is, dispatch the MouseEvent of type "click".

    Option 2: make this.wikiLink(•) return a function!

    The concept of function returning a function is often referred to as partial application or, in cases similar to this more often, a closure. So the wikiLink function would then instead of this:

    wikiLink(TEAM_NAME) {
      // getting TEAM_NAME from a component inside the render method
      const wiki = this.state.API_DATA.map(provider => {
        if ((provider.team_name = TEAM_NAME && provider.team_wiki !== null)) {
          return provider.team_wiki;
        }
      }).map(link => {
        if (link !== undefined) {
          return link;
        }
      });
    
      this.setState({
        // TEAM_WIKI is a state { its a string } where i want to store the
        //team_wiki at last
        TEAM_WIKI: wiki
      });
    }
    

    look like this:

    wikiLink(TEAM_NAME) {
      return () => {
        // getting TEAM_NAME from a component inside the render method
        const wiki = this.state.API_DATA.map(provider => {
          if ((provider.team_name = TEAM_NAME && provider.team_wiki !== null)) {
            return provider.team_wiki;
          }
        }).map(link => {
          if (link !== undefined) {
            return link;
          }
        });
    
        this.setState({
          // TEAM_WIKI is a state { its a string } where i want to store the
          //team_wiki at last
          TEAM_WIKI: wiki
        });
      };
    }
    

    So that when it is called, it returns a new function. Because onClick prop is expected to receive a function, we solve the problem of type matching. Because we call wikiLink with some argument TEAM_NAME and this argument is being applied every time that function is executed, we solve the problem of retaining context. Isn't this awesome!


    To get a better idea of using a function that returns a function, check out this Codesandbox, lines 16 to 23. Hope it will give some idea of how to leverage closures in situations when you need to manage state of a React component.

    Cheers!