bootstrap
사용하기설치
$ npm install react-bootstrap bootstrap
index.js에 import 추가
import "bootstrap/dist/css/bootstrap.min.css";
사용하고자하는 파일에 import 해주고 사용하면 된다!!
import Dropdown from "react-bootstrap/Dropdown";
import Image from "react-bootstrap/Image";
...
<Image
src="holder.js/171x180"
style={{ width: "30px", height: "30px", marginTop: "3px" }}
roundedCircle
/>
<Dropdown>
<Dropdown.Toggle id="dropdown-basic">user name</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item href="#/action-1">프로필 사진 변경</Dropdown.Item>
<Dropdown.Item href="#/action-2">로그아웃</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
...
import { IoIosChatboxes } from "react-icons/io";
...
<h3 style={{ color: "white", display: "flex", alignItems: "center" }}>
<IoIosChatboxes />
<div style={{ marginLeft: "5px" }}> Chat App</div>
</h3>
useSelector
로 리덕스 store에 저장된 currentUser를 가져와서
user가 있다면 user에 저장된 photoURL로 이미지 src를 설정해준다.
user에 있는 displayName으로 프로필 이름도 설정해준다.
import { useSelector } from "react-redux";
const UserPanel = () => {
const user = useSelector((state) => state.user.currentUser);
return (
...
<Image
src={user && user.photoURL}
...
/>
<Dropdown>
...
{user && user.displayName}
...
실행 화면
1. firebase에서 로그아웃하기
firebase에서 로그아웃 하려면
.auth에 접근해서 signOut 메소드를 사용해서 로그아웃 해주면 된다!
import firebase from "../../../firebase";
const UserPanel = () => {
const handleLogout = () => {
firebase.auth().signOut();
};
...
<Dropdown.Item onClick={handleLogout}>로그아웃</Dropdown.Item>
2. 리덕스 스토어에서 user 지워주기
App.js에서 dispatch(clearUser())
import { setUser, clearUser } from "./redux/actions/user_action";
...
function App() {
useEffect(() => {
firebase.auth().onAuthStateChanged((user) => {
//user가 있으면 로그인이 된 상태
if (user) {
history.push("/");
dispatch(setUser(user));
} else {
history.push("/login");
>>>> dispatch(clearUser());
}
});
}, []);
...
import { SET_USER, CLEAR_USER } from "./types";
...
export function clearUser() {
return {
type: CLEAR_USER,
};
}
types.js에 추가
export const CLEAR_USER = "clear_user";
currentUser를 null
로 바꾸준다!
import { SET_USER, CLEAR_USER } from "../actions/types";
...
case CLEAR_USER:
return {
...state,
currentUser: null,
isLoading: false,
};
...
Firebase Storage 서비스에 파일을 넣어준다.
파일에 대한 정보는 Firebase DB에 넣어준다.
type이 file인 input
창을 display: 'none'
으로 없애주고 ref를 이용해서 '프로필 사진 변경'을 눌렀을 때 눌리게 해주자!
<Dropdown.Item onClick={handleOpenImageRef}>
프로필 사진 변경
</Dropdown.Item>
...
<input
onChange={handleUploadImage}
accept="image/jpeg, image/png"
style={{ display: "none" }}
ref={inputOpenImageRef}
type="file"
/
useRef를 사용해서 원하는 dom에 접근한다.
const inputOpenImageRef = useRef();
const handleOpenImageRef = () => {
inputOpenImageRef.current.click();
};
uploadTaskSnapshot = await firebase.storage().ref()
.child(`user_image/${user.uid}`)
.put(file, metadata)
업로드할 file의 데이터를 첫번째 인자에 넣어주고, 두번째 인자에는 metadata를 넣어줘야한다.
metadata는 {contentType: 'image/png'}
file을 살펴보면 type에 image/png가 적혀있음 이 type을 metadata에 넣어주어도 되고, mime-types
를 이용해서 metadata에 넣어 줘도 된다.
설치
$ npm install mime-types --save
handleUploadImage 함수 작성하기
const handleUploadImage = async (event) => {
const file = event.target.files[0];
const metadata = { contentType: mime.lookup(file.name) };
//스토리지에 파일 저장하기
//user_image 폴더 안에다가 저장이 됨
try {
let uploadTaskSnapshot = await firebase
.storage()
.ref()
.child(`user_image/${user.uid}`)
.put(file, metadata);
} catch (error) {}
};
스토리지에 이미지가 저장된 화면
Firebase 스토리지에 저장시킨 사진 url을 받아와서
1. currentUser.updateProfile
의 photoURL 업데이트시켜준다.
그리고 dispatch
를 사용해서 리덕스 스토어에 저장된 photoURL도 변경시켜줘서 화면에 출력되는 이미지를 변경시킨다.
마지막으로 데이터베이스에 저장되어있는 image를 변경시켜 준다.
import { setPhotoURL } from "../../../redux/actions/user_action";
const UserPanel = () => {
...
const dispatch = useDispatch();
...
const handleUploadImage = async (event) => {
...
let downloadURL = await uploadTaskSnapshot.ref.getDownloadURL();
//프로필 이미지 수정
await firebase.auth().currentUser.updateProfile({
photoURL: downloadURL,
});
dispatch(setPhotoURL(downloadURL));
//데이터베이스 유저 이미지 수정
await firebase
.database()
.ref("users")
.child(user.uid)
.update({ image: downloadURL });
} catch (error) {
alert(error);
}
};
import { SET_USER, CLEAR_USER, SET_PHOTO_URL } from "./types";
export function setPhotoURL(photoURL) {
return {
type: SET_PHOTO_URL,
payload: photoURL,
};
}
redux actions types.js 코드
SET_PHOTO_URL
추가
//USER TYPES
export const SET_USER = "set_user";
export const CLEAR_USER = "clear_user";
export const SET_PHOTO_URL = "set_photo_url";
currentUser에서 모든 state는 그대로 두고 photoURL만 변경!
case SET_PHOTO_URL:
return {
...state,
currentUser: { ...state.currentUser, photoURL: action.payload },
isLoading: false,
};
데이터베이스에서 image가 gravatar에서 firebasestorage..로 변경된것을 확인 할 수 있다.
바뀐 프로필 사진
(chatRoom.js
는 파이어베이스에서 데이터 실시간으로 받아올때 함수형 컴포넌트로 할때 오류가 나서 class 컴포넌트로 했다고 함)
import { FaRegSmileWink } from "react-icons/fa";
import { FaPlus } from "react-icons/fa";
import Modal from "react-bootstrap/Modal";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
class ChatRoom extends Component {
...
<div>
<div
style={{
position: "relative",
width: "100%",
display: "flex",
alignItems: "center",
}}
>
<FaRegSmileWink style={{ marginRight: 3 }} />
CHAT ROOMS (1)
<FaPlus
style={{ position: "absolute", right: 0, cursor: "pointer" }}
onClick={this.handleShow}
/>
</div>
{/* ADD CHAT ROOM MODAL */}
<Modal show={this.state.show} onHide={this.handleClose}>
<Modal.Header closeButton>
<Modal.Title>Create a chat room</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={this.handleSubmit}>
<Form.Group controlId="formBasicEmail">
<Form.Label>방 이름</Form.Label>
<Form.Control
onChange={(e) => this.setState({ name: e.target.value })}
type="text"
placeholder="Enter a chat room name"
/>
</Form.Group>
<Form.Group controlId="formBasicPassword">
<Form.Label>방 설명</Form.Label>
<Form.Control
onChange={(e) =>
this.setState({ description: e.target.value })
}
type="text"
placeholder="Enter a chat room description"
/>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.handleClose}>
Close
</Button>
<Button variant="primary" onClick={this.handleSubmit}>
Create
</Button>
</Modal.Footer>
</Modal>
</div>
);
}
chatRoomsRef
를 이용해 firebase database chatRooms 테이블에 접근한다.
isFormValid
로 이름과 설명이 있는지만 유효성 검사를 해준다.
addChatRoom
함수에서는 chatRooms 테이블에 자동으로 생성시킨 키를 만들어주고,
newChatRoom에 현재 상태의 name과 description을 넣어주고,
리덕스에서 user를 받아와 user 정보도 넣어준다.
Firebase에 chatRooms 테이블에 key를 넣어주고
거기에 채팅방 정보를 넣어준다.
chatRoomsRef.child(key).update(newChatRoom);
class ChatRoom extends Component {
state = {
show: false,
name: "",
description: "",
//firebase database chatRooms table에 접근
chatRoomsRef: firebase.database().ref("chatRooms"),
};
handleClose = () => this.setState({ show: false });
handleShow = () => this.setState({ show: true });
handleSubmit = (e) => {
e.preventDefault();
const { name, description } = this.state;
if (this.isFormValid(name, description)) {
this.addChatRoom();
}
};
isFormValid = (name, description) => {
return name && description;
};
addChatRoom = async () => {
//자동으로 생성된 키 넣어주기
const key = this.state.chatRoomsRef.push().key;
const { name, description } = this.state;
const { user } = this.props;
const newChatRoom = {
id: key,
name: name,
description: description,
createdBy: {
name: user.displayName,
image: user.photoURL,
},
};
try {
await this.state.chatRoomsRef.child(key).update(newChatRoom);
this.setState({
name: "",
description: "",
show: false,
});
} catch (error) {
alert(error);
}
};
클래스형 컴포넌트 이므로 리덕스에서 user를 받아올때 hook을 사용하지 못하고, connect
를 사용한다.
mapStateToProps
를 사용해 state에 들어있는 정보를 여기서 props로 바꿔서 사용해준다.
import { connect } from "react-redux";
...
//state에 들어있는거를 여기서 props로 바꿔서 사용하겠다
const mapStateToProps = (state) => {
return { user: state.user.currentUser };
};
export default connect(mapStateToProps)(ChatRoom);
파이어베이스에 추가된것을 볼 수 있다.
Firebase에서 데이터가 추가가 되면 DataSnapshot에 저장이 된다.
추가된 채팅 룸 목록들 보여주기
화면에 보여주기 위해서 state
에 chatRooms 배열
을 만들어준다.
AddChatRoomsListeners
에서 chatRoomsRef에 child가 추가될때마다 DataSnapshot에 들어가게 된다.renderChatRooms
에서 chatRooms안에 채팅 룸이 하나 이상일 때 룸의 이름을 반환해준다.class ChatRoom extends Component {
state = {
show: false,
name: "",
description: "",
//firebase database chatRooms table에 접근
chatRoomsRef: firebase.database().ref("chatRooms"),
>>> chatRooms: [],
};
componentDidMount() {
this.AddChatRoomsListeners();
}
AddChatRoomsListeners = () => {
let chatRoomsArray = [];
//listener를 통해서 생성된 데이터가 바로 array에 들어감
this.state.chatRoomsRef.on("child_added", (DataSnapshot) => {
chatRoomsArray.push(DataSnapshot.val())
this.setState({ chatRooms: chatRoomsArray });
});
};
renderChatRooms = (chatRooms) =>
chatRooms.length > 0 &&
chatRooms.map((room) => <li key={room.id}># {room.name}</li>);
...
render() {
return (
<div>
...
<ul style={{ listStyleType: "none", padding: 0 }}>
{this.renderChatRooms(this.state.chatRooms)}
</ul>
...
changeChatRoom = (room) => {
//리덕스에 지금 선택한 채팅룸 전달
>>> this.props.dispatch(setCurrentChatRoom(room));
//active 된 채팅룸 id 배경색 변경하기 위해 설정
this.setState({ activeChatRoomId: room.id });
};
...
renderChatRooms = (chatRooms) =>
chatRooms.length > 0 &&
chatRooms.map((room) => (
<li
key={room.id}
style={{
cursor: "pointer",
backgroundColor:
room.id === this.state.activeChatRoomId && "#ffffff45",
}}
>>> onClick={() => this.changeChatRoom(room)}
>
# {room.name}
</li>
));
//CHAT ROOM TYPES
export const SET_CURRENT_CHAT_ROOM = "set_current_chat_room";
import { SET_CURRENT_CHAT_ROOM } from "./types";
export function setCurrentChatRoom(currentChatRoom) {
return {
type: SET_CURRENT_CHAT_ROOM,
payload: currentChatRoom,
};
}
import { SET_CURRENT_CHAT_ROOM } from "../actions/types";
const initialChatRoomState = {
currentChatRoom: null,
};
export default function (state = initialChatRoomState, action) {
switch (action.type) {
case SET_CURRENT_CHAT_ROOM:
return {
...state,
currentChatRoom: action.payload,
};
default:
return state;
}
}
componentWillUnmount() {
//child_added listener off 해주는 것
this.state.chatRooms.off();
}