~~~~~~~~~~ (Contents) MERN ~~~~~~~~~~~
[MERN①] Express & MongoDB Setup
https://qiita.com/niyomong/private/3281af84486876f897f7
[MERN②]User API Routes & JWT Authentication
https://qiita.com/niyomong/private/c11616ff7b64925f9a2b
[MERN③] Profile API Routes
https://qiita.com/niyomong/private/8cff4e6fa0e81b92cb49
[MERN④] Post API
https://qiita.com/niyomong/private/3ce66f15375ad04b8989
[MERN⑤] Getting Started With React & The Frontend
https://qiita.com/niyomong/private/a5759e2fb89c9f222b6b
[MERN⑥] Redux Setup & Alerts
https://qiita.com/niyomong/private/074c27259924c7fd306b
[MERN⑦] React User Authentication
https://qiita.com/niyomong/private/37151784671eff3b92b6
[MERN⑧] Dashboard & Profile Management
https://qiita.com/niyomong/private/ab7e5da1b1983a226aca
[MERN⑨] Profile Display
https://qiita.com/niyomong/private/42426135e959c7844dcb
[MERN⑩] Posts & Comments
https://qiita.com/niyomong/private/19c78aea482b734c3cf5
[MERN11] デプロイ
https://qiita.com/niyomong/private/150f9000ce51548134ad
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Finish Profile Actions & Reducer
アクションタイプ
//...
export const GET_PROFILE = 'GET_PROFILE';
+ export const GET_PROFILES = 'GET_PROFILES';
Profileアクション
import {
CLEAR_PROFILE,
GET_PROFILE,
PROFILE_ERROR,
ACCOUNT_DELETED,
+ GET_PROFILES,
} from './types';
//...
//以下追加。
// Get all profiles
export const getProfiles = () => async (dispatch) => {
dispatch({ type: CLEAR_PROFILE });
try {
const res = await axios.get('/api/profile');
dispatch({
type: GET_PROFILES,
payload: res.data,
});
} catch (err) {
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status },
});
}
};
//...
profileリデューサー
//...
case GET_PROFILES:
return {
...state,
profiles: payload,
loading: false,
};
//...
getProfileByIdアクション
// Get profile by ID
export const getProfileById = (userId) => async (dispatch) => {
dispatch({ type: CLEAR_PROFILE });
try {
const res = await axios.get(`/api/profile/user/${userId}`);
dispatch({
type: GET_PROFILE,
payload: res.data,
});
} catch (err) {
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status },
});
}
};
//...
2. Display Profiles
① components/profilesフォルダ作成
・Profilesコンポーネント生成 (Profilesページ)
・ProfileItemコンポーネント生成 (Profilesの一部)
② ProfilesページをルートとNavbarにを設置。
//...
import EditProfile from './components/profile-forms/EditProfile';
+ import Profiles from './components/profiles/Profiles';
//...
<Switch>
<Route exact path="/register" component={Register} />
<Route exact path="/login" component={Login} />
+ <Route exact path="/profiles" component={Profiles} />
//...
//...
const Navbar = ({ auth: { isAuthenticated, loading }, logout }) => {
const authLinks = (
<ul>
+ <li>
+ <Link to="/profiles">Users</Link>
+ </li>
<li>
<Link to="/dashboard">
<i className="fas fa-user" />
<span className="hide-sm">Dashboard</span>
</Link>
//...
const guestLinks = (
<ul>
+ <li>
+ <Link to="/profiles">Users</Link>
+ </li>
<li>
<Link to="/register">Register</Link>
</li>
//...
③ Profilesコンポーネント(Profilesページ)
import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import ProfileItem from './ProfileItem';
import { getProfiles } from '../../actions/profile';
const Profiles = ({ getProfiles, profile: { profiles, loading } }) => {
useEffect(() => {
getProfiles();
}, [getProfiles]);
return (
<Fragment>
{loading ? (
<Spinner />
) : (
<Fragment>
<h1 className="large text-primary">Developers</h1>
<p className="lead">
<i className="fab fa-connectdevelop" /> Browse and connect with
developers
</p>
<div className="profiles">
{profiles.length > 0 ? (
profiles.map((profile) => (
<ProfileItem key={profile._id} profile={profile} />
))
) : (
<h4>No profiles found...</h4>
)}
</div>
</Fragment>
)}
</Fragment>
);
};
Profiles.propTypes = {
getProfiles: PropTypes.func.isRequired,
profile: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
profile: state.profile,
});
export default connect(mapStateToProps, { getProfiles })(Profiles);
④ ProfileItemコンポーネント (Profilesの一部)
import React, { Fragment } from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
const ProfileItem = ({
profile: {
user: { _id, name, avatar },
status,
skills,
},
}) => {
return (
<Fragment>
<div className="profile bg-light">
<img src={avatar} alt="" className="round-img" />
<div>
<h2>{name}</h2>
<ul>
{status.slice(0, 2).map((status, index) => (
<li key={index}>{status}</li>
))}
</ul>
<Link to={`/profile/${_id}`} className="btn btn-primary">
View Profile
</Link>
</div>
<ul>
{skills.slice(0, 4).map((skill, index) => (
<li key={index} className="text-primary">
<i className="fas fa-check" /> {skill}
</li>
))}
</ul>
</div>
</Fragment>
);
};
ProfileItem.propTypes = {
profile: PropTypes.object.isRequired,
};
export default ProfileItem;
3. プロフィールページ
① profileフォルダ生成 -> Profileコンポーネント生成。
② Profileページのルートを設置。
//...
import Profiles from './components/profiles/Profiles';
import Profile from './components/profile/Profile';
//...
<Route exact path="/profiles" component={Profiles} />
+ <Route exact path="/profile/:id" component={Profile} />
//...
③ Profileコンポーネント(Profileページ)
match: IDとマッチしてるか検証
import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import ProfileTop from './ProfileTop';
import ProfileAbout from './ProfileAbout';
import { getProfileById } from '../../actions/profile';
const Profile = ({
getProfileById,
profile: { profile, loading },
auth,
match,
}) => {
useEffect(() => {
getProfileById(match.params.id);
}, [getProfileById, match.params.id]);
return (
<Fragment>
{profile === null || loading ? (
<Spinner />
) : (
<Fragment>
<Link to="/profiles" className="btn btn-light">
Back To Profiles
</Link>
{auth.isAuthenticated &&
auth.loading === false &&
auth.user._id === profile.user._id && (
<Link to="/edit-profile" className="btn btn-dark">
Edit Profile
</Link>
)}
<div class="profile-grid my-1">
<ProfileTop profile={profile} />
<ProfileAbout profile={profile} />
</div>
</Fragment>
)}
</Fragment>
);
};
Profile.propTypes = {
getProfileById: PropTypes.func.isRequired,
profile: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
profile: state.profile,
auth: state.auth,
});
export default connect(mapStateToProps, { getProfileById })(Profile);
④ ProfileTopコンポーネント(Profileページの一部)
import React from 'react';
import PropTypes from 'prop-types';
const ProfileTop = ({
profile: {
status,
website,
social,
user: { name, avatar },
},
}) => {
return (
<div class="profile-top bg-primary p-2">
<img class="round-img my-1" src={avatar} alt="" />
<h1 class="large">{name}</h1>
<p class="lead">{status}</p>
<div class="icons my-1">
{website && (
<a href={website} target="_blank" rel="noopener noreferrer">
<i class="fas fa-globe fa-2x" />
</a>
)}
{social && social.twitter && (
<a href={social.twitter} target="_blank" rel="noopener noreferrer">
<i class="fab fa-twitter fa-2x"></i>
</a>
)}
{social && social.facebook && (
<a href={social.facebook} target="_blank" rel="noopener noreferrer">
<i class="fab fa-facebook fa-2x"></i>
</a>
)}
{social && social.linkedin && (
<a href={social.linkedin} target="_blank" rel="noopener noreferrer">
<i class="fab fa-linkedin fa-2x"></i>
</a>
)}
{social && social.youtube && (
<a href={social.youtube} target="_blank" rel="noopener noreferrer">
<i class="fab fa-youtube fa-2x"></i>
</a>
)}
</div>
</div>
);
};
ProfileTop.propTypes = {
profile: PropTypes.object.isRequired,
};
export default ProfileTop;
⑤ ProfileAboutコンポーネント(Profileページの一部)
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
const ProfileAbout = ({
profile: {
bio,
skills,
user: { name },
},
}) => {
return (
<div class="profile-about bg-light p-2">
{bio && (
<Fragment>
<h2 class="text-primary">{name}'s Bio</h2>
<p>{bio}</p>
<div class="line"></div>
</Fragment>
)}
<h2 class="text-primary">Skill Set</h2>
<div class="skills">
{skills.map((skill, index) => (
<div key={index} className="p-1">
<i className="fas fa-check" /> {skill}
</div>
))}
</div>
</div>
);
};
ProfileAbout.propTypes = {
profile: PropTypes.object.isRequired,
};
export default ProfileAbout;