⚡️ UserCard: React.memo + <a> instead of <div>
This commit is contained in:
		| @@ -33,20 +33,20 @@ const FunctionCard = memo(forwardRef((props, ref) => { | ||||
|       } | ||||
|     > | ||||
|       {/* FunctionCard a une hauteur pendant chargement */} | ||||
|       <a ref={ref} style={isLoading ? { height: '360px', justifyContent: 'center' } : null} className='FunctionCard col-sm-24 col-md-10 col-xl-7'>         | ||||
|           {isLoading && <Loader width='125px' height='125px' />} | ||||
|       <a ref={ref} style={isLoading ? { height: '360px', justifyContent: 'center' } : null} className='FunctionCard col-sm-24 col-md-10 col-xl-7'> | ||||
|         {isLoading && <Loader width='125px' height='125px' />} | ||||
|  | ||||
|           <div className={`FunctionCard__container ${isLoading ? 'd-none' : ''}`}> | ||||
|             <div className='FunctionCard__top'> | ||||
|               <img onLoad={handleLoad} onError={handleError} className='FunctionCard__image' alt={props.title} src={API_URL + props.image} /> | ||||
|               <h2 className='FunctionCard__title'>{props.title}</h2> | ||||
|               <p className='FunctionCard__description text-center'>{props.description}</p> | ||||
|             </div> | ||||
|             <div className='FunctionCard__info'> | ||||
|               <p className='FunctionCard__category' style={{ backgroundColor: props.categorie.color }}>{props.categorie.name}</p> | ||||
|               <p className='FunctionCard__publication-date'>{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p> | ||||
|             </div> | ||||
|         <div className={`FunctionCard__container ${isLoading ? 'd-none' : ''}`}> | ||||
|           <div className='FunctionCard__top'> | ||||
|             <img onLoad={handleLoad} onError={handleError} className='FunctionCard__image' alt={props.title} src={API_URL + props.image} /> | ||||
|             <h2 className='FunctionCard__title'>{props.title}</h2> | ||||
|             <p className='FunctionCard__description text-center'>{props.description}</p> | ||||
|           </div> | ||||
|           <div className='FunctionCard__info'> | ||||
|             <p className='FunctionCard__category' style={{ backgroundColor: props.categorie.color }}>{props.categorie.name}</p> | ||||
|             <p className='FunctionCard__publication-date'>{date.format(new Date(props.createdAt), 'DD/MM/YYYY', true)}</p> | ||||
|           </div> | ||||
|         </div> | ||||
|       </a> | ||||
|     </Link> | ||||
|   ) | ||||
|   | ||||
							
								
								
									
										47
									
								
								website/components/UserCard/UserCard.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								website/components/UserCard/UserCard.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| .UserCard { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   position: relative; | ||||
|   flex-direction: column; | ||||
|   word-wrap: break-word; | ||||
|   box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25); | ||||
|   border: 1px solid black; | ||||
|   border-radius: 1rem; | ||||
|   margin: 0 0 50px 0; | ||||
|   cursor: pointer; | ||||
|   transition: all 0.3s; | ||||
|   padding: 10px; | ||||
| } | ||||
| .UserCard__container { | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   align-items: center; | ||||
|   color: var(--text-color); | ||||
|   text-decoration: none !important; | ||||
| } | ||||
| .UserCard:hover { | ||||
|   transform: translateY(-7px); | ||||
| } | ||||
| /* col-md */ | ||||
| @media (min-width: 768px) { | ||||
|   .UserCard { | ||||
|     margin: 0 30px 50px 30px; | ||||
|   } | ||||
| } | ||||
| /* col-xl */ | ||||
| @media (min-width: 1200px) { | ||||
|   .UserCard { | ||||
|     margin: 0 20px 50px 20px; | ||||
|   } | ||||
| } | ||||
| .UserCard__logo { | ||||
|   width: 150px; | ||||
|   height: 150px; | ||||
|   border-radius: 50%; | ||||
|   object-fit: cover; | ||||
| } | ||||
| .UserCard__name { | ||||
|   margin: 30px 0 10px 0; | ||||
| } | ||||
							
								
								
									
										25
									
								
								website/components/UserCard/UserCard.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								website/components/UserCard/UserCard.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import Link from 'next/link' | ||||
| import { forwardRef, memo } from 'react' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
| import './UserCard.css' | ||||
|  | ||||
| const UserCard = memo( | ||||
|   forwardRef((props, ref) => { | ||||
|     return ( | ||||
|       <div className='UserCard col-sm-24 col-md-10 col-xl-7' ref={ref}> | ||||
|         <Link href='/users/[name]' as={`/users/${props.name}`}> | ||||
|           <a className='UserCard__container'> | ||||
|             <img | ||||
|               className='UserCard__logo' | ||||
|               src={API_URL + props.logo} | ||||
|               alt={props.name} | ||||
|             /> | ||||
|             <h2 className='UserCard__name'>{props.name}</h2> | ||||
|           </a> | ||||
|         </Link> | ||||
|       </div> | ||||
|     ) | ||||
|   }) | ||||
| ) | ||||
|  | ||||
| export default UserCard | ||||
| @@ -1,74 +1,95 @@ | ||||
| import { useState, useEffect, useRef, useCallback } from 'react' | ||||
| import Link from 'next/link' | ||||
| import HeadTag from '../../components/HeadTag' | ||||
| import Loader from '../../components/Loader' | ||||
| import UserCard from '../../components/UserCard/UserCard' | ||||
| import api from '../../utils/api' | ||||
| import '../../public/css/pages/users.css' | ||||
| import { API_URL } from '../../utils/config/config' | ||||
|  | ||||
| const Users = () => { | ||||
|   let pageUsers = 1 | ||||
|  | ||||
|   const [inputSearch, setInputSearch] = useState('') | ||||
|   const [usersData, setUsersData] = useState({ totalItems: 0, hasMore: true, rows: [] }) | ||||
|   const [usersData, setUsersData] = useState({ | ||||
|     totalItems: 0, | ||||
|     hasMore: true, | ||||
|     rows: [] | ||||
|   }) | ||||
|   const [isLoadingUsers, setLoadingUsers] = useState(true) | ||||
|  | ||||
|   // Récupère les users si la recherche change | ||||
|   useEffect(() => { | ||||
|     pageUsers = 1 | ||||
|     getUsersData().then((data) => setUsersData(data)) | ||||
|     getUsersData().then(data => setUsersData(data)) | ||||
|   }, [inputSearch]) | ||||
|  | ||||
|   const getUsersData = async () => { | ||||
|     setLoadingUsers(true) | ||||
|     const { data } = await api.get(`/users?page=${pageUsers}&limit=15&search=${inputSearch}`) | ||||
|     const { data } = await api.get( | ||||
|       `/users?page=${pageUsers}&limit=15&search=${inputSearch}` | ||||
|     ) | ||||
|     setLoadingUsers(false) | ||||
|     return data | ||||
|   } | ||||
|  | ||||
|   const handleSearchChange = (event) => { | ||||
|   const handleSearchChange = event => { | ||||
|     setInputSearch(event.target.value) | ||||
|   } | ||||
|  | ||||
|   // Permet la pagination au scroll | ||||
|   const observer = useRef() | ||||
|   const lastUserCardRef = useCallback((node) => { | ||||
|     if (isLoadingUsers) return | ||||
|     if (observer.current) observer.current.disconnect() | ||||
|     observer.current = new window.IntersectionObserver((entries) => { | ||||
|       if (entries[0].isIntersecting && usersData.hasMore) { | ||||
|         pageUsers += 1 | ||||
|         getUsersData().then((data) => { | ||||
|           setUsersData((oldData) => { | ||||
|             return { | ||||
|               totalItems: data.totalItems, | ||||
|               hasMore: data.hasMore, | ||||
|               rows: [...oldData.rows, ...data.rows] | ||||
|             } | ||||
|           }) | ||||
|         }) | ||||
|       } | ||||
|     }, { threshold: 1 }) | ||||
|     if (node) observer.current.observe(node) | ||||
|   }, [isLoadingUsers, usersData.hasMore]) | ||||
|   const lastUserCardRef = useCallback( | ||||
|     node => { | ||||
|       if (isLoadingUsers) return | ||||
|       if (observer.current) observer.current.disconnect() | ||||
|       observer.current = new window.IntersectionObserver( | ||||
|         entries => { | ||||
|           if (entries[0].isIntersecting && usersData.hasMore) { | ||||
|             pageUsers += 1 | ||||
|             getUsersData().then(data => { | ||||
|               setUsersData(oldData => { | ||||
|                 return { | ||||
|                   totalItems: data.totalItems, | ||||
|                   hasMore: data.hasMore, | ||||
|                   rows: [...oldData.rows, ...data.rows] | ||||
|                 } | ||||
|               }) | ||||
|             }) | ||||
|           } | ||||
|         }, | ||||
|         { threshold: 1 } | ||||
|       ) | ||||
|       if (node) observer.current.observe(node) | ||||
|     }, | ||||
|     [isLoadingUsers, usersData.hasMore] | ||||
|   ) | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <HeadTag | ||||
|         title='Utilisateurs' | ||||
|         description='Liste des utilisateurs.' | ||||
|       /> | ||||
|       <HeadTag title='Utilisateurs' description='Liste des utilisateurs.' /> | ||||
|  | ||||
|       <div className='container text-center'> | ||||
|         <div className='row justify-content-center'> | ||||
|           <div className='col-24'> | ||||
|             <h1 style={{ marginBottom: 0, paddingTop: '20px' }}>Utilisateurs</h1> | ||||
|             <p style={{ marginTop: '5px' }}>La liste des utilisateurs - Total de {usersData.totalItems} utilisateurs :</p> | ||||
|             <h1 style={{ marginBottom: 0, paddingTop: '20px' }}> | ||||
|               Utilisateurs | ||||
|             </h1> | ||||
|             <p style={{ marginTop: '5px' }}> | ||||
|               La liste des utilisateurs - Total de {usersData.totalItems}{' '} | ||||
|               utilisateurs : | ||||
|             </p> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <div className='Users__search-container row justify-content-center'> | ||||
|           <input value={inputSearch} onChange={handleSearchChange} type='search' className='Users__form-control Users__search-input' name='search' id='search' placeholder='🔎 Rechercher...' /> | ||||
|           <input | ||||
|             value={inputSearch} | ||||
|             onChange={handleSearchChange} | ||||
|             type='search' | ||||
|             className='Users__form-control Users__search-input' | ||||
|             name='search' | ||||
|             id='search' | ||||
|             placeholder='🔎 Rechercher...' | ||||
|           /> | ||||
|         </div> | ||||
|  | ||||
|         <div className='row justify-content-center'> | ||||
| @@ -76,25 +97,11 @@ const Users = () => { | ||||
|             // Si c'est le dernier élément | ||||
|             if (usersData.rows.length === index + 1) { | ||||
|               return ( | ||||
|                 <div ref={lastUserCardRef} key={user.id} className='UserCard col-sm-24 col-md-10 col-xl-7'> | ||||
|                   <Link href='/users/[name]' as={`/users/${user.name}`}> | ||||
|                     <div className='UserCard__container'> | ||||
|                       <img className='UserCard__logo' src={API_URL + user.logo} alt={user.name} /> | ||||
|                       <h2 className='UserCard__name'>{user.name}</h2> | ||||
|                     </div> | ||||
|                   </Link> | ||||
|                 </div> | ||||
|                 <UserCard key={index} {...user} ref={lastUserCardRef} /> | ||||
|               ) | ||||
|             } | ||||
|             return ( | ||||
|               <div key={user.id} className='UserCard col-sm-24 col-md-10 col-xl-7'> | ||||
|                 <Link href='/users/[name]' as={`/users/${user.name}`}> | ||||
|                   <div className='UserCard__container'> | ||||
|                     <img className='UserCard__logo' src={API_URL + user.logo} alt={user.name} /> | ||||
|                     <h2 className='UserCard__name'>{user.name}</h2> | ||||
|                   </div> | ||||
|                 </Link> | ||||
|               </div> | ||||
|               <UserCard key={index} {...user} /> | ||||
|             ) | ||||
|           })} | ||||
|         </div> | ||||
|   | ||||
| @@ -1,65 +1,19 @@ | ||||
| .Users__form-control { | ||||
|     display: block; | ||||
|     height: calc(1.5em + .75rem + 2px); | ||||
|     padding: .375rem .75rem; | ||||
|     font-size: 1rem; | ||||
|     font-weight: 400; | ||||
|     line-height: 1.5; | ||||
|     color: #495057; | ||||
|     background-color: #fff; | ||||
|     background-clip: padding-box; | ||||
|     border: 1px solid #ced4da; | ||||
|     border-radius: .5em; | ||||
|   display: block; | ||||
|   height: calc(1.5em + 0.75rem + 2px); | ||||
|   padding: 0.375rem 0.75rem; | ||||
|   font-size: 1rem; | ||||
|   font-weight: 400; | ||||
|   line-height: 1.5; | ||||
|   color: #495057; | ||||
|   background-color: #fff; | ||||
|   background-clip: padding-box; | ||||
|   border: 1px solid #ced4da; | ||||
|   border-radius: 0.5em; | ||||
| } | ||||
| .Users__search-container { | ||||
|     margin-bottom: 50px; | ||||
|   margin-bottom: 50px; | ||||
| } | ||||
| .Users__search-input { | ||||
|     width: 50%; | ||||
|   width: 50%; | ||||
| } | ||||
|  | ||||
| .UserCard { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     position: relative; | ||||
|     flex-direction: column; | ||||
|     word-wrap: break-word; | ||||
|     box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, .25); | ||||
|     border: 1px solid black; | ||||
|     border-radius: 1rem; | ||||
|     margin: 0 0 50px 0; | ||||
|     cursor: pointer; | ||||
|     transition: all .3s; | ||||
|     padding: 10px; | ||||
| } | ||||
| .UserCard__container { | ||||
|     height: 100%; | ||||
|     width: 100%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     align-items: center; | ||||
| } | ||||
| .UserCard:hover { | ||||
|     transform: translateY(-7px); | ||||
| } | ||||
| /* col-md */ | ||||
| @media (min-width: 768px) { | ||||
|     .UserCard { | ||||
|         margin: 0 30px 50px 30px; | ||||
|     }  | ||||
| } | ||||
| /* col-xl */ | ||||
| @media (min-width: 1200px) { | ||||
|     .UserCard { | ||||
|         margin: 0 20px 50px 20px; | ||||
|     }    | ||||
| } | ||||
| .UserCard__logo { | ||||
|     width: 150px; | ||||
|     height: 150px; | ||||
|     border-radius: 50%; | ||||
|     object-fit: cover; | ||||
| } | ||||
| .UserCard__name { | ||||
|     margin: 30px 0 10px 0; | ||||
| } | ||||
		Reference in New Issue
	
	Block a user