1

I'm trying to filter an array with fetched list of users. Users are stored in component state. I want to filter it by text from input.

Problem: When I enter letters the list is filtering, but when I delete letters result remains unchanged.

Thanks for help!!!

class App extends React.Component {
    state = {
        isLoading: true,
        users: [],
        error: null
    }

    fetchUsers() {
        fetch(`https://jsonplaceholder.typicode.com/users`)
        .then(response => response.json())
        .then(data =>
            this.setState({
              users: data,
              isLoading: false,
            })
        )
        .catch(error => this.setState({ error, isLoading: false }));
    }

    componentDidMount() {
        this.fetchUsers();
    }

    onChangeHandler(e) {
        console.log(e.target.value);
        let newArray = this.state.users.filter((d)=>{
            let searchValue = d.name.toLowerCase();
            return searchValue.indexOf(e.target.value) !== -1;
        });
        console.log(newArray)
        this.setState({
            users:newArray
        })
    }

    render() {
        const {isLoading, users, error} = this.state;
            return (
                <div>
                    <h1>Users List</h1>
                    <input type="text" value={this.state.value} placeholder="Search by user name..." onChange={this.onChangeHandler.bind(this)}/>
                    {error ? <p>{error.message}</p> : null}
                    <ol>
                    {!isLoading ? (
                        users.map(user => {
                            const {username, name, id} = user;
                            return (
                                <li key={id}>
                                    <p>{name} <span>@{username}</span></p>
                                </li>
                            );
                        })
                    ) : (
                        <h3>Loading...</h3>
                    )}
                    </ol>
                </div>
            );
      }
}

export default App;
1
  • well, you are removing when filtering, maybe you should just filter on the render instead, or use the users state only to filter down filteredUsers and show filteredUsers if defined, otherwise users? Commented Sep 20, 2019 at 14:34

3 Answers 3

1

To achieve expected result, use below option of storing the users list from API to a variable after using setState to update users

apiUsers = [];

    fetchUsers() {
        fetch(`https://jsonplaceholder.typicode.com/users`)
        .then(response => response.json())
        .then(data =>{
        this.apiUsers = data;
            this.setState({
              users: data,
              isLoading: false,
            })
            }
        )
        .catch(error => this.setState({ error, isLoading: false }));
    }

Instead of filtering this.state.users use the newly created variable - apiUsers in onChangeHandler

onChangeHandler(e) {
        console.log(e.target.value);
        let newArray = this.apiUsers.filter((d)=>{
          console.log(d)
            let searchValue = d.name.toLowerCase();
            return searchValue.indexOf(e.target.value) !== -1;
        });
        console.log(newArray)
        this.setState({
            users:newArray
        })
    }

working code for reference - https://stackblitz.com/edit/react-sncf1e?file=index.js

Issue: : state.users array is getting updated without the copy of actual users list from api

Sign up to request clarification or add additional context in comments.

Comments

0

It happens because you do not keep a track of the original values you get from the API. Once you start filtering it, you lose the full list.

I created a codesandbox: https://codesandbox.io/s/nostalgic-sunset-jig33 to fix your issue.

What I did is to add a initialUsers value in the state and use it to store the value coming from the API:

state = {
    isLoading: true,
    initialUsers: [],
    users: [],
    inputValue: "",
    error: null
  };

 fetchUsers() {
    fetch(`https://jsonplaceholder.typicode.com/users`)
      .then(response => response.json())
      .then(data =>
        this.setState({
          initialUsers: data,
          isLoading: false
        })
      )
      .catch(error => this.setState({ error, isLoading: false }));
  }

and in the render method, I switch between displaying the filtered list of the original list by checking if a text is entered in the input element

(!!inputValue ? users : initialUsers).map(
              user => {
                const { username, name, id } = user;
                return (
                  <li key={id}>
                    <p>
                      {name} <span>@{username}</span>
                    </p>
                  </li>
                );
              }
            )

Comments

0

Well, if you logically look at your current solution then you have the following situation

  • users is an empty array, with isLoading true
  • users are fetched and pushed in the array, lets assume ['John', 'Joe', 'Alfred']
  • you filter your users for the letter J and update the user state to ['John', 'Joe']
  • You remove the filter for letter J and update the user state to ['John', 'Joe'] cause the component completely forgot about Alfred, you deleted hem in step 3

So, you have several options, just filter the render before the mapping, depending on how many users, that shouldn't be that bad or create more than 1 property on the state, say filteredUsers which contains the list of users that match your filter, and work with that state instead.

The cool thing about your current filter, is that it will always go faster, as your resultset kind of trims down, so that's a good thing, but I doubt you'll be dealing with so many users that you need this.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.