[MERN] 検索機能



##1. SearhBox作成

```components/layout/SearchBox.js
import React, { useState } from 'react';
import { Form, Button } from 'react-bootstrap';

const SearchBox = ({ history }) => {
  const [keyword, setKeyword] = useState('');

  const submitHandler = (e) => {
    e.preventDefault();
    if (keyword.trim()) {
      history.push(`/search/${keyword}`);
    } else {
      history.push('/');
    }
  };

  return (
    <Form onSubmit={submitHandler} inline>
      <Form.Control
        type="text"
        name="q"
        onChange={(e) => setKeyword(e.target.value)}
        placeholder="Search Products..."
        className="mr-sm-2 ml-sm-5"
      ></Form.Control>
      <Button type="submit" variant="outline-success" className="p-2">
        Search
      </Button>
    </Form>
  );
};

export default SearchBox;
```


##2. search/:keywordのRoute設置

```components/routing/Routes.js
//...
const Routes = () => {
//...
        <Route exact path="/profile/:id" component={Profile} />
+       <Route exact path="/search/:keyword" component={Posts} />
        <Route exact path="/posts" component={Posts} />
        <Route exact path="/posts/:id" component={Post} />
//...
```

##3. Navbarに検索フォーム設置

```components/layout/Navbar.js
//...
import PropTypes from 'prop-types';
+ import SearchBox from './SearchBox';
import { logout } from '../../actions/auth';

const Navbar = ({ auth: { isAuthenticated, loading }, logout }) => {
//...
  return (
    <nav className="navbar bg-dark">
      <h1>
        <Link to="/posts">
          <i className="fas fa-code" /> Refnote
        </Link>
      </h1>

+      <Route render={({ history }) => <SearchBox history={history} />} />

      {!loading && (
        <Fragment>{isAuthenticated ? authLinks : guestLinks}</Fragment>
      )}
    </nav>
  );
};
//...
```

##4. Posts一覧にkeyword Params設置

```components/posts/Posts.js
//...
import { getPosts } from '../../actions/post';

+ const Posts = ({ getPosts, post: { posts, loading }, match, auth }) => {
+  const keyword = match.params.keyword;

  useEffect(() => {
+    getPosts(keyword);
+  }, [getPosts, keyword]);

  return loading ? (
//...
      <div className="Posts">
        {posts.map((post) => (
          <PostItem key={post._id} post={post} />
        ))}
      </div>
    </Fragment>
  );
};
//...
```


##5.  getPostsアクションにKeyword Propsを設置

```actions/post.js
//...
// Get posts
+ export const getPosts = (keyword = '') => async (dispatch) => {
  try {
+    const res = await axios.get(`/api/posts?keyword=${keyword}`);
    dispatch({
      type: GET_POSTS,
      payload: res.data,
    });
  } catch (err) {
    dispatch({
      type: POST_ERROR,
      payload: { msg: err.response.statusText, status: err.response.status },
    });
  }
};
//...
```

##6. postsAPIを編集

```routes/api/posts.js
//...
// @route  GET api/posts
// @desc   Get all posts
// @access Public
router.get('/', async (req, res) => {
+ const keyword = req.query.keyword
+   ? {
+       text: {
+         $regex: req.query.keyword,
+         $options: 'i',
+       },
+     }
+   : {};

  try {
+   const posts = await Post.find({ ...keyword }).sort({ date: -1 });
    res.json(posts);
  } catch (err) {
    console.error(err.message);
    res.status(500).send('Server Error');
  }
});
//...
```