Twitter-Clone (12~13)

70482 단어 useRefformDataformData

➰Create Profile & Image Upload

1. 📝Create 📁web ➡ 📁src ➡📁component➡📃CreateProfile.tsx

import { gql, useMutation } from '@apollo/client';
import React, { useState } from 'react';
import { ME_QUERY } from '../pages/Profile';
import { ErrorMessage, Field, Form, Formik } from 'formik';
import Modal from 'react-modal';
import CustomStyles from '../styles/CustomModalStyles';

const CREATE_PROFILE_MUTATION = gql`
    mutation createProfile (
        $bio: String
        $location: String
        $website: String
        $avatar: String
    ){
        createProfile(
            bio : $bio
            location : $location
            website : $website
            avatar : $avatar
        ){
            id
        }
    }
`
interface ProfileValue {
        bio: string
        location: string
        website: string
        avatar: string
}

const CreateProfile = () => {
    const [createProfile] = useMutation(CREATE_PROFILE_MUTATION, {
        🚗 refetchQueries: [{query: ME_QUERY}]
    }) // second argument is refetch queries
    
    const [modalIsOpen, setIsOpen] = useState(false);

    const initialValues: ProfileValue = {
        bio: "",
        location: "",
        website: "",
        avatar: ""
    }

    const openModal = () => {
        setIsOpen(true)
    }
    const closeModal = () => {
        setIsOpen(false)
    }
    return (
        <div>
            <button onClick={openModal} >Create Profile</button>
            🚓 <Modal
                isOpen={modalIsOpen}
                onRequestClose={closeModal}
                contentLabel="Modal"
                style={CustomStyles}
            >
            <Formik
                    initialValues={initialValues}
                    // validationSchema={validationSchema}
                    onSubmit={async (value, { setSubmitting }) => {
                        setSubmitting(true)
                        await createProfile({
                            variables: value
                        })
                        
                        setSubmitting(false)
                        setIsOpen(false)
                    }}
                >
                    <Form>
                        <Field name="bio" type="text" as="textarea" placeholder="Bio" />
                        <ErrorMessage name="bio" component={'div'} />
                        <Field name="location" type="location" placeholder="Location" />
                        <ErrorMessage name="location" component={'div'} />
                        <Field name="website" type="website" placeholder="Website" />
                        <ErrorMessage name="website" component={'div'} />
                    
                        <button type="submit" className="login-button" >
                            <span>Create Profile</span>
                        </button>
                    </Form>

                </Formik>
                </Modal>
        </div>
    );
}

export default CreateProfile

📋Memo(CreateProfile.tsx)

🚗 refetchQueries: [{query: ME_QUERY}
1. Refetch queries
🚓 <Modal isOpen={modalIsOpen} ... style={CustomStyles} >
1. Open window called Modal

2. 📝Create 📁web ➡ 📁src ➡📁component➡📃UpdateProfile.tsx

import { gql, useMutation, useQuery } from '@apollo/client';
import React, { MutableRefObject, useRef, useState } from 'react';
import { ME_QUERY } from '../pages/Profile';
import { ErrorMessage, Field, Form, Formik } from 'formik';
import Modal from 'react-modal';
import CustomStyles from '../styles/CustomModalStyles';
import '../styles/UpdateProfile.css'

const UPDATE_PROFILE_MUTATION = gql`
    mutation updateProfile (
        $id: Int!
        $bio: String
        $location: String
        $website: String
        $avatar: String
    ){
        updateProfile(
            id : $id
            bio : $bio
            location : $location
            website : $website
            avatar : $avatar
        ){
            id
        }
    }
`
interface ProfileValue {
        id : number
        bio: string
        location: string
        website: string
        avatar: string
}

const UpdateProfile = () => {
    🚗 const inputFile = useRef() as MutableRefObject<HTMLInputElement> 
    const [image, setImage] = useState("")
    const [imageLoading, setImageLoading] = useState(false)
    const { loading, error, data } = useQuery(ME_QUERY)
    const [updateProfile] = useMutation(UPDATE_PROFILE_MUTATION, {
        refetchQueries: [{query: ME_QUERY}]
    }) // second argument is refetch queries
    
    const [modalIsOpen, setIsOpen] = useState(false);

    if(loading) return <p>Loading...</p>
    if (error) return <p>{error.message}</p>

    const initialValues: ProfileValue = {
        id: data.me.Profile.id,
        bio: data.me.Profile.bio,
        location: data.me.Profile.location,
        website: data.me.Profile.website,
        avatar:data.me.Profile.avatar
    }

    const openModal = () => {
        setIsOpen(true)
    }
    const closeModal = () => {
        setIsOpen(false)
    }

    const uploadImage = async (e: any) => {
     
        🚓 const files = e.target.files
           const data = new FormData()
           data.append('file', files[0])
           data.append("upload_preset", "Coaspe")
           setImageLoading(true)
            const res = await fetch("API_ADDRESS", {
                method: "POST",
                body: data
            })
        const file = await res.json()
        
        setImage(file.secure_url)
        setImageLoading(false)
      
    }
    return (
        <div>
            <button onClick={openModal} className="edit-button">Edit Profile</button>
            <Modal
                isOpen={modalIsOpen}
                onRequestClose={closeModal}
                contentLabel="Modal"
                style={CustomStyles}
                ariaHideApp={false}
            >
                <input
                    type="file"
                    name="file"
                    placeholder="upload file"
                    onChange={uploadImage}
                    ref={inputFile}
                    style={{display:"none"}}
                />
                {/* velog 정리 */}
                {imageLoading ? (
                    <h3>Loading...</h3>
                ) : (
                        <>
                            {data.me.Profile.avatar ? (
                                // span 클릭하면 위에 input을 클릭한 것과 같다
                               🚕 <span onClick={() => inputFile.current.click()}> 
                                    <img className="profile_img"
                                    src={data.me.Profile.avatar}
                                    style={{width:"150px", borderRadius: "30%"}}
                                        alt="avatar"
                                    />
                                </span>
                            ) : (
                                <span onClick={() => inputFile.current.click()}>
                                    <i className="fa fa-user fa-5x" aria-hidden="true"></i>
                                </span>
                                )
                            }
                        </>
                )}
            <Formik
                    initialValues={initialValues}
                    // validationSchema={validationSchema}
                    onSubmit={async (value, { setSubmitting }) => {
                        setSubmitting(true)
                        await updateProfile({
                            variables: {...value, avatar: image}
                        })
                        
                        setSubmitting(false)
                        setIsOpen(false)
                    }}
                >
                    <Form>
                        <Field name="bio" type="text" as="textarea" placeholder="Bio" />
                        <ErrorMessage name="bio" component={'div'} />
                        <Field name="location" type="location" placeholder="Location" />
                        <ErrorMessage name="location" component={'div'} />
                        <Field name="website" type="website" placeholder="Website" />
                        <ErrorMessage name="website" component={'div'} />
                    
                        <button type="submit" className="login-button" >
                            <span>Update Profile</span>
                        </button>
                    </Form>

                </Formik>
                </Modal>
        </div>
    );
}

export default UpdateProfile

📋Memo(UpdateProfile.tsx)

🚗 const inputFile = useRef() as MutableRefObject<HTMLInputElement>
💥 First i did const inputFile = useRef(null) but it has type issue

🚓 const files = e.target.files

🚕 <span onClick={() => inputFile.current.click()}>
1. Use useRef()

3. 📝Create 📁web ➡ 📁src ➡📁pages➡📃Profile.tsx

import { gql, useQuery } from '@apollo/client';
import { Link, useHistory } from 'react-router-dom';
import CreateProfile from '../components/CreateProfile';
import UpdateProfile from '../components/UpdateProfile';
import "../styles/profile.css"
import "../styles/primary.css"
import '../styles/UpdateProfile.css'
import LeftNav from '../components/LeftNav';

export const ME_QUERY = gql`
    query me {
        me {
            id
            name
            Profile {
                id
                bio
                location
                website
                avatar
            }
        }
    }
`

const Profile = () => {
    // npm install react-modal
    // npm install -D @types/react-modal
    const history = useHistory();
    const { loading, error, data } = useQuery(ME_QUERY);
    if (loading) {
        return <p>Loading...</p>
    }
    if (error) {
        return <p>{error.message}</p>
    }
    return (
        <>
        <div className="primary">
                <div className="left"><LeftNav /></div>
                <div className="profile">
                    <div className="profile-info">
                        <div className="profile-head">
                            <span className="back-arrow" onClick={() => history.goBack()}>
                                <i className="fa fa-arrow-left" aria-hidden="true"></i>
                            </span>
                            <span className="nickname">
                                <h3>{data.me.name}</h3>
                            </span>
                        </div>
                        <div className="avatar">
                            {data.me.Profile.avatar ? (
                                <img className="profile_img"
                                src={data.me.Profile.avatar}
                                style={{width:"150px", borderRadius: "30%"}}
                                    alt="avatar"
                                />
                            ) : (<i className="fa fa-user fa-5x" aria-hidden="true"></i>) }
                        </div>
                        <div className="make-profile">
                            {data.me.Profile ? <UpdateProfile /> : <CreateProfile />}
                        </div>

                        <h3 className="name">{data.me.name}</h3>
                        {data.me.Profile ? 
                            <p>{data.me.Profile.bio}</p>
                            : null}
                        {data.me.Profile ? 
                            <p>{data.me.Profile.location}</p>
                         : null}
                        {data.me.Profile ? (
                            <p>
                                <i className="fas fa-link"> </i>{" "}
                                <Link
                                    to={{ pathname: `http://${data.me.Profile.website}` }}
                                    target="_blank"
                                >
                                    {data.me.Profile.website}
                                </Link>
                            </p>
                        ) : null}
                        <div className="followers">
                            <p>200 following</p>
                            <p>384 followers</p>
                        </div>
                    </div>
                </div>
                <div className="right">
                    Right
                </div>
        </div>
        </>
    );
}

export default Profile;

4. 📝Create 📁web ➡ 📁src ➡📁components➡📃LeftNav.tsx

import { Link } from 'react-router-dom'
import favicon from "../styles/assets/Twitter-Logo.png"
import "../styles/leftNav.css"

const LeftNav = () => {
    return (
        <div>
            <Link to='/users'>
                <img src={favicon} alt="logo" style={{width:'40px'}} />
            </Link>
            <Link to='/users'>
                <h2>
                    <i className="fa fa-home" aria-hidden="true"></i>{" "}
                    <span className="title">Home</span>
                </h2>
            </Link>
            <Link to='/profile'>
                <h2>
                    <i className="fa fa-user" aria-hidden="true"></i>{" "}
                    <span className="title">Profile</span>
                </h2>
            </Link>
            <Link to='/users'>
                <h2>
                    <i className="fa fa-envelope" aria-hidden="true"></i>{" "}
                    <span className="title">Messages</span>
                </h2>
            </Link>
            <Link to='/users'>
                <h2>
                    <i className="fa fa-bell" aria-hidden="true"></i>{" "}
                    <span className="title">Notification</span>
                </h2>
            </Link>
            <Link to='/users'>
                <h2>
                    <i className="fa fa-ellipsis-h" aria-hidden="true"></i>{" "}
                    <span className="title">More</span>
                </h2>
            </Link>
            <button style={{ marginRight: "10px", marginTop: "30px" }}>
                <span style={{padding : "15px 70px 15px 70px"}}>Tweet</span>
            </button>
        </div>
    );
}

export default LeftNav;

좋은 웹페이지 즐겨찾기