0

I am new to ReactJS and am unsuccessfully attempting to manage a state change. The initial state renders as expected, the state successfully changes, however the elements do not render afterwards. There are no errors in the DOM console to go off of. I've made sure to set the initial state in the constructor of the component class, and I've also tried binding the method I'm using in the constructor since I've read auto-binding is not a part of ES6. The relevant component code is as follows:

class MyComponent extends Component {
    constructor(props) {
        super(props);
            this.state = {
                myIDs: Array(6).fill('0')
        };
        this.getMyIDs = this.getMyIDs.bind(this);

};

componentDidMount() {
    var ids = this.getMyIDs();
    ids.then((result)=> {
        this.setState({ myIDs: result }, () => {
            console.log(this.state.myIDs)
        });
    })

};

componentWillUnmount() {
    this.setState({
        myIDs: Array(6).fill('0')
    });
};

getMyIDs() {
    return fetch('/api/endpoint').then((response) =>{
        return response.json();
    }).then((myIDs) => {
        return myIDs.result
    })
};

render() {
    return (
        <Tweet tweetId={this.state.myIDs[0]} />
        <Tweet tweetId={this.state.myIDs[1]} />
        );
   }
}

export default MyComponent

UPDATE: The 'element' being updated is the 'Tweet' component from react-twitter-widgets. Its source is here:

https://github.com/andrewsuzuki/react-twitter-widgets/blob/master/src/components/Tweet.js'

export default class Tweet extends React.Component {
  static propTypes = {
    tweetId: PropTypes.string.isRequired,
    options: PropTypes.object,
    onLoad: PropTypes.func,
  };

  static defaultProps = {
    options: {},
    onLoad: () => {},
  };

  shouldComponentUpdate(nextProps) {
    const changed = (name) => !isEqual(this.props[name], nextProps[name])
    return changed('tweetId') || changed('options')
  }

  ready = (tw, element, done) => {
    const { tweetId, options, onLoad } = this.props

    // Options must be cloned since Twitter Widgets modifies it directly
    tw.widgets.createTweet(tweetId, element, cloneDeep(options))
    .then(() => {
      // Widget is loaded
      done()
      onLoad()
    })
  }

  render() {
    return React.createElement(AbstractWidget, { ready: this.ready })
  }
}
ark9719
  • 1
  • 2

2 Answers2

2

As in React docs:

componentWillMount() is invoked just before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering. Generally, we recommend using the constructor() instead.

Avoid introducing any side-effects or subscriptions in this method. For those use cases, use componentDidMount() instead.

you should not use ajax calls in componentWillMount call ajax inside: componentDidMount

another thing: why do you use componentWillUnmount

the object will be removed no reason to have that call there.

Tzook Bar Noy
  • 11,337
  • 14
  • 51
  • 82
  • 1
    That won't make any difference to OP use case. Its a practice one must follow and not a reason for the issue – Shubham Khatri Feb 12 '18 at 17:57
  • 1
    `calling setState() synchronously in this method will not trigger an extra rendering`. But OP is calling `setState` asynchronously in `componentWillMount`. – Prakash Sharma Feb 12 '18 at 18:01
  • I have tried both ComponentWillMount and ComponentDidMount neither with success. – ark9719 Feb 12 '18 at 18:08
  • please "console.log(this.state)" in your render method and lets see the output that you have for page load – Tzook Bar Noy Feb 12 '18 at 18:12
  • This is the console output from logging this.state inside the render function, I've changed the unique IDs to stars: (6) ["*******************", "*******************", "*******************", "*******************", "*******************", "*******************"] – ark9719 Feb 12 '18 at 18:18
  • What is your expectation in your UI now? Should it display 2 list of stars ? can you just change Tweet to
    { this.state.myIDs[0] }
    and test it
    – G_S Feb 12 '18 at 18:20
  • When you pass a tweet ID to a element, it fetches, renders and embeds a full tweet. I removed the IDs because I'm not trying to promote my personal twitter account. – ark9719 Feb 12 '18 at 18:22
  • @G_S I can render just the ID like you said. But when I put it in the tweet element, it does not render. – ark9719 Feb 12 '18 at 18:25
  • If you are getting the IDs that you are expecting when you add a div, there should be something fishy that component is doing. – G_S Feb 12 '18 at 18:26
  • @ark9719 if a simple print of the ids works well. So lets close this question and post a new one with your `Tweet` components – Tzook Bar Noy Feb 12 '18 at 18:29
0

The only issue that is present in your current code is that you are returning multiple Element component instances without wrapping them in an array of a React.Fragment or a wrapper div. With the latest version of react, you must write

render() {
    return (
        <React.Fragment>
           <Element Id={this.state.myIDs[0]} />
            <Element Id={this.state.myIDs[1]} />
        </React.Fragment>
        );
   }
}

Also as a practice you must have your Async calls in componentDidMount instead of componentWillMount as the React docs also suggest. You might want to read this answer on where write async calls in React for more details

Another thing that you must remember while using prop Id in your Element component is that componentWillMount and componentDidMount lifecycle functions are only called on the initial Render and not after that, so if you are using this.props.Id in one of these function in Element component then you will not be able to see the update since the result of async request will only come later, check this answer on how to tacke this situation

Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • @G_S, yes you are right of course, it should throw an error for the other person, however on the contrary, I have also pointed on the other thing that could be wrong in the implementation apart from pointing out what is wrong in the provided code – Shubham Khatri Feb 12 '18 at 18:05
  • I have followed the suggestions to use componentDidMount, however that has not fixed the rendering problem. I will look into the solutions you've linked. At the moment I only plan on using the prop on render and not after that. – ark9719 Feb 12 '18 at 18:09
  • Can you add the Element component in your question – Shubham Khatri Feb 12 '18 at 18:09
  • I've updated the question with information on that component. – ark9719 Feb 12 '18 at 18:19