์๋ ํ์ธ์, ์ ๋ ๋ฐ์ํ๋ ๊ฒ์ด ์ฒ์์ด๊ณ ๊ตฌ์ฑ ์์์ ๋ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง๋ค๋ ค๊ณ ํ๋๋ฐ ๋ด๊ฐ ๋ง๋ค๊ณ ์๋ ๊ตฌ์ฑ ์์ ์ค ํ๋๊ฐ REACT HOOKS ๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
๋ฉด์ฑ ์กฐํญ: ๋ฌธ์ ๋ฅผ ๋ง๋๋ ๊ฒ์ ์ด๋ฒ์ด ์ฒ์์ด๋ฏ๋ก ์ํดํด ์ฃผ์ญ์์ค.
๊ทธ๋์ accordion__item--open
๋ฐ accordion__item
ํด๋์ค ์ฌ์ด๋ฅผ ํ ๊ธํ์ฌ ์ด๊ณ ๋ซ๋ ์์ฝ๋์ธ ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋ค๋ ค๊ณ ํฉ๋๋ค.
ํจํค์ง.json
{
"name": "react-lib",
"version": "0.3.0",
"description": "A simple UI library of react components",
"main": "dist/index.js",
"publishConfig": {
"registry": ""
},
"scripts": {
"login": "",
"build": "webpack --mode=production",
"develop": "webpack --mode=development --watch"
},
"repository": {
"type": "git",
"url": ""
},
"keywords": [],
"author": "",
"license": "ISC",
"homepage": "",
"dependencies": {
"@babel/core": "^7.3.4",
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.5",
"eslint": "^5.15.1",
"eslint-loader": "^2.1.2",
"webpack": "^4.29.6",
"webpack-cli": "^3.2.3",
"webpack-node-externals": "^1.7.2"
},
"devDependencies": {
"eslint-config-airbnb": "^17.1.0",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-react": "^7.12.4",
"eslint-plugin-react-hooks": "^1.6.0"
}
}
์นํฉ.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode: 'development',
optimization: {
minimize: true,
},
entry: './index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index.js',
library: '',
libraryTarget: 'commonjs'
},
target: 'node',
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/react']
}
}
]
}
};
์ด๊ฒ์ ์์ฝ๋์ธ ์ปจํ ์ด๋์ ๋๋ค.
```
'๋ฐ์'์์ React ๊ฐ์ ธ์ค๊ธฐ;
๊ธฐ๋ฅ ์์ฝ๋์ธ({์์ }) {
๋ฐํ (
}
๊ธฐ๋ณธ ์์ฝ๋์ธ ๋ด๋ณด๋ด๊ธฐ;
**This is the accordion item that will live inside the container:**
'๋ฐ์'์์ React, { useState } ๊ฐ์ ธ์ค๊ธฐ;
๊ธฐ๋ฅ AccordionItem({ํค๋, ์ฝํ ์ธ }) {
const [ isActive, toggleActive ] = useState(๊ฑฐ์ง);
๋ฐํ (
<button
className="accordion__item-header"
onClick={ () => isActive ? toggleActive(false) : toggleActive(true) }
>
{ header }
<svg className="icon icon--debit" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
<path className="icon__path" d="M22.37,22.33V2.67a2.63,2.63,0,0,1,5.26,0V47.24a2.63,2.63,0,0,1-5.26.09V27.58H2.71a2.63,2.63,0,0,1,0-5.25Zm11.92,5.25a2.63,2.63,0,0,1,0-5.25h13a2.63,2.63,0,0,1,0,5.25Z">
</path>
</svg>
</button>
<div className="accordion__item-content">
{ children }
</div>
)
};
๊ธฐ๋ณธ AccordionItem ๋ด๋ณด๋ด๊ธฐ;
Now inside of a [create-react-app](https://github.com/sethandleah/myapp) I import these components
My [library](https://github.com/sethandleah/react-lib) and the [create-react-app](https://github.com/sethandleah/myapp) are relative to each other and I am using ```npm link```
'๋ฐ์'์์ React ๊ฐ์ ธ์ค๊ธฐ;
'react-lib'์์ {AccordionItem} ๊ฐ์ ธ์ค๊ธฐ
'react-lib'์์ {Accordion} ๊ฐ์ ธ์ค๊ธฐ;
ํจ์ ์ฑ({props}) {
๋ฐํ (
<Accordion>
<AccordionItem header={"Hello"} content={"World"}/>
</Accordion>
)
}
๊ธฐ๋ณธ ์ฑ ๋ด๋ณด๋ด๊ธฐ;
I have followed all of these [instructions](https://reactjs.org/warnings/invalid-hook-call-warning.html) and I still get the same error.
**Current behavior?**
์๋ชป๋ ํํฌ ํธ์ถ์ ๋๋ค. ํํฌ๋ ํจ์ ๊ตฌ์ฑ ์์์ ๋ณธ๋ฌธ ๋ด๋ถ์์๋ง ํธ์ถํ ์ ์์ต๋๋ค. ๋ค์ ์ด์ ์ค ํ๋๋ก ์ธํด ๋ฐ์ํ ์ ์์ต๋๋ค.
**Steps to reproduce**
- clone [https://github.com/sethandleah/react-lib](https://github.com/sethandleah/react-lib)
- clone [https://github.com/sethandleah/myapp](https://github.com/sethandleah/myapp)
- ```cd react-lib```
- ```npm install```
- ```npm link```
- ```cd ../myapp```
- ```npm i```
- ```npm link react-lib```
- ```npm start```
**Expected behavior**
- It should show a button with a "plus" svg sign and the words "Hello" and "World" respectively
- Open devtools and go to elements
- When clicking on the button the class ```accordion_item--open``` should toggle
**To see the above, do the following:**
- Uncomment these lines at ```myapp/src/App.js```
'./Accordion'์์ ์์ฝ๋์ธ ๊ฐ์ ธ์ค๊ธฐ;
'./AccordionItem'์์ AccordionItem ๊ฐ์ ธ์ค๊ธฐ;
- The comment out these line, alse at ```myapp/src/App.js```:
'react-lib'์์ { ์์ฝ๋์ธ } ๊ฐ์ ธ์ค๊ธฐ;
'react-lib'์์ { AccordionItem } ๊ฐ์ ธ์ค๊ธฐ;
```
React, Browser/OS ๋ฒ์ ์ ์ด ๋ฌธ์ ์ ์ํฅ์ ๋ฐ์ต๋๋ค.
^16.8.6
์
๋๋ค.Version 0.61.52 Chromium: 73.0.3683.86 (Official Build) (64-bit)
Version 10.13.6 (17G5019)
https://github.com/facebook/react/issues/13991 ์ ์ฌ๋๋ค๊ณผ ๋์ผํ ๋ฌธ์ ์ ์ง๋ฉดํ๊ณ ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ๋ฌธ์ ์ ๋งจ ์๋์ ์ ๊ณต๋ ํด๊ฒฐ ๋ฐฉ๋ฒ ์ค ์ผ๋ถ๋ฅผ ์ดํด๋ณด๊ณ ๋ฌธ์ ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. ๋น์ ์ ์ํด ์ผ?
์ด๊ฒ์ HOC ๋ด๋ถ์์ ํํฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ฐ์ํฉ๋๋ค.
const HOC = Component => {
return (props) => {
const [val] = useState();
return <div>{val}</div>
}
}
@revskill10 ์ฝ๋์๋ ๋ฐ์ค ์ฌํ์ผ๋ก ๋ณ๋์ ์ด์๋ฅผ ๋ง๋ค์ด์ฃผ์ธ์.
https://www.npmjs.com/package/webpack-bundle-analyzer ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค
๋ ๊ฐ์ ๋ฐ์ ๋น๋๋ฅผ ์ถ๊ฐํ๊ณ ์ด ๋ฌธ์ ๋ฅผ ์ผ์ผํค๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ฒฝ์ฐ ๋ฐ์ ๋ฐ ๋ฐ์ ์์ญ์ ์์ ์์ฉ ํ๋ก๊ทธ๋จ์ ์ฐ๊ฒฐํ๋ ๊ฒ์ ์ข ์ข ์์์ต๋๋ค.
์ด ๊ฒฝ์ฐ npm์ react ๋ฐ react-dom์ react ๋ฐ react-dom์ ์์ ๋ฒ์ ์ ์ฐ๊ฒฐํฉ๋๋ค.
๋ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ create-react-app์ ์๋ก ์๋์ ์ด๋ฉฐ npm ๋งํฌ๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
์ด ๋ถ๋ถ์ ์ฝ์์ต๋๊น?
https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate - ๋ฐ์
link
์ํฌํ๋ก๋ฅผ ์ง์ ์ฒ๋ฆฌํฉ๋๋ค.
(์ด ๋ฐฉ๋ฒ์ด ๋์์ด ๋์ง ์์ผ๋ฉด ์๋ ค์ฃผ์๋ฉด ๋ค์ ์ด ์ ์์ต๋๋ค. ์์ธํ ์ ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค.)
์๊ณ ํด์ฃผ์ ๋ชจ๋ ๋ถ๋ค ๊ฐ์ฌํฉ๋๋ค
@gaearon ๋๋ ์ด๋ฏธ React ํ์ด์ง์ ๊ถ์ฅ ์ฌํญ์ ๋ฐ๋ผ ์ด๊ฒ์ ํด๊ฒฐํ๋ ค๊ณ ์๋ํ๊ณ ์ฌ์๋ํ์ต๋๋ค.
์ด ๋ชจ๋ ์ง์นจ ์ ๋ฐ๋์ง๋ง ์ฌ์ ํ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
@threepointone ์ถฉ๋ถํ ์๊ธฐ๊ณ ๋ง์ ์ฌ๋๋ค์ด #13991 ์์ ์ด์ ๋ํ ํด๊ฒฐ์ฑ ์
์๊ฒ ์ต๋๋ค. ๋์์ด ๋์ง ์์ผ๋ฉด ๋๊ตฐ๊ฐ ๋ฌธ์ ๋ฅผ ๋๋ฒ๊น ํ๊ณ ๋์์ ์ค ์ ์๋๋ก ์ด์ด ๋ก๋๋ค. ๋ฌธ์ ๋ _is_ ๋์ผํ์ง๋ง(ํจํค์ง๋ฅผ ๋งํฌํ๋ ๋ฐฉ์์ผ๋ก ์ธํด ํจํค์ง๊ฐ ๋ฒ๋ค์ ๋ณต์ ๋จ) ์ ์์ด ํจ๊ณผ๊ฐ ์๋ ์ด์ ๊ฐ ํ์คํ์ง ์์ต๋๋ค.
@gaearon ์ด์ ๋ซ์ ์ ์์ต๋๋ค. ์์ ์๋ฃจ์ ์ด ์๋ํ์ต๋๋ค.
์ ๋ ์์ฃผ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ์์ผ๋ก hook์ ํธ์ถํฉ๋๋ค.
import React, {useState} from 'react'
import ReactDOM from 'react-dom'
function App() {
const [number, setNumber] = useState(0);
const increase = () => {
setNumber(number+1);
}
return <div>
<span>Counting: {number}</span>
<button onClick={increase} >Increase</button>
</div>
}
const selector = document.getElementById('app');
ReactDOM.render(<App />, selector);
๋๋ ์ค๋ฅ๋ฅผ ์ป๋๋ค
Hooks can only be called inside of the body of a function component.
์ค์ผ์น์์ ๋ฐ์ ํ๋ก์ ํธ๋ฅผ ๋ง์ด ์์ฑํ์ง๋ง ์ด์ ์๋ ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์์์ต๋๋ค.
ํํฌ์ ๋ชจ๋ ๊ท์น์ ์ดํดํฉ๋๋ค.
๋๋ฒ๊น
ํ๊ณ ํด๊ฒฐํ๊ธฐ ์ํด 12์๊ฐ ๋์ ์๊ฐ์ โโํ ์ ํ์ง๋ง ๋ถํํ๋ ๊ทธ๋ด ์ ์์ต๋๋ค. ์ง๊ธ์ ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
// Add this in node_modules/react-dom/index.js
window.React1 = require('react');
// Add this in your component file
require('react-dom');
window.React2 = require('react');
console.log(window.React1 === window.React2); // I get true
@xeastcrast ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์์ต๋๊น?
@carlosriveros
์ฌ๋ก 1:
ํ๋์ ์คํฌ๋ฆฝํธ ํ๊ทธ ๋ฒ๋ค๋ง ์๋ index.html์ ํ์ธํ ์ ์์ต๋๋ค.
index.html์ ํ์ธํ ํ์๋ ๋์ผํ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ
์ฌ๋ก 2:
Webpack html์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ webpack.config ํ์ผ ๋ด์ ํ๋ฌ๊ทธ์ธ ์์ฑ
๊ทธ๋ฆฌ๊ณ new HTMLWebpackPlugin()์ ์ต์
์ {reject: true}๊ฐ ์์ต๋๋ค.
index.html์์ ๋ฒ๋ค๋ก srcํ๋ ์คํฌ๋ฆฝํธ ํ๊ทธ๋ฅผ ์ ๊ฑฐํด์ผ ํฉ๋๋ค.
HTMLWebpackPlugin์ ๋ฒ๋ค ํ์ผ์ ์๋์ผ๋ก ํฌํจํ๋ ์คํฌ๋ฆฝํธ ํ๊ทธ๋ฅผ ์ถ๊ฐํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
์๋
ํ์ธ์, ์ ๋ React Hooks๋ฅผ ์ฒ์ ์ ํ์ต๋๋ค. ๋ฐฉ๊ธ ๋ด ํ๋ก์ ํธ์์ ๊ฒ์ ํ์์ค์ ๋ง๋ค๊ธฐ ์ํด Material-UI๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ์ด ์ฝ๋๋ฅผ ์์ฑํ์ง๋ง ์๋ํ์ง ์๊ณ ๋์ผํ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. ๋๋ React Hooks ๋ฌธ์๋ฅผ ์ฝ์์ง๋ง ์ด๊ฒ์ ๊ดํด ์๋ฌด๊ฒ๋ ์ป์ง ๋ชปํ์ต๋๋ค.
REACTJS ์ฝ๋
```'react'์์ React ๊ฐ์ ธ์ค๊ธฐ;
'@material-ui/core/styles'์์ { makeStyles } ๊ฐ์ ธ์ค๊ธฐ;
'@material-ui/core/Paper'์์ ์ข
์ด ๊ฐ์ ธ์ค๊ธฐ;
'@material-ui/core/InputBase'์์ InputBase ๊ฐ์ ธ์ค๊ธฐ;
'@material-ui/core/IconButton'์์ IconButton ๊ฐ์ ธ์ค๊ธฐ;
'@material-ui/icons/Search'์์ SearchIcon ๊ฐ์ ธ์ค๊ธฐ;
const useStyles = makeStyles({
๋ฃจํธ: {
ํจ๋ฉ: '2px 4px',
๋์คํ๋ ์ด: 'ํ๋ ์ค',
alignItems: '์ค์',
๋๋น: 400,
},
์
๋ ฅ: {
์ฌ๋ฐฑ์ผ์ชฝ: 8,
ํ๋ ์ค: 1,
},
์์ด์ฝ ๋ฒํผ: {
ํจ๋ฉ: 10,
},
๊ตฌ๋ถ์ : {
๋๋น: 1,
ํค: 28,
์ฌ๋ฐฑ: 4,
},
});
๊ธฐ๋ฅ ๊ธฐ๋ฅ() {
const ํด๋์ค = useStyles();
๋ฐํ (
);
}
๊ธฐ๋ณธ ๊ธฐ๋ฅ ๋ด๋ณด๋ด๊ธฐ;
**PACKAGE.JSON**
```{
"name": "builder-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.0.1",
"@material-ui/docs": "^3.0.0-alpha.9",
"@material-ui/icons": "^4.0.0",
"@material-ui/styles": "^4.0.1",
"react": "^16.8.5",
"react-dom": "^16.8.5",
"react-redux": "^6.0.1",
"react-router-dom": "^5.0.0",
"react-scripts": "2.1.8",
"redux": "^4.0.1",
"styled-components": "^4.2.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
๋ฒ์
์์ ๊ฒฐ๊ณผ
๋
ธ๋ ฅํ๋ค
_CODESANDBOX์์ ์คํํด ๋ณด์ญ์์ค . ๋ชจ๋ ๊ฒ์ด ์ ์คํ๋ฉ๋๋ค_ ํ์ธํ์ญ์์ค .
์๋ ํ์ธ์, ์ ๋ React Hooks๋ฅผ ์ฒ์ ์ ํ์ต๋๋ค. ๋ฐฉ๊ธ ๋ด ํ๋ก์ ํธ์์ ๊ฒ์ ํ์์ค์ ๋ง๋ค๊ธฐ ์ํด Material-UI๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ์ด ์ฝ๋๋ฅผ ์์ฑํ์ง๋ง ์๋ํ์ง ์๊ณ ๋์ผํ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. ๋๋ React Hooks ๋ฌธ์๋ฅผ ์ฝ์์ง๋ง ์ด๊ฒ์ ๊ดํด ์๋ฌด๊ฒ๋ ์ป์ง ๋ชปํ์ต๋๋ค.
REACTJS ์ฝ๋import { makeStyles } from '@material-ui/core/styles'; import Paper from '@material-ui/core/Paper'; import InputBase from '@material-ui/core/InputBase'; import IconButton from '@material-ui/core/IconButton'; import SearchIcon from '@material-ui/icons/Search'; const useStyles = makeStyles({ root: { padding: '2px 4px', display: 'flex', alignItems: 'center', width: 400, }, input: { marginLeft: 8, flex: 1, }, iconButton: { padding: 10, }, divider: { width: 1, height: 28, margin: 4, }, }); function Feature() { const classes = useStyles(); return ( <Paper className={classes.root}> <InputBase className={classes.input} placeholder="Search Google Maps" /> <IconButton className={classes.iconButton} aria-label="Search"> <SearchIcon /> </IconButton> </Paper> ); } export default Feature;
ํจํค์ง.JSON
"name": "builder-frontend", "version": "0.1.0", "private": true, "dependencies": { "@material-ui/core": "^4.0.1", "@material-ui/docs": "^3.0.0-alpha.9", "@material-ui/icons": "^4.0.0", "@material-ui/styles": "^4.0.1", "react": "^16.8.5", "react-dom": "^16.8.5", "react-redux": "^6.0.1", "react-router-dom": "^5.0.0", "react-scripts": "2.1.8", "redux": "^4.0.1", "styled-components": "^4.2.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": [ ">0.2%", "not dead", "not ie <= 11", "not op_mini all" ] }
๋ฒ์
- ๋ ธ๋ -- v10.15.3
- ๋ฐ์ -- ^16.8.5
- ์ฌ๋ฃ-UI -- ^4.0.1
์์ ๊ฒฐ๊ณผ
- ๋ด์ฅ VS CODE ๋ฐ ์์ _๊ฒ์์ฐฝ_
์ฐ์ถ
_TypeError: Object(...)๋ ํจ์๊ฐ ์๋๋๋ค_
_์๋ชป๋ ํํฌ ํธ์ถ์ ๋๋ค. ํํฌ๋ ํจ์ ๊ตฌ์ฑ ์์์ ๋ณธ๋ฌธ ๋ด๋ถ์์๋ง ํธ์ถํ ์ ์์ต๋๋ค. ๋ค์ ์ด์ ์ค ํ๋๋ก ์ธํด ๋ฐ์ํ ์ ์์ต๋๋ค._๋ ธ๋ ฅํ๋ค
_CODESANDBOX์์ ์คํํด ๋ณด์ญ์์ค . ๋ชจ๋ ๊ฒ์ด ์ ์คํ๋ฉ๋๋ค_ ํ์ธํ์ญ์์ค .
๋๋ ๋ํ material-ui๋ฅผ ์ฌ์ฉํ ๋ ์ด ์ง๋ฌธ์ ๋ง๋๋ค.
@stupidsongshu , ๊ทธ๋์ ์ด๋ป๊ฒ ํด๊ฒฐํ์ จ๋์?
์๋ ํ์ธ์, ์ ๋ React Hooks๋ฅผ ์ฒ์ ์ ํ์ต๋๋ค. ๋ฐฉ๊ธ ๋ด ํ๋ก์ ํธ์์ ๊ฒ์ ํ์์ค์ ๋ง๋ค๊ธฐ ์ํด Material-UI๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ์ด ์ฝ๋๋ฅผ ์์ฑํ์ง๋ง ์๋ํ์ง ์๊ณ ๋์ผํ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. ๋๋ React Hooks ๋ฌธ์๋ฅผ ์ฝ์์ง๋ง ์ด๊ฒ์ ๊ดํด ์๋ฌด๊ฒ๋ ์ป์ง ๋ชปํ์ต๋๋ค.
REACTJS ์ฝ๋import { makeStyles } from '@material-ui/core/styles'; import Paper from '@material-ui/core/Paper'; import InputBase from '@material-ui/core/InputBase'; import IconButton from '@material-ui/core/IconButton'; import SearchIcon from '@material-ui/icons/Search'; const useStyles = makeStyles({ root: { padding: '2px 4px', display: 'flex', alignItems: 'center', width: 400, }, input: { marginLeft: 8, flex: 1, }, iconButton: { padding: 10, }, divider: { width: 1, height: 28, margin: 4, }, }); function Feature() { const classes = useStyles(); return ( <Paper className={classes.root}> <InputBase className={classes.input} placeholder="Search Google Maps" /> <IconButton className={classes.iconButton} aria-label="Search"> <SearchIcon /> </IconButton> </Paper> ); } export default Feature;
ํจํค์ง.JSON
"name": "builder-frontend", "version": "0.1.0", "private": true, "dependencies": { "@material-ui/core": "^4.0.1", "@material-ui/docs": "^3.0.0-alpha.9", "@material-ui/icons": "^4.0.0", "@material-ui/styles": "^4.0.1", "react": "^16.8.5", "react-dom": "^16.8.5", "react-redux": "^6.0.1", "react-router-dom": "^5.0.0", "react-scripts": "2.1.8", "redux": "^4.0.1", "styled-components": "^4.2.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": [ ">0.2%", "not dead", "not ie <= 11", "not op_mini all" ] }
๋ฒ์
- ๋ ธ๋ -- v10.15.3
- ๋ฐ์ -- ^16.8.5
- ์ฌ๋ฃ-UI -- ^4.0.1
์์ ๊ฒฐ๊ณผ
- ๋ด์ฅ VS CODE ๋ฐ ์์ _๊ฒ์์ฐฝ_
์ฐ์ถ
_TypeError: Object(...)๋ ํจ์๊ฐ ์๋๋๋ค_
_์๋ชป๋ ํํฌ ํธ์ถ์ ๋๋ค. ํํฌ๋ ํจ์ ๊ตฌ์ฑ ์์์ ๋ณธ๋ฌธ ๋ด๋ถ์์๋ง ํธ์ถํ ์ ์์ต๋๋ค. ๋ค์ ์ด์ ์ค ํ๋๋ก ์ธํด ๋ฐ์ํ ์ ์์ต๋๋ค._๋ ธ๋ ฅํ๋ค
_CODESANDBOX์์ ์คํํด ๋ณด์ญ์์ค . ๋ชจ๋ ๊ฒ์ด ์ ์คํ๋ฉ๋๋ค_ ํ์ธํ์ญ์์ค .
์ด๊ฒ์ ์ค๋ ๋์๊ฒ ์ผ์ด๋ฌ๊ณ npm install --save react-dom@latest๋ฅผ ์คํํฉ๋๋ค.
https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate - ๋ฐ์
๋์๋ง ํ์ด์ง์ ์ ๊ณต๋ ์ง์นจ์ ๋ฐ๋์ง๋ง ์ฌ์ ํ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์ง ์์์ต๋๋ค.
๋๋ฅผ ์ํด, ์ฐ๋ฆฌ๋ ๋จ์ํ React๋ฅผ ์ํ์ผ๋ก ์ ๋ฌํ์ฌ ๊ทธ๊ฒ์ ํด๊ฒฐํ์ต๋๋ค. ์ด๊ฒ์ ์ฐ๋ฆฌ๊ฐ ์์ ํ ์ ์ดํ ์ ์๋ ์ฑ์ด์ ๋ณ๋์ ๋ชจ๋์ด๋ฏ๋ก ๋ชจ๋ ์ฌ๋์๊ฒ ์ ์ฉ๋๋์ง ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
์ ์๊ฒ๋ ๋์์ด ๋์ง ์์์ต๋๋ค..
React ๋ฐ React-DOM = 16.8.0 ๋ฐ Material-UI = 4.1.3 ์ฌ์ฉ
๋๋ ์ด ํํฌ ๋ฌธ์ ์ ๋ํ ๋ชจ๋ ์๋ฃจ์ ์ ํ ์คํธํ๊ณ ์์ต๋๋ค. ํํฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๊ตฌ์ฑ ์์๋ฅผ ๊ฐ์ ธ์ค๋ฉด 2๊ฐ์ ๋ฐ์ ์ธ์คํด์ค(lib์์ ํ๋, ๋ด ์ฑ์์ ํ๋)๊ฐ ์์ผ๋ฏ๋ก ์ถฉ๋์ด ๋ฐ์ํฉ๋๋ค.
๋๋ ๋ชจ๋ ์ ์์ ๋ฐ๋์ง๋ง ์ง๊ธ๊น์ง ์๋ฌด ๊ฒ๋ ์๋ํ์ง ์์์ต๋๋ค. ๋๋ ๊ทธ๊ฒ์ด ๋ ๋ช ํํด์ง ๋๊น์ง ์ํ๋ก ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋ค ๊ฒ์ ๋๋ค ...
์ฌ๊ธฐ์ ๊ฐ์ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๋ ๊ฐ์ ๋ฐ์ ์ธ์คํด์ค๊ฐ ์๋ ํ๋ก์ ํธ๊ฐ ์์ต๋๋ค. ํ๋๋ lib
์ด๊ณ ํ๋๋ app
๋ฅผ ์๋นํ๋ lib
์
๋๋ค.
yarn.lock
, package-lock.json
, node_modules
, dist
, ์บ์ ๋ฐ ๋น๋ ํ์ผ์ ์ข
๋ฅ๋ฅผ ์ ๊ฑฐํ ๋ค์ yarn
๋๋ npm install
๋ฅผ ์คํํ์ฌ ํด๊ฒฐํฉ๋๋ค npm install
์
๋๋ค. ์ด์ ์๋ํฉ๋๋ค!
๋๋ ์ด๊ฒ์ ๋ณํ๋ก ํด๊ฒฐํ๋ค
<Route key={index} render={route.component} path={route.path} />
์๊ฒ
<Route key={index} component={route.component} path={route.path} />
ํ์ง๋ง ๋๋ ์ ๊ทธ๋ฐ์ง ๋ชจ๋ฅด๊ฒ ๋ค :(
์ค์๋ก ๋ ๋๋งํ๋ ๋์ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ์ฌ ์์ ํ์ต๋๋ค. ๊ณต์ ๊ฐ์ด๋๋ ์ด ์๋๋ฆฌ์ค๋ฅผ ์ธ๊ธํ์ง ์์์ต๋๋ค.
๊ฐ์ฌํฉ๋๋ค, xyj404
<Route path="/login" exact component={LoginForm} />
๋๋ ๊ฐ์ ๋ฌธ์ ๊ฐ ์์์ง๋ง ์์ธ์ด ๋ฌ๋์ต๋๋ค. ์๋ ๊ฐ์ ธ์ค๊ธฐ ํ๋ฌ๊ทธ์ธ ๋๋ ์ค๋ํซ์ ์ฌ์ฉํ๋ ๋ชจ๋ ์ฌ์ฉ์์ ๊ฒฝ์ฐ ๋์ผํ ๋์๋ฌธ์ ๋ฐ์์์ ๊ฐ์ ธ์์ผ ํฉ๋๋ค.
import {useEffect} from 'react'
์ import {useEffect} from 'React'
๊ฐ ํผํฉ๋์ด ์์ผ๋ฉด ์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
๋๋ ๋ํ์ด ๋ฌธ์ ๊ฐ ์์ง๋ง ์คํ ๋ฆฌ ๋ถ์์ ๋ชจ๋ ๊ฒ์ด ์ ์๋ํ์ง๋ง ์์ฉ ํ๋ก๊ทธ๋จ์์ ์ฌ์ฉํ ๋ ๋์ผํ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
๊ฐ์ ๋ฌธ์ ๋ ์๋ํฉ๋๋ค
์คํ ๋ฆฌ๋ถ์์๋ ๊ด์ฐฎ์ง๋ง ์ฑ์ ์ฌ์ฉํ๋ฉด ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
npm test
์คํํด๋ ๋์ผํ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง๋ง ์ฑ์ ์ ๋๋ก ์๋ํฉ๋๋ค.
์ฌ๊ธฐ ๋ด package.json
, ์ ์ ์ฌํญ์ด ์์ต๋๊น? ๋๋ ์ ๋ง๋ก ๋ถ์ด ์๊ณ ์ด ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐ ์๊ฐ์ ๋ญ๋นํ๊ณ ์์ต๋๋ค.
{
"name": "myproject",
"version": "0.1.0",
"private": true,
"dependencies": {
"@date-io/moment": "1.0.1",
"@material-ui/core": "^3.9.3",
"@material-ui/icons": "^3.0.1",
"animate.css": "^3.7.0",
"aws-amplify": "^0.4.8",
"aws-amplify-react": "^0.1.54",
"axios": "^0.18.0",
"file-saver": "^2.0.0",
"ics-js": "^0.10.2",
"material-ui-pickers": "2.1.1",
"moment": "^2.23.0",
"normalize.css": "^8.0.1",
"react": "^16.8.6",
"react-app-polyfill": "^1.0.0",
"react-date-range": "^1.0.0-beta",
"react-dom": "^16.8.6",
"react-google-maps": "^9.4.5",
"react-jss": "^8.6.1",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-scripts": "3.0.1",
"recharts": "^1.3.5",
"redux": "^4.0.1",
"styled-components": "^4.2.0",
"sweetalert2": "^7.33.1",
"sweetalert2-react-content": "^1.0.1"
},
"scripts": {
"start:dev": "node -r dotenv/config ./node_modules/react-scripts/bin/react-scripts start dotenv_config_path=./env/development.env",
"start:stage": "node -r dotenv/config ./node_modules/react-scripts/bin/react-scripts start dotenv_config_path=./env/stage.env",
"start:prod": "node -r dotenv/config ./node_modules/react-scripts/bin/react-scripts start dotenv_config_path=./env/production.env",
"build:stage": "node --max_old_space_size=2048 --max_old_space_size=1024 -r dotenv/config ./node_modules/.bin/react-scripts build dotenv_config_path=./env/stage.env",
"build:dev": "node --max_old_space_size=2048 -r dotenv/config ./node_modules/react-scripts/bin/react-scripts build dotenv_config_path=./env/development.env",
"build:prod": "node --max_old_space_size=2048 -r dotenv/config ./node_modules/react-scripts/bin/react-scripts build dotenv_config_path=./env/production.env",
"test": "node -r dotenv/config ./node_modules/.bin/react-scripts test dotenv_config_path=./env/development.env --runInBand",
"eject": "react-scripts eject"
},
"devDependencies": {
"@testing-library/jest-dom": "^4.0.0",
"@testing-library/react": "^8.0.5",
"@testing-library/react-hooks": "^1.1.0",
"dotenv": "^6.0.0",
"prettier": "^1.15.3",
"react-test-renderer": "^16.8.6"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
npm ๋งํฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ฐ๊ฒฐ๋ ๋ณ๋์ ์ฑ์์ makeStyles
from @material-ui/core
makeStyles
ํํฌ๋ฅผ ์ฌ์ฉํ์ฌ react ๋ฐ react-dom ๋ ๋ค v16.8.6์ ์ฌ์ฉํ์ฌ ์ด ์ํฉ์ ์ง๋ฉดํ์ต๋๋ค.
๋ฉ์ธ ์ฑ์ react ๋ฐ react-dom v16.9.0์ผ๋ก ์ ๊ทธ๋ ์ด๋ํ๊ณ ๋ฌธ์ ๋ฅผ ์ผ์ผํค๋ ํํฌ๋ฅผ ์ฌ์ฉํ๋ ์ฑ์ ๋ค์ ์ฐ๊ฒฐํ๋ฉด ๋ชจ๋ ๊ฒ์ด ๋ค์ ์๋ํ๊ธฐ ์์ํ์ต๋๋ค.
์, @material-ui๋ฅผ ์ฌ์ฉํ ๋๋ ๋์ผํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํ์ ํ๋ ๋ฐ ๋ ๋ง์ ์๊ฐ์ด ๊ฑธ๋ฆฝ๋๋ค. ๊ทธ๋ฐ ๋ค์ Google์ ๊ฒ์ํ์ฌ ์ด์ ๋ํ ํด๊ฒฐ์ฑ ์ ์ฐพ์์ต๋๋ค. ์ฌ๊ธฐ์ ์์ต๋๋ค http://putridparrot.com/blog/react-material-ui-invalid-hook-call-hooks-can-only-be-call-inside-of-the-body-of-a-function-component/
์๋ณธ ๊ฒ์๋ฌผ:
React ๋ด์์ Material UI ์ฝ๋๋ฅผ ์์ฑํ๊ณ TypeScript(์ ํํ๊ฒ๋ .tsx ํ์ผ ๋ด)๋ฅผ ์ฌ์ฉํ๋ ๋ช ๊ฐ์ง ์๋ฅผ ์ดํด๋ณด๋ฉด ๋ฐํ์์ "์๋ชป๋ ํํฌ ํธ์ถ"๊ณผ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ํํฌ๋ ํจ์ ๊ตฌ์ฑ ์์์ ๋ณธ๋ฌธ ๋ด๋ถ์์๋ง ํธ์ถํ ์ ์์ต๋๋ค."
๋ค์์ ์ด ์ค๋ฅ๋ฅผ ์ผ์ผํค๋ ์ฝ๋์ ์์ ๋๋ค.
import React, { Component } from "react";
import AddIcon from "@material-ui/icons/Add";
import { Fab } from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles(theme => ({
fab: {
margin: theme.spacing(1),
},
}));
const classes = useStyles();
export default class SampleComponent extends Component<{}, {}> {
public render() {
return (
<div>
<Fab color="primary" aria-label="Add" className={classes.fab}><AddIcon /></Fab>
</div>
);
}
}
์ฐ๋ฆฌ๊ฐ ํด์ผ ํ ์ผ์ useStyles ์ฝ๋๋ฅผ ํจ์๋ก ๊ฐ์ธ๊ณ ๊ทธ๊ฒ์ ์ฌ์ฉํ๋ ์ฝ๋๋ฅผ ๋ฐ๊พธ๋ ๊ฒ์ ๋๋ค. ์๋ฅผ ๋ค์ด
const SampleFab = () => {
const classes = useStyles();
return <Fab color="primary" aria-label="Add" className={classes.fab}><AddIcon /></Fab>;
}
export default class SampleComponent extends Component<{}, {}> {
public render() {
return (
<div>
<SampleFab />
</div>
);
}
}
์ด๊ฒ์ ๋ํ ๊ฐ๋จํ ํจ์์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๊ตฌ์ฑ ์์๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
=> ์ด์ํ์ง๋ง ์ ์๋ํฉ๋๋ค. ์ด๊ฒ์ด ๋์์ด ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
lerna ๋๋ ์ ์ ๊ฐ์ monorepo๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ Webpack ํด๊ฒฐ ๊ตฌ์ฑ์ ์ด์ ๊ฐ์ sth๋ฅผ ์ถ๊ฐํ์ญ์์ค.
...
alias: {
'react-dom': 'path/to/main-package/node_modules/react-dom',
react: 'path/to/main-package/form/node_modules/react',
}
...
Storybook์์ ๊ฐ์ ธ์ค๋ ๊ฒฝ์ฐ ์ฌ์ฉ์ ์ง์ Webpack ๊ตฌ์ฑ์๋ ์ถ๊ฐํ์ญ์์ค.
์ฐธ๊ณ ๋ก, ์ด ๋ฐฉ๋ฒ์ผ๋ก ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋๋ฉด ์นํฉ ๋น๋ ๋ฒ๋ค์๋ 2๊ฐ์ง ๋ฒ์ ์ ๋ฐ์์ด ์์ต๋๋ค(์นํฉ ๋ฒ๋ค ๋ถ์๊ธฐ์ ๊ฐ์ sth๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒ์ฌ). ์ ๊ฒฝ์ฐ์๋ ๋ฒ๋ค๋ง๊ณผ ๋ชจ๋ ๋๊ตฌ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ณ๋์ ํจํค์ง๊ฐ ๋ชจ๋ ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ ์์ผ๋ฏ๋ก ๋ณ์นญ์ ์ถ๊ฐํด์ผ ํ์ต๋๋ค.
lerna ๋๋ ์ ์ ๊ฐ์ monorepo๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ Webpack ํด๊ฒฐ ๊ตฌ์ฑ์ ์ด์ ๊ฐ์ sth๋ฅผ ์ถ๊ฐํ์ญ์์ค.
... alias: { 'react-dom': 'path/to/main-package/node_modules/react-dom', react: 'path/to/main-package/form/node_modules/react', } ...
Storybook์์ ๊ฐ์ ธ์ค๋ ๊ฒฝ์ฐ ์ฌ์ฉ์ ์ง์ Webpack ๊ตฌ์ฑ์๋ ์ถ๊ฐํ์ญ์์ค.
์ฐธ๊ณ ๋ก, ์ด ๋ฐฉ๋ฒ์ผ๋ก ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋๋ฉด ์นํฉ ๋น๋ ๋ฒ๋ค์๋ 2๊ฐ์ง ๋ฒ์ ์ ๋ฐ์์ด ์์ต๋๋ค(์นํฉ ๋ฒ๋ค ๋ถ์๊ธฐ์ ๊ฐ์ sth๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒ์ฌ). ์ ๊ฒฝ์ฐ์๋ ๋ฒ๋ค๋ง๊ณผ ๋ชจ๋ ๋๊ตฌ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ณ๋์ ํจํค์ง๊ฐ ๋ชจ๋ ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ ์์ผ๋ฏ๋ก ๋ณ์นญ์ ์ถ๊ฐํด์ผ ํ์ต๋๋ค.
๊ฐ์ฌํฉ๋๋ค. ์ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์์ต๋๋ค. docs
๋ค๋ฅธ React๋ฅผ ์ค์นํ๋๋ฐ ์ด ๋๋ฌธ์ ์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
์ ๋ ์์ฃผ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ์์ผ๋ก hook์ ํธ์ถํฉ๋๋ค.
import React, {useState} from 'react' import ReactDOM from 'react-dom' function App() { const [number, setNumber] = useState(0); const increase = () => { setNumber(number+1); } return <div> <span>Counting: {number}</span> <button onClick={increase} >Increase</button> </div> } const selector = document.getElementById('app'); ReactDOM.render(<App />, selector);
๋๋ ์ค๋ฅ๋ฅผ ์ป๋๋ค
Hooks can only be called inside of the body of a function component.
์ค์ผ์น์์ ๋ฐ์ ํ๋ก์ ํธ๋ฅผ ๋ง์ด ์์ฑํ์ง๋ง ์ด์ ์๋ ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์์์ต๋๋ค.
ํํฌ์ ๋ชจ๋ ๊ท์น์ ์ดํดํฉ๋๋ค.
๋๋ฒ๊น ํ๊ณ ํด๊ฒฐํ๊ธฐ ์ํด 12์๊ฐ ๋์ ์๊ฐ์ โโํ ์ ํ์ง๋ง ๋ถํํ๋ ๊ทธ๋ด ์ ์์ต๋๋ค. ์ง๊ธ์ ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.// Add this in node_modules/react-dom/index.js window.React1 = require('react'); // Add this in your component file require('react-dom'); window.React2 = require('react'); console.log(window.React1 === window.React2); // I get true
React ๋ฐ React DOM ๋ฒ์ ์ด ์ผ์นํ์ง ์์ ์ ์์ต๋๋ค.
React ๋ฒ์ ์ ๋ณ๊ฒฝํ์ฌ ๋์ผํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค.
๋ด ๊ตฌ์ฑ ์์๋ฅผ ๊ธฐ๋ฅ์ ๊ตฌ์ฑ ์์์์ classfull ๊ตฌ์ฑ ์์๋ก ๋ณ๊ฒฝํ๋ ค๊ณ ํ ๋ ์ด ๋ฌธ์ ์ ์ง๋ฉดํ์ผ๋ฉฐ ์ด ์ค๋ฅ๋ ๋ฐ์ํ๋ฏ๋ก ์ด ์ค๋ฅ์ ๋ํ ํด๊ฒฐ์ฑ ์ด ์ฌ๊ธฐ์ ์์ต๋๋ค. ๊ทํ์ ๊ฒฝ์ฐ์๋ ๋์์ด ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
์ด ๊ฒฝ์ฐ ๊ณ ์ฐจ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํด๋ณด์ญ์์ค.
'๋ฐ์'์์ React ๊ฐ์ ธ์ค๊ธฐ;
'prop-types'์์ PropType์ ๊ฐ์ ธ์ต๋๋ค.
'@material-ui/styles'์์ { withStyles } ๊ฐ์ ธ์ค๊ธฐ;
'@material-ui/core/Button'์์ ๋ฒํผ ๊ฐ์ ธ์ค๊ธฐ;
const ์คํ์ผ = ํ
๋ง => ({
๋ฃจํธ: {
๋ฐฐ๊ฒฝ: '์ ํ ๊ทธ๋ผ๋ฐ์ด์
(45deg, #FE6B8B 30%, #FF8E53 90%)',
ํ
๋๋ฆฌ: 0,
ํ
๋๋ฆฌ ๋ฐ๊ฒฝ: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
์์: 'ํฐ์',
ํค: 48,
ํจ๋ฉ: '0 30px',
},
});
ํด๋์ค HigherOrderComponent๋ React.Component๋ฅผ ํ์ฅํฉ๋๋ค. {
์ธ์ฐ๋ค(){
const { ํด๋์ค } = this.props;
๋ฐํ (
HigherOrderComponent.propTypes = {
ํด๋์ค: PropTypes.object.isRequired,
};
๊ธฐ๋ณธ ๋ด๋ณด๋ด๊ธฐ withStyles(styles)(HigherOrderComponent);
๋๋ React์ FWIW๋ฅผ ๋ฐฐ์ฐ๊ณ ์์ผ๋ฉฐ ๋ค์๊ณผ ๊ฐ์ด App()์ ๋ ๋๋งํ๋ ค๊ณ ํ ๋๋ ๋ฌธ์ ๋ฅผ ๋ณด์์ต๋๋ค.
ReactDOM.render(App(), $("#root"));
๋ด ์ถ๋ก ์ <App />
๊ฐ App ๊ตฌ์ฑ ์์๋ฅผ ์ฝ์
ํ๊ธฐ ์ํด ์์์ ์ผ๋ก ๋ฏธ๋ ํ
ํ๋ฆฟ์ ์์ฑํ๋ค๋ ๊ฒ์ด๋ฏ๋ก ๋์ JSX ํ
ํ๋ฆฟ์ ๋ฐํํ๋ App์ ์ง์ ํธ์ถํ์ต๋๋ค.
๊ทธ๋ฌ๋ setState()๋ฅผ ์ฌ์ฉํ๊ธฐ ์์ํ๋ฉด ์์ ์ธ๊ธํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. <App />
๋ณ๊ฒฝํ๋ฉด ์ด ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์์ต๋๋ค.
ReactDOM.render(<App />, $("#root"));
์ฐธ๊ณ : $๋ ๋์ฐ๋ฏธ const $ = sel => document.querySelector(sel);
๋ฟ์
๋๋ค.
์ ์๋ ๋ชจ๋ ์์ ์ฌํญ์ ํด๊ฒฐํ ํ์๋ ์ด ์ค๋ฅ๊ฐ ๊ณ์ ๋ฐ์ํ๋ ๊ฒฝ์ฐ ํ๋ก์ ํธ๋ฅผ ์คํํ ๋ ์ฌ๋ฐ๋ฅธ ๋๋ ํฐ๋ฆฌ ๋์๋ฌธ์๋ฅผ ์ฌ์ฉํ๊ณ ์๋์ง ๋ค์ ํ์ธํ์ญ์์ค. ๋๋ ๋ด PC์ ๊ณ์ ๋ฌธ์ ๊ฐ ์์๊ณ ์ด๊ฒ์ด ๊ทธ ์ด์ ์์ ๋ฐ๊ฒฌํ์ต๋๋ค.
react-dom
๋ฅผ 16.10.2
๋ฒ์ ์ผ๋ก ์
๋ฐ์ดํธํ ํ ํ
์คํธ๋ฅผ ์คํํ ๋ ํด๋น ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. enzyme-adapter-react-16
๋ฅผ 1.15.1
๋ฒ์ ์ผ๋ก ์
๊ทธ๋ ์ด๋ํ๋ฉด ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์์ต๋๋ค.
์ค, ๋๋ ๋ฐ์ v16.10.2์ ์์ต๋๋ค. ๋๋ ์ด๋ฏธ ๋ช ์๊ฐ ๋์ ์ด๊ฒ๊ณผ ์ธ์ฐ๋ ค๊ณ ๋ ธ๋ ฅํ์ง๋ง ๋ ์ค์ํ ์ผ์ด ์๊ธฐ ๋๋ฌธ์ ๋ ์ด์ ์ด ์ ํฌ๋ฅผ ๊ณ์ํ์ง ์์ ๊ฒ์ ๋๋ค. ์ง๊ธ์ react-final-form 4.1.0์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ข์ ๊ฐ.
์ด๊ฒ์ ๋ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค.
https://github.com/facebook/create-react-app/issues/7676#issuecomment -531543375
๋๋ ์์กด์ฑ์ ์ ๊ทธ๋ ์ด๋ํ ์งํ์ ๊ฐ์ ๋ฌธ์ ๋ฅผ ๊ฒช์๊ณ ์ด๊ฒ์ ํด๊ฒฐํ๊ธฐ ์ํด ๋ช ์๊ฐ์ ๋ณด๋์ต๋๋ค.
npm ls react
๋ ๋ค์์ ๋ณด์ฌ์ฃผ์์ต๋๋ค.
โโโฌ @storybook/[email protected]
โ โโโ [email protected]
โโโ [email protected]
react
๋ฐ react-dom
๋ฅผ 16.9.0
ํ๋ฉด ์ผ์์ ์ธ ์ข
์์ฑ์ด ์ฌ๋ผ์ง๋ฏ๋ก ์ค๋ฅ๊ฐ ์ฌ๋ผ์ง๋๋ค. ๋๋ ์ฒ์์ ์ด๊ฒ์ด 16.8.6
์์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค๋ ๊ฒ์ ์์์ฐจ๋ฆด ๋๊น์ง 16.10.0
ํ๊ท๊ฐ ์๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค๊ณ ์๊ฐํ์ต๋๋ค(์ผ์์ ์ธ ์ข
์์ฑ์ด ๋ถ๋ช
ํ ๊ณ ์ ๋์๊ธฐ ๋๋ฌธ์).
yarn์ resolutions
ํ๋ ๋ ํนํ lerna monorepos์์ ์ ๋๋ก ์๋ํ๊ธฐ๊ฐ ์ฝ๊ฐ ๊น๋ค๋กญ์ต๋๋ค. ๊ทธ๋ฌ๋ React์ ๋ ๋ฒ์งธ ์ฌ๋ณธ์ ์ ๊ฑฐํ๋ ๊ฒ์ด ํธ๋ฆญ์ ์ํํ์ต๋๋ค. ๋๋ ๊ทธ๊ฒ์ด ๋ฒ๋ค๋ก ์ด๋ป๊ฒ ๋๋ฌ๋์ง ์ฌ์ ํ ๋ชจ๋ฅด์ง๋ง ๊ทธ๊ฒ์ด ๋ฌธ์ ๋ฅผ ์ผ์ผํจ ์์ธ์
๋๋ค.
์ง๊ธ์ @storybook/addon-info
์์ ํ ์ญ์ ํ๋ ๊ฒ์ ๊ณ ๋ คํ๊ณ ์์ต๋๋ค. React 0.14์ ๋ํ ์ข
์์ฑ์ด ์๋ ๋ค๋ฅธ ์ข
์์ฑ์๋ ์์กดํ๊ธฐ ๋๋ฌธ์
๋๋ค(์). ํ์ง๋ง ์์ง ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง๋ ์์์ต๋๋ค.
ํธ์ง: ๊ธฐ๋ก์ ์ํด, ๋๋ resolutions
๋ฅผ ์ค๋ณต์ ์ ๊ฑฐํ ์ ์์๊ณ React ์ฌ๋ณธ์ด ๋ฒ๋ค์ ๋ค์ด์๋ค๋ ๊ฒ์ ๋ฏฟ๊ธฐ๋ฅผ ๊ฑฐ๋ถํ๊ธฐ ๋๋ฌธ์ ์ด๊ฒ์ ๋ํด ์ฝ 4์๊ฐ์ ๋ณด๋์ต๋๋ค. ๋์ฒ๋ผ ํ์ง๋ง. ์ผ์์ ์ธ React๋ฅผ ์ค๋ณต ์ ๊ฑฐํ์ญ์์ค.
์ด๊ฒ์ HOC ๋ด๋ถ์์ ํํฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ฐ์ํฉ๋๋ค.
const HOC = Component => { return (props) => { const [val] = useState(); return <div>{val}</div> } }
๋๋ ๋ํ ๊ฐ์ ๋ฌธ์ ์ ์ง๋ฉดํ๊ณ ์์ต๋๋ค.
์ ๋ฅผ ์๋ดํด ์ฃผ์๊ฒ ์ต๋๊น?
@Neeraj-swarnkar ์ฌ์ ํ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ์ต์ํ์ ์์ ๋ก ๋ฌธ์ ๋ฅผ ์ค์ด์ญ์์ค.
๊ฑฐ์ ํญ์ ๋ ๊ฐ์ง ๋ฌธ์ ์ค ํ๋๋ก ์์ฝ๋ฉ๋๋ค.
์ฌํ๊ฒ๋ ์ด ๊ฒฝ์ฐ ์คํ ์ถ์ ์ ์คํด์ ์์ง๊ฐ ์๋ ๊ฒฝํฅ์ด ์์ง๋ง ์ค๋ฅ ๋ฉ์์ง๋ ์ผ๋ฐ์ ์ผ๋ก ์ฌ๋ฐ๋ฅธ ์์ด๋์ด๋ฅผ ์ ๊ณตํฉ๋๋ค.
@Neeraj-swarnkar ์ฌ์ ํ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ์ต์ํ์ ์์ ๋ก ๋ฌธ์ ๋ฅผ ์ค์ด์ญ์์ค.
๊ฑฐ์ ํญ์ ๋ ๊ฐ์ง ๋ฌธ์ ์ค ํ๋๋ก ์์ฝ๋ฉ๋๋ค.
- ๊ตฌ์ฑ ์์๋ฅผ ์ ๋ฌํ๋ค๊ณ ์๊ฐํ์ง๋ง ์ค์ ๋ก๋ ๋ ๋๋ง ์ํ์ ๋๋ค(์ฆ, "๊ตฌ์ฑ ์์"๋ ๊ตฌ์ฑ ์์๊ฐ ์๋๋ผ ํจ์๋ก ํธ์ถ๋จ).
- ์ผ์์ ์ธ ์ข ์์ฑ์ ํตํด ์ด๋ป๊ฒ๋ ๋ ์ด์์ React ๋ฒ์ ์ ๋ฒ๋ค๋ก ๋ฌถ์๊ณ ์๋ก์ ๋ฐ๊ฐ๋ฝ์ ๋ฐ์ต๋๋ค.
์ฌํ๊ฒ๋ ์ด ๊ฒฝ์ฐ ์คํ ์ถ์ ์ ์คํด์ ์์ง๊ฐ ์๋ ๊ฒฝํฅ์ด ์์ง๋ง ์ค๋ฅ ๋ฉ์์ง๋ ์ผ๋ฐ์ ์ผ๋ก ์ฌ๋ฐ๋ฅธ ์์ด๋์ด๋ฅผ ์ ๊ณตํฉ๋๋ค.
ํฌ์ธํฐ ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค. ๋ ์ํ ์ ์๋ ์์ ์ด๋ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํฉ๋๊น?
@Neeraj-swarnkar๋ ์ฌ์ ํ ๋์ผํ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ์ต์ํ์ ์์ ๊ฐ ์์ ๋๊น์ง ์ฝ๋๋ฅผ ์ฃผ์ ์ฒ๋ฆฌํ๊ณ ์๋ฆฌ ํ์์๋ก ๊ต์ฒดํด ๋ด ๋๋ค.
๋๋ฃ๋ ๋๋ฃ๊ฐ ์๋ ๊ฒฝ์ฐ ์ด ๋ฌธ์ ๋ฅผ ๋๋ฒ๊น ํ๋ ๋ฐ ๋์์ ์์ฒญํ ์ ์์ต๋๋ค. ์ง์ญ ๋ชจ์, ์คํฐ๋ ๊ทธ๋ฃน ๋๋ ๊ทผ๋ฌด ์๊ฐ์ ์ฌ๋ ํ์ฌ๋ฅผ ์ฐพ์ ๋์์ ์์ฒญํ ์ ์์ต๋๋ค. ๋ค๋ฅธ ๋ชจ๋ ๋ฐฉ๋ฒ์ด ์คํจํ๋ฉด ๋ค๋ฅธ ์ฌ๋์ ๊ณ ์ฉํ์ฌ ์กฐ์ฌํ์ญ์์ค.
๋ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ create-react-app์ ์๋ก ์๋์ ์ด๋ฉฐ npm ๋งํฌ๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
์ด ๋ถ๋ถ์ ์ฝ์์ต๋๊น?
https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate - ๋ฐ์
link
์ํฌํ๋ก๋ฅผ ์ง์ ์ฒ๋ฆฌํฉ๋๋ค.
์ด๊ฒ์ ๋งค๋ ฅ์ฒ๋ผ ์๋ํฉ๋๋ค!
๋ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ create-react-app์ ์๋ก ์๋์ ์ด๋ฉฐ npm ๋งํฌ๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
์ด ๋ถ๋ถ์ ์ฝ์์ต๋๊น?
https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate - ๋ฐ์
link
์ํฌํ๋ก๋ฅผ ์ง์ ์ฒ๋ฆฌํฉ๋๋ค.
์ด๊ฒ์ ๋งค๋ ฅ์ฒ๋ผ ์๋ํฉ๋๋ค!
์ด์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋๋ฐ ๋ฐ๋์ ๋์ ๊ท์ฐฎ๊ฒ ํ์ต๋๋ค.
๋๋ ์ด๊ฒ์ ๋ณํ๋ก ํด๊ฒฐํ๋ค
<Route key={index} render={route.component} path={route.path} />
์๊ฒ
<Route key={index} component={route.component} path={route.path} />
ํ์ง๋ง ๋๋ ์ ๊ทธ๋ฐ์ง ๋ชจ๋ฅด๊ฒ ๋ค :(
๊ทธ๊ฒ์ ๋๋ฅผ ์ํด ์๋ํ์ง๋ง ๋๋ ์ด์ ๋ฅผ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
๋จธํฐ๋ฆฌ์ผ UI์์ HOC + makeStyles
๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ฌ๊ธฐ์ ์ค๋ ๊ฒฝ์ฐ,
์ฌ๊ธฐ ๋น์ ์ ๋๋ต์ด ์์ต๋๋ค: https://stackoverflow.com/questions/56329992/invalid-hook-call-hooks-can-only-be-call-inside-of-the-body-of-a-function-com
๋๋ ์ด๊ฒ์ ๋ณํ๋ก ํด๊ฒฐํ๋ค
<Route key={index} render={route.component} path={route.path} />
์๊ฒ
<Route key={index} component={route.component} path={route.path} />
ํ์ง๋ง ๋๋ ์ ๊ทธ๋ฐ์ง ๋ชจ๋ฅด๊ฒ ๋ค :(
๊ทธ๊ฒ์ ๋๋ฅผ ์ํด ์๋ํ์ง๋ง ๋๋ ์ด์ ๋ฅผ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
"component" ์ํ์ React ๊ตฌ์ฑ์์๋ฅผ ์์ํ๊ณ "render" ์ํ์ ํจ์๋ก ํธ์ถ๋ ํจ์๋ฅผ ์์ํฉ๋๋ค. ํจ์ ๊ตฌ์ฑ ์์๋ ๊ตฌ์ฑ ์์์ด๋ฉฐ ํํฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์๋ํ์ง ์์ผ๋ฉฐ ์ผ๋ฐ์ ์ผ๋ก ์ข์ ์๊ฐ์ด ์๋๋๋ค.
๋๋ ์ด๊ฒ์ ๋ณํ๋ก ํด๊ฒฐํ๋ค
<Route key={index} render={route.component} path={route.path} />
์๊ฒ
<Route key={index} component={route.component} path={route.path} />
ํ์ง๋ง ๋๋ ์ ๊ทธ๋ฐ์ง ๋ชจ๋ฅด๊ฒ ๋ค :(
๊ณ ๋ง์, ์น์ ํ๋, ๊ทธ๊ฒ์ ๋๋ฅผ ์ํด ์ผํ์ต๋๋ค. ์ฌ์ ํ, ๋๋ ํผ๋ ์ค๋ฝ์ต๋๋ค. ์ด์ ๋ ๋ฌด์์ ๋๊น?
@jaisonjjames ๋ด ๋ต์ฅ์ ์ฐธ์กฐํ์ญ์์ค. ์ฐจ์ด์ ์ ์ดํดํ๋ ๋ฐ ๋ฌธ์ ๊ฐ ์๋ ๊ฒฝ์ฐ StackOverflow์ ๋ํ ๋ช ๊ฐ์ง ์ค๋ช ์ด ์์ต๋๋ค. https://stackoverflow.com/questions/48150567/react-router-difference-between-component-and-render
์ ๊ฒฝ์ฐ์๋ react-router์ hook์ธ component ์ธ๋ถ์ let match = useRouteMatch("/user/:id");
๊ฐ ์์ผ๋ฏ๋ก ์ค๋ฅ๊ฐ ๋ง์ต๋๋ค.
~์์
์๊ฒ
~์์
<Route path="/component" component={myComponent} />
์๊ฒ
<Route path="/component"><myComponent /></Route>
์ ๊ฒฝ์ฐ์๋ React ๋ฒ์ ์ด ๋ค๋ฅธ window
๊ฐ์ฒด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐธ์กฐํ์ต๋๋ค. ๋๋ ๊ฒฐ๊ตญ ๊ทธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ react
๋ฐ react-dom
์ฐธ์กฐํ๋ ๋ฐ ๋์์ด ๋๋ webpack์ externals
๊ตฌ์ฑ ์ต์
์ ์ฌ์ฉํ์ฌ ์ค๋ฅ๋ฅผ ์ ๊ฑฐํ์ต๋๋ค.
๊ทธ๋์ ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ด์ ๋ You might have more than one copy of React in the same app
๋๋ฌธ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
์ ๊ฒฝ์ฐ์๋ React ๋ฒ์ ์ด ๋ค๋ฅธ
window
๊ฐ์ฒด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐธ์กฐํ์ต๋๋ค. ๋๋ ๊ฒฐ๊ตญ ๊ทธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์react
๋ฐreact-dom
์ฐธ์กฐํ๋ ๋ฐ ๋์์ด ๋๋ webpack์externals
๊ตฌ์ฑ ์ต์ ์ ์ฌ์ฉํ์ฌ ์ค๋ฅ๋ฅผ ์ ๊ฑฐํ์ต๋๋ค.๊ทธ๋์ ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ด์ ๋
You might have more than one copy of React in the same app
๋๋ฌธ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
@AbreezaSaleem ์ด๋ป๊ฒ ์ด๊ฒ์ ๋ฌ์ฑ ํ์ต๋๊น? react
๊ฐ ์ธ๋ถ๋ก ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๋์ผํ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ์ด๋ป๊ฒ๋ ๋ค๋ฅธ ๋ฒ์ ์ผ๋ก ํ์๋ฉ๋๋ค... externals: [ 'react' ]
, externals: { react: 'react' }
๋ฐ externals: { react: 'React' }
, ๊ฒฐ๊ณผ๋ ํญ์ ๋์ผํฉ๋๋ค...
๋๋ ์ด๊ฒ์ ์ฌํํ๊ธฐ ์ํด ๋ค์ ์ต์ํ์ ์์ ๋ฅผ ๋ง๋ค์์ต๋๋ค. ์ ๊ฒฝ์ฐ์๋ electron-webpack ๋ฐ react-dropzone์ด ์๋ ElectronJS์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
๋์ผํ/๋จ์ผ ๋ฐ์ ์ธ์คํด์ค๋ฅผ ๊ฐ๋ฆฌํค๋ ๋ฉ์ง ์นํฉ ๊ตฌ์ฑ ์๋ฃจ์ ์ด ์์ต๋๋ค. 'to be npm ๋ชจ๋'์ ์ฌ์ฉํ๋ ์ฑ ๋ด๋ถ์ ํด์๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด์์ต๋๋ค.
webpack.config.js ๋ด๋ถ์์ ๋ฐ์์ ๋ณ์นญ์ ์ถ๊ฐํ์ฌ ํ๋์ ๋ฐ์ ์ธ์คํด์ค๋ฅผ ์ฌ์ฉํฉ๋๋ค.
module.exports = {
resolve: {
alias: {
react: path.resolve('./node_modules/react')
}
},
// other webpack settings
}
์ค๋ ๋ฐฐ์ด(lerned?) lerna๋ ์ด ๋ฌธ์ ๋ฅผ ์ผ์ผํฌ ์ ์์ต๋๋ค.
lerna ์ฌ์ฉ์๋ ๊ตฌ์ฑ ์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ๋ฐํ๋ ๊ฒฝ์ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ข ์์ฑ์ ๋ฐ์์ ํฌํจํ์ง ๋ง๊ณ peerDependencies์ ๋ฃ๊ณ @types/react๋ฅผ devDependencies์ ๋ฃ์ต๋๋ค.
lerna๋ฅผ ์ฌ์ฉํ๋ ๋ชจ๋ ์ฌ๋์ ์ฝ๋๊ฐ ๋ค๋ฅธ ํจํค์ง์ ๊ตฌ์ฑ ์์๋ฅผ ์ฐธ์กฐํ๋ ํ ํจํค์ง์์ ํ ์คํธ๋ฅผ ์คํํ ๋ ์ด ๋ฌธ์ ๋ฅผ ์ ํ ์ ์์ต๋๋ค.
์ด ๊ฒฝ์ฐ์ ์ฐ๋ฆฌ๊ฐ ๊ฒฝํํ ๋ฌธ์ ๋ ์ฌ์์์ ์ํฌํธ๋๋ ๋ฐ์๊ณผ Material UI์ ํํฌ๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํ๋๋ ๋์๊ธฐ ๋๋ฌธ ์ ๋๋ค.
๋ฐ์์ ๋ด๋ถ์ ์ผ๋ก ReactCurrentDispatcher.current
๋ณ์์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๊ฒ์ผ๋ก ๋ณด์ด๋ฉฐ ์ด๋ ๊ฒฐ๊ตญ ๋ฐ์์ ํ ์ธ์คํด์ค์์ ์ค์ ๋์ง๋ง ๋ฐ์์ ๋ค๋ฅธ ์ธ์คํด์ค์์ ์ฌ์ฉ๋ฉ๋๋ค. ๋น์ด ์์ผ๋ฉด Invalid hook call ...
๋์ง๋๋ค.
์ฐ๋ฆฌ๋ ์ด๋ฏธ Craco ๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋ ์ Create React App์ webpack ๊ตฌ์ฑ์ ์ฌ์ ์ํ๊ณ ์์์ต๋๋ค.
webpack: {
alias: {
react: path.resolve(__dirname, './node_modules/react'),
},
},
๊ทธ๋ฌ๋ ์ด ์นํฉ ์ฌ์ ์๋ ๋น๋ ์์๋ง ์ฌ์ฉ๋ฉ๋๋ค. ํ
์คํธ๋ฅผ ์คํํ ๋ ์ฝ๋๋ ๋น๋๋์ง ์๊ณ ์์ค์์ ์ธ์คํด์คํ๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ฐ๋ฆฌ์ ์๋ฃจ์
์ craco.config.js
์์ CracoAlias ๋ฅผ ์ฌ์ฉํ์ฌ ์ง์ ํ๋ ๊ฒ์ด์์ต๋๋ค. ํ
์คํธ ์ค ๋ฐ์ ๊ฒฝ๋ก:
plugins: [
{
plugin: CracoAlias,
options: {
source: 'options',
baseUrl: './',
aliases: {
// We need to alias react to the one installed in the desktop/node_modules
// in order to solve the error "hooks can only be called inside the body of a function component"
// which is encountered during desktop jest unit tests,
// described at https://github.com/facebook/react/issues/13991
// This is caused by two different instances of react being loaded:
// * the first at packages/desktop/node_modules (for HostSignUpDownloadComponent.spec.js)
// * the second at packages/components/node_modules (for packages/components/Modal)
react: './node_modules/react',
},
},
},
],
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
์ด ๋ถ๋ถ์ ์ฝ์์ต๋๊น?
https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate - ๋ฐ์
link
์ํฌํ๋ก๋ฅผ ์ง์ ์ฒ๋ฆฌํฉ๋๋ค.