Handling click with .sort

Hello - I’m trying to sort a list on the click of a button, with the below code nothing happens when I click.

If I instead have onClick={this.handleClick()} I get the error: warning: Expected onClick listener to be a function, instead got a value of object type. It seems simple, I can’t see where the problem is.

 handleClick = event => {
    return this.renderList().sort();
  };

render() {
    return (
      <div>
          <button
            className="ui button primary"
            onClick={() => this.handleClick()}
          >
            Sort List
          </button>
          <div className="item-list">{this.renderList()}</div>
        </div>
    );
  }
}

React re-renders only if there is a change of state or props.

thanks! it did cross my mind but

handleClick = event => {
    return this.renderList().sort();
    this.renderList()
  };

gives a warning of unreachable code, even though the same function is accessed on the penultimate line…?

return ends function execution. Lines after it will never be called.

You can’t expect anything to execute after a return statement. The return value of a button click handler isn’t meaningful anyway. Furthermore, as @jenovs mentioned, your component won’t re-render unless you change the state or props of the component, so you’ll need to figure out how to set the component’s renderList to the new value (hint: check out the setState method on components)

I’m personally dreading the React course and having to use vanilla React with setState and all that, since I’ve only ever used pure functional components and redux to manage state.

setState is sooo 2018. Hooks all the way.

okay - I’ve got this now:

 handleClick = event => {
    const newList = this.renderList().sort();
    return newList;
  };

I don’t think it’s a question of changing state, because it is just rearranging the array?
(it’s also within a react-redux app, so state/store is another area I’d rather avoid anyway).

Unless you use setState React doesn’t see it (and doesn’t care about it). Or if you use redux you need to update store.

If it is a redux application, you’ll want to dispatch an action, something like dispatch({type: 'SORT_LIST'}) and handle it in the reducer which should return a new state with the list sorted. If you already have a reducer defined and your component hooked up to the store with connect(), it should be pretty straightforward to do it. Otherwise it’s a little beyond the forum format to introduce react-redux at this time.

hmm I wanted it to be a dumb/presentational component… I was asked to create a sort without dispatching actions, so using standard js/react.

What jenovs says makes sense, I just hope it is maybe possible to set local component state within a broader redux app.

Thanks for your help!

Yes it’s perfectly fine (and even recommended) for components to have local state. No need to put every piece of data into redux and write 100% functional components.

And since v16.8 introduced react hooks you can write dumb components with local state :scream:

EDIT: Here’s a simple example using hooks: https://codesandbox.io/s/xo731qplxo

Great, it now looks:

 state = {
    books: this.props.books
  };

...

 handleClick = event => {
    this.setState({
      books: this.props.books.sort()
    });
    return this.renderList();
  };

(the props come from the store), but it still doesn’t do anything when I click. Thanks for the hooks tip, I think that might be a bit advanced, the question was to see if I know js/react fundamentals I think!

You shouldn’t be calling this.renderList() from handleClick. The way you have your render function, renderList() should take this.state.books as an argument and then if you use setState React will automatically rerender.

Also hooks are React fundamentals (since one week ago :smile:)

not sure my bootcamp has had a chance to write the syllabus for it yet!

The whole component looks like this, I wonder if renderAdmin is getting in the way (still nothing happens on click):

import React from "react";
import { connect } from "react-redux";
import { fetchBooks } from "../actions/books";
import { Link } from "react-router-dom";
import "./ItemList.css";

class BookList extends React.Component {
  componentDidMount() {
    this.props.fetchBooks();
  }

  state = {
    books: this.props.books
  };

  renderAdmin(book) {
    if (book.userId === this.props.currentUserId) {
      return (
        <div className="right floated content">
          <Link
            to={`/books/edit/${book.id}`}
            className="ui button primary green"
          >
            Edit
          </Link>
          <Link to={`/books/delete/${book.id}`} className="ui button negative">
            Delete
          </Link>
        </div>
      );
    }
  }

  renderList() {
    return this.state.books.map(book => {
      return (
        <div className="item" key={book.id}>
          {this.renderAdmin(book)}
          <div className="item">
            <div style={{ textAlign: "center" }}>
              <h4>
                {book.title} by <br /> {book.writers.map(writer => writer.name)}
              </h4>
            </div>
            <Link to={`/books/${book.id}`} className="header">
              <div>
                <img src={book.image} alt="nearly" className="img-responsive" />
              </div>
            </Link>
            <div style={{ textAlign: "right" }} />
          </div>
        </div>
      );
    });
  }

  renderCreate() {
    if (this.props.isSignedIn) {
      return (
        <div style={{ textAlign: "right" }}>
          <Link to="/books/new" className="ui button primary olive">
            Recommend a book
          </Link>
        </div>
      );
    }
  }

  handleClick = () => {
    this.setState({
      books: this.props.books.sort()
    });
  };

  render() {
    return (
      <div>
        <h2>Books</h2>
        <div>
          <div className="item-list">{this.renderCreate()}</div>
          <button
            className="ui button primary"
            onClick={() => this.handleClick()}
          >
            Sort!
          </button>
          <div className="item-list">{this.renderList()}</div>
        </div>
      </div>
    );
  }
}

const mapStateToProps = state => {
  return {
    //returns an array instead of an object
    books: Object.values(state.books),
    currentUserId: state.auth.userId,
    isSignedIn: state.auth.isSignedIn
  };
};

export default connect(
  mapStateToProps,
  { fetchBooks }
)(BookList);

Change handleClick:

handleClick = () => {
    this.setState({
      books: [...this.props.books].sort() // sort copy
    });
  };

still nothing… :confused:

Doh, it should be this.state.books

hmm either way it is breaking the initial receive from the store, so it renders nothing until I move around the app to trigger the fetch elsewhere, and then nothing happens on click…

I just joined discordapp dot com , a redux support group chat, they say that sort won’t work because book is an object (with string attributes) not a string! so I have to write a custom function to pass in.

I’m going to have another go!
thanks for your help :slight_smile:

Correct. I used strings in my example so I automatically assumed (wrongly) that books is a string.

yep, I just conflated the two, cause it’s made up of string attributes. What about forceUpdate() though to prevent the need for using a component state for the re-render? …I might try that