0

I have a function that renders content to page based on a state populated by API data, but I need to have an onClick event to refine that content;

So currently getPosts returns information from the state 'posts' which is provided with data from our API, but i want to filter this content further, so my idea is to have some sort of event listener, and if actioned, change the data coming out of getPosts.

constructor() {
    super();
    this.state = {
        posts: ""
    }
    this.getPosts = this.getPosts.bind(this);
}
async componentWillMount(){
    var data = await api.posts();
    this.setState({posts: data.data});
    console.log(this.state.posts);
}
getPosts(type){
        if(this.state.posts.length){
            return this.state.posts.map((content,index) => {
                var url = content.Title.replace(/[^\w\s]/gi, '');
                url = url.replace(/\s+/g, '-').toLowerCase();
                if(type === content.PostType){
                    //output something different
                }
                else{
                    return(
                        <Col md={4} className="mb-4" key={index}>
                            {content.title}
                        </Col>
                    );
                }
            })

        }
    }
    render() {
        return (
            <div>
            <p><button onClick={()=>{this.getPosts('blog')}}>blog</button> <button onClick={()=>{this.getPosts('news')}}>news</button></p>
            {this.getPosts()}
            </div>
        )
    }

So my getPosts works fine without any type, how to do tell it to re-output the function on the page based in the onClick event?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
grhmstwrt
  • 341
  • 1
  • 6
  • 20
  • Sorry I don't understand well, you want to requesting data from api any time you click the button ? – Juorder Gonzalez Sep 27 '19 at 16:08
  • No, i already have all the data in 'posts' - i was just explaining where the data was coming from. I just want to put a condition around the return on getPosts to filter this return down – grhmstwrt Sep 27 '19 at 16:17

2 Answers2

1

Without getting into the complexities of context and keys, a component requires a change in props or state to re-render. To read more about state and component life-cycle, the docs have a great explanation for class components.

Your component does not re-render after the onClick event handler's call to getPosts because getPosts does not update internal component state. getPosts works within render because those values are being returned to React. By using getPosts as an onClick event handler, you are creating React elements and trying to return them to the window.

What follows should be treated as psuedo code that shows how to trigger your component to render different posts:

  • Consider adding another key to state in your constructor,
constructor(props) {
  super(props);
  this.state = {
    posts: "",
    type: null
  };
  this.getPosts = this.getPosts.bind(this);
  this.onClick = this.onClick.bind(this);
}
  • and creating a click handler that doesn't try to return React elements
function onClick(evt) {
  this.setState({ type: evt.target.value });
}
  • and values to your buttons
<button onClick={this.onClick} type="button" value="blog">blog</button>
  • Now your button will update state with your new post type, causing your component to re-render:
render() {
  return (
    <div>
      <p>
        <button onClick={this.onClick} type="button" value="blog">blog</button>
        <button onClick={this.onClick} type="button" value="news">news</button>
      </p>
      {this.getPosts()}
    </div>
  );
}

With the content type being stored in state, you can now implement your getPosts call in any way that works for you. Good luck!

It strays from the question asked, but it is worth noting componentWillMount is being deprecated, and componentDidMount is a preferable life-cycle function for side-effects and asynchronous behavior. Thankfully, the documentation has lots of details!

BEVR1337
  • 623
  • 4
  • 10
  • Thanks @BEVR1337 - that's exactly what i was looking for! Just a note; I couldn't get your code to pick up the target.value, not sure if perhaps theres a syntax error there, so i just past the val into the param 'onClick={() => { this.onClick("blog") }}'. I also noticed that you have to pass it through on the onClick as a arrow function or it runs the function onload (which i found the answer to here: https://stackoverflow.com/questions/33846682/react-onclick-function-fires-on-render) – grhmstwrt Sep 30 '19 at 14:58
  • @grhmstwrt Glad this was helpful! Happy coding! `evt.target.value` will be null unless your html element has the attribute value, e.g. ``. Was that a possible issue? In my example the function should not run `onload`, so I would need to see more code to understand how you reproduced that error, likely by calling a function instead of passing the function itself? – BEVR1337 Oct 08 '19 at 15:30
0

Ok so you should start by changing your default this.state to

this.state = {
  posts: []
}

remember that you want to iterate over an array of data instead of iterate a string, that will throw an error if you do that, so better keep from the beginning the data type you want to use.

Then you need to separate the responsibility for your getPosts method, maybe getPostByType is a better name for that, so you have

getPostByType(type) {
  // if type is same as content.PostType then return it;
  const nextPosts = this.state.posts.filter((content) => type === content.PostType);
  this.setState({ posts: nextPosts });
}

and finally you can iterate over posts, like this

render() {
  // better use content.id if exists instead of index, this is for avoid problems 
  // with rerender when same index key is applied.
  const posts = this.state.posts.map((content, index) => (
    <Col md={4} className="mb-4" key={content.id || index}>
      {content.title}
    </Col>
  ));
  return (
    <div>
      <button onClick={() => this.getPostByType('blog')}>Show Blog Posts</button>
      {posts}
    </div>
  );
}

Then you can use getPostsByType any time in any click event passing the type that you want to render.

Juorder Gonzalez
  • 1,642
  • 1
  • 8
  • 10
  • this errors out: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops. – grhmstwrt Sep 27 '19 at 16:27
  • please remember not to use getPostByType directly into the `render` method only invoke the function in onClick events or any other events. – Juorder Gonzalez Sep 27 '19 at 16:32
  • please can you provide the `render` method code here – Juorder Gonzalez Sep 27 '19 at 16:35