npx create-next-app my-next-app
What is your project named? my-next-app
Would you like to use TypeScript? No
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? No
Would you like to use `src/` directory? Yes
Would you like to use App Router? (recommended) Yes
Would you like to customize the default import alias (@/*)? Yes
What import alias would you like configured? @/*
public/
src/
├── app/ (페이지 뎁스)
├── menu/
├── login/
├── .../
├── assets/ (스타일 관련)
├── fonts/
├── images/
├── styles/
├── api/ (=services : api관련)
├── components/ (컴포넌트)
├── constants/ (상수)
해결방법
1. pakage.json
"@mui/material-nextjs": "^5.15.11",
npm install --save-dev @mui/material-nextjs
app/layout.js
import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter";
<body>
<AppRouterCacheProvider options={{ enableCssLayer: true }}>
{children}
</AppRouterCacheProvider>
</body>
App Router 에서 jsx파일은 기본적으로 서버 컴포넌트로 만들어진다.
그래서 useState같은 클라이언트에서 동작해야하는 것들을 쓰려면
해당 컴포넌트를 클라이언트 컴포넌트로 명시해야한다."use client";
를 상단에 추가해줄것.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
1번방식
<Button className="btn bgc-primary" onClick={saveData}>
2번방식
<Button className="btn bgc-primary" onClick={() => { saveData() }}>
둘 다 기능은 같다. 클릭핸들러에 함수를 전달하는것.
다만 2번 방식은 saveData(param1,param2)와 같이 매개변수를 전달해야할 필요성이 있을때
익명함수를 생성하여 onClick핸들러에 전달한다.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
function handleGridPopOpen(rowData) {
// 데이터 통신으로 get
getPopDetailData(rowData.id);
// 1번
// if (popData) {
// setPopOpen(true);
// }
// 2번
setPopOpen(true);
// 3번
// setTimeout(() => {
// setPopOpen(true);
// }, 100);
}
부모페이지의 그리드에서 행(row)에 속해있는 버튼을 클릭하면
팝업이 열리고, 그 행(row)에 맞는 데이터를 부모에서 받아서 팝업으로 전달한다.
이 때 데이터를 먼저 받은 후에 팝업이 열려야하는데
당장 떠오르는 방법은 1,2,3번이었다.
하지만 셋 다 정답은 아니었다.
정답은 async await를 사용하여 비동기인 getPopDetailData가 완료가 된 후 실행하면 된다.
async function handleGridPopOpen(rowData) {
console.log(rowData);
await getPopDetailData(rowData.id);
setPopOpen(true);
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
src/app/menu/sub0/[detail]/page.jsx
위와 같이 동적으로 생성될 경로의 폴더명을 중괄호[]로 감싸서 생성한다.
page.jsx
import { useRouter, usePathname, useSearchParams } from "next/navigation";
export default function DetailPage() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
return {...}
}
위와 같이 적용해서 사용하면 된다.
이 때 주의할 점은 router.push(href: string, { scroll: boolean }) 방식으로 써야한다.
page router방식일 때처럼 router.push({pathname:'name'}) 이게 안된다.
고로 router.push(/menu/sub0/${params}
); 이런식으로 써야한단 말.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
const [apiData, setApiData] = useState(null);
const [loadStatus, setLoadStatus] = useState(false);
useEffect(() => {
getDetailData(params.detail);
}, []);
// api : get data
async function getDetailData(path) {
const _params = {
url: "/users/" + path,
};
const _data = await getDetail(_params);
setApiData(_data);
setLoadStatus(true);
console.log(apiData);
}
자. 위와같이 apiData에 저장된 데이터를 확인하려 콘솔로그를 찍었다.
하지만 콘솔엔 null밖에 표시가 안된다.
왜지? 분명 getDetail로 리턴되는 data값은 확인이 되고
setApiData로 그 값을 넣었는데?
그 이유는 useState, 정확하겐 setState가 비동기 함수이다.
값을 넣었다 하더라도 바로 적용이 안되고 돔이 렌더링된 이후에
데이터 변경값이 들어간다.
콘솔로 확인하는 부분에선 데이터가 안들어가있고, 돔이 그려지고 데이터도 렌더링되며 적용된다.
비동기를 동기화 하려면 useEffect를 사용하여 렌더링조건변수에 apiData를 추가해줘야한다는데 이 부분은 좀 더 해봐야 감이 올 것 같다.
useEffect(()=>{...},[apiData]);
이런식으로 말이다.