michasaucer
michasaucer

Reputation: 5258

ReactJS instant Search with input

Im making my first react project. Im new in JS, HTML, CSS and even web app programming.

What i want to do it is a Search input label. Now its look like this:

enter image description here

Like you can see i have some list of objects and text input.

I Have two components, my ProjectList.js with Search.js component...

class ProjectsList extends Component {
  render() {
    return (
      <div>
        <Search projects={this.props.projects} />
        <ListGroup>
          {this.props.projects.map(project => {
            return <Project project={project} key={project.id} />;
          })}
        </ListGroup>
      </div>
    );
  }
}

export default ProjectsList;

... and ProjectList.js displays Project.js:

How looks Search.js (its not ended component)

class Search extends Component {
  state = {
    query: ""
  };

  handleInputChange = () => {
    this.setState({
      query: this.search.value
    });
  };

  render() {
    return (
      <form>
        <input
          ref={input => (this.search = input)}
          onChange={this.handleInputChange}
        />
        <p />
       </form>
    );
  }
}

export default Search;

My project have name property. Could you tell me how to code Search.js component poperly, to change displaying projects dynamically based on input in text label? for example, return Project only, if text from input match (i want to search it dynamically, when i start typing m... it shows all projects started on m etc).

How to make that Search input properly? How to make it to be universal, for example to Search in another list of objects? And how to get input from Search back to Parent component?

For now, in react dev tools whatever i type there i get length: 0

Thanks for any advices!

EDIT:

If needed, my Project.js component:

class Project extends Component {
  state = {
    showDetails: false
  };
  constructor(props) {
    super(props);
    this.state = {
       showDetails: false
    };
  }

  toggleShowProjects = () => {
    this.setState(prevState => ({
      showDetails: !prevState.showDetails
    }));
  };

  render() {
    return (
      <ButtonToolbar>
        <ListGroupItem className="spread">
          {this.props.project.name}
        </ListGroupItem>
        <Button onClick={this.toggleShowProjects} bsStyle="primary">
          Details
        </Button>
        {this.state.showDetails && (
          <ProjectDetails project={this.props.project} />
        )}
      </ButtonToolbar>
    );
  }
}

export default Project;

Upvotes: 4

Views: 14726

Answers (2)

Treycos
Treycos

Reputation: 7492

I will assumes both your Search and ProjectList component have a common parent that contains the list of your projects. If so, you should pass a function into your Search component props, your Search component will then call this function when the user typed something in the search bar. This will help your parent element decide what your ProjectsLists needs to render :

handleInputChange = () => {
    this.props.userSearchInput(this.search.value);
    this.setState({
        query: this.search.value
    });
};

And now, here is what the parent element needs to include :

searchChanged = searchString => {
    const filteredProjects = this.state.projects.filter(project => project.name.includes(searchString))
    this.setState({ filteredProjects })
}

With this function, you will filter out the projects that includes the string the user typed in their names, you will then only need to put this array in your state and pass it to your ProjectsList component props

You can find the documentation of the String includes function here

You can now add this function to the props of your Search component when creating it :

<Search userSearchInput={searchChanged}/>

And pass the filtered array into your ProjectsList props :

<ProjectsList projects={this.state.filteredProjects}/>

Side note : Try to avoid using refs, the onCHnage function will send an "event" object to your function, containing everything about what the user typed :

handleInputChange = event => {
    const { value } = event.target
    this.props.userSearchInput(value);
    this.setState({
        query: value
    });
};

You can now remove the ref from your code

Upvotes: 2

Dacre Denny
Dacre Denny

Reputation: 30400

To create a "generic" search box, perhaps you could do something like the following:

class Search extends React.Component {

  componentDidMount() {

    const { projects, filterProject, onUpdateProjects } = this.props;
    onUpdateProjects(projects);
  }

  handleInputChange = (event) => {

    const query = event.currentTarget.value;
    const { projects, filterProject, onUpdateProjects } = this.props;

    const filteredProjects = projects.filter(project => !query || filterProject(query, project));

    onUpdateProjects(filteredProjects);
  };

  render() {
    return (
      <form>
        <input onChange={this.handleInputChange} />
       </form>
    );
  }
}

This revised version of Search takes some additional props which allows it to be reused as required. In addition to the projects prop, you also pass filterProject and onUpdateProjects callbacks which are provided by calling code. The filterProject callback allows you to provide custom filtering logic for each <Search/> component rendered. The onUpdateProjects callback basically returns the "filtered list" of projects, suitable for rendering in the parent component (ie <ProjectList/>).

The only other significant change here is the addition of visibleProjects to the state of <ProjectList/> which tracks the visible (ie filtered) projects from the original list of projects passed to <ProjectList/>:

class Project extends React.Component {
  render() {
    return (
      <div>{ this.props.project }</div>
    );
  }
}

class ProjectsList extends React.Component {

  componentWillMount() {
    
    this.setState({ visibleProjects : [] })
  }

  render() {
    return (
      <div>
        <Search projects={this.props.projects} filterProject={ (query,project) => (project == query) } onUpdateProjects={ projects => this.setState({ visibleProjects : projects }) }  />
        <div>
          {this.state.visibleProjects.map(project => {
            return <Project project={project} key={project.id} />;
          })}
        </div>
      </div>
    );
  }
}

class Search extends React.Component {
  
  componentDidMount() {
    
    const { projects, filterProject, onUpdateProjects } = this.props;
    onUpdateProjects(projects);
  }
  
  handleInputChange = (event) => {
    
    const query = event.currentTarget.value;
    const { projects, filterProject, onUpdateProjects } = this.props;
    
    const filteredProjects = projects.filter(project => !query || filterProject(query, project));
    
    onUpdateProjects(filteredProjects);
  };

  render() {
    return (
      <form>
        <input onChange={this.handleInputChange} />
       </form>
    );
  }
}

ReactDOM.render(
  <ProjectsList projects={[0,1,2,3]} />,
  document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Upvotes: 5

Related Questions