Open Kyounghwan01 opened 2 years ago
시멘틱 ui
Seo 잘되게하는법
nextjs는 dotenv가 다르네
next.config.js
/* eslint-disable @typescript-eslint/no-var-requires */
const Dotenv = require("dotenv-webpack");
const withTM = require('next-transpile-modules')([
'@mui/material',
'@mui/system',
]);
module.exports = withTM({
reactStrictMode: true,
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
'@mui/styled-engine': '@mui/styled-engine-sc',
};
config.plugins.push(new Dotenv({ silent: true }));
return config;
},
});
추가완료
리눅스에서 nginx
vscode 탭 workbench.editor.showTabs
Failed to execute 'scroll' on 'Window': No function was found that matched the signature provided.
chrome 버전에 따라서 smooth 가 없을 수 있음
nextjs dynamic import with params (typescript)
interface IPointComponent { next: (path: number | null) => void; back: () => void; }
const PointComponents = dynamic@domains/point/components/${components[pointStore.currentIndex].component}
));
추가완료
express에서 웹 도메인가져오기
setInterval interface type
let interval: ReturnType<typeof setInterval>;
useEffect(() => {
return () => {
clearInterval(interval);
};
}, []);
prettier cli
prettier --config ./.prettierrc --write -u
parser 가 제대로 안들어가는 경우는 plugins, extends가 안들어갔을 확률이 높다
typescript 에서 절대경로로 component import 할때
eslint[import/no-unresolved](https://github.com/import-js/eslint-plugin-import/blob/v2.26.0/docs/rules/no-unresolved.md)
이런 에러 나면
yarn add -D eslint-import-resolver-typescript
eslint 설정에서
"settings":{
"import/resolver":{
...
"typescript":{
"project": "renderer/tsconfig.json" // tsconfig.json이 root이면 필요없음
}
}
}
husky
허스키 설치
yarn add -D husky
허스키 세팅
npx husky-init && yarn
lint-staged 설정
"lint-staged": {
"renderer/**/*.{js,ts,jsx,tsx}": [
"eslint --ext .tsx,.ts . --quiet --fix",
"prettier --config ./renderer/.prettierrc.json --write -u"
]
}
hint: The 'pre-commit' hook was ignored because it's not set as executable.
아래 실행 (pre-commit 에 맞는 디렉토리로)
`chmod +x .husky/pre-commit
"lint-staged": {
"renderer/**/*.{js,ts,jsx,tsx}": [
"eslint --ext .tsx,.ts . --quiet --fix",
"prettier --config ./renderer/.prettierrc.json --write -u"
]
}
완료
useEffect, dependencise에 객체 넣었을 때 객체 내부의 값이 변경되어도 useEffect 호출되지 않음, preimitve 한 값과 연관이 있어보임
dynamic이면 자기자신을 한번더 호출하는거같은데
useEffect, dependencise에 객체 넣었을 때 객체 내부의 값이 변경되어도 useEffect 호출되지 않음, preimitve 한 값과 연관이 있어보임
객체는 객체의 주소값이 바뀌지 않는 이상 useEffect가 실행되지 않음. object, array가 변경시 실행되게 하려면 https://github.com/kentcdodds/use-deep-compare-effect
이거 참조
비슷한 예시로 useState가 있고 그 state가 객체 또는 배열일 때 useEffect에서 그 객체 또는 배열을 참조한다면 해당 컴포넌트가 리렌더링 될때마다 그 state도 리렌더링에 의해 다시 만들어질테고 그렇게된다면 주소값이 바뀌게 되어 useEffect도 재실행된다.
react checkbox 사용 에러
next-dev.js?36dd:24 Warning: You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.
input type으로 checkbox를 쓸 때 onClick 핸들러를 제공하고 checked 값을 설정하는 식으로 코딩하면 이런 에러 메시지가 발생한다.
해결방법 onClick 핸들러를 없애고 onChange 핸들러를 사용한다. onClick 핸들러를 그대로 두고 싶으면 readonly 키워드를 붙이거나 checked 속성 대신 defaultChecked를 사용한다.
turborepo
typescript empty object
Record<string, never>
밑줄 위로 올리기
text-decoration: underline;
text-decoration-color: #f0cdb9;
text-decoration-thickness: 10px;
text-underline-offset: -10px;
Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
chrome extenstion eval error
react에서 debounce사용하기 rerendering때문에 debounce가 제대로 작동하지 않는 이유랑 같이 서술 -> useCallback을 사용하는데 dependence를 잘 이용해야함
const changePrice = (e) => {
const { name, value } = e.currentTarget || e.target;
const params = {price: value}; handleCarPrice(params);
}
const handleCarPrice = useCallback(
debounce(async params => {
try {
const response = await api.어떤거{ params });
setRealCarPrice(Math.floor(response.data.payload.price / 10000));
} catch (e) {
openSystemModal({ msg: '값이 없습니다' });
}
}, 300),
[],
);
반영완료
index.js?4755:251 Uncaught Error: [MobX] Cannot apply '@observable' to 'CarRewardStore.prototype.pagValdYn': The field is already decorated with '@observable'. Re-decorating fields is not allowed. Use '@override' decorator for methods overriden by subclass.
-- store에 같은 변수 두번 이상 선언한 경우
array 같은 값 추가 new Array(3).fill('adawd')
[ "adawd", "adawd", "adawd" ]
완료
TS2604: JSX element type 'Component' does not have any construct or call signatures.
component: React.ComponentClass<{ selectIndex: number }>;
이미지 본래 height, width 값 알아내기
const checkImageWithHeight = ({ target: img }) => {
// 8개 사진 나오는 미리보기 때 찍었는데 이미지가 세로가 가로보다 크면 이미지 90도 로테이트
console.log(img.naturalHeight, img.naturalWidth);
};
<img alt="preview" onLoad={checkImageWithHeight} src={} />
완료
empty object: {} -> Record<string, never>
완료
Error: A form label must be associated with a control.
-> label에 htmlFor를 input의 id와 일치시킨다
id값은 html input태그에서 label태그와 연결할때 사용한다
label 태그는 checkbox, radio 를 설명하는 문자를 삽일할때 사용하는 태그다.
for 라는 속성을 삽입하여 input의 id와 일치시키면 체크받스 클릭 범위를 증가시킬수있다.
<label for="wow">이그젬플</label>
<input type="checkbox" id="wow">
이렇게 하면 글자를 클릭해도 체크가 된다.
const reader = new FileReader();
reader.onload 가 작동하지 않을때
readAsArrayBuffer, readAsBinaryString, readAsDataURL 또는 readAsText 중 하나를 호출해야 onLoad 작동한다
nextjs redirect -> 특정페이지로 들어오면 특정페이지로 무조건 보냄 next.config.js
async redirects() {
return [
{source: '/contact:path*', destination: '/form:/path*',permanent: true|
]
},
async reweites() {
return [
{source: '/api/xxxx*', destination: 'https://xxx.xxx'
]
},
예전주소 다음 주소로 바뀔때 유용
rewrite
Optional catch all routes
완료
import { useState, useEffect } from 'react';
const useMediaQuery = (query: string) => {
const [matches, setMatches] = useState(false);
const handleChange = (e: { matches: boolean }) => {
setMatches(e.matches);
};
useEffect(() => {
const matchQueryList = window.matchMedia(query);
matchQueryList.addEventListener('change', handleChange);
return () => {
matchQueryList.removeEventListener('change', handleChange);
};
}, [query]);
return matches;
};
export default useMediaQuery;
안드로이드 블루투스 스캔, 데이터 퍼오기, bluefi 브릿지만들기 멀티커넥션 할때 disconnect안되는거 scan에 uuid값 안넣으면 어트케 되는지
deeplink
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="telepodsee"/>
</intent-filter>
app.tsx
import React from 'react';
import { Button, LogBox, Text, TextInput, View } from 'react-native';
import BootSplash from 'react-native-bootsplash';
import CodePush from 'react-native-code-push';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import VersionCheck from 'react-native-version-check';
import { QueryClient, QueryClientProvider } from 'react-query';
import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { httpClientIntegration } from '@sentry/integrations';
import * as Sentry from '@sentry/react-native';
import dayjs from 'dayjs';
import 'dayjs/locale/ko';
import { PDErrorModal, PDLoading } from '@/domains/common/components';
import GlobalDataFetcher from '@/domains/common/components/GlobalDataFetcher';
import Splash from '@/domains/common/components/Splash';
import OverlayContext from '@/domains/common/context/OverlayContext.tsx';
import { navigationRef } from '@/navigation/RootNavigation';
import RootStack from '@/navigation/RootStack.tsx';
import { sentryIgnoreError } from '@/lib/config/constants';
dayjs.locale('ko');
// OS 글자 크기 조정 제한
// @ts-ignore
if (Text.defaultProps == null) {
// @ts-ignore
Text.defaultProps = {};
// @ts-ignore
Text.defaultProps.allowFontScaling = false;
// @ts-ignore
TextInput.defaultProps = TextInput.defaultProps || {};
// @ts-ignore
TextInput.defaultProps.allowFontScaling = false;
}
if (!__DEV__) {
CodePush.getUpdateMetadata().then(update => {
let codepushVersion = '';
if (update) codepushVersion = update.label;
Sentry.init({
dsn: 'https://6ecc487cbdfd3c5523ecfe8fd446b6f0@o4506980764483584.ingest.us.sentry.io/4506980765204480',
tracesSampleRate: 1,
ignoreErrors: sentryIgnoreError,
release: `${VersionCheck.getCurrentVersion()}@${VersionCheck.getCurrentBuildNumber()}+codepush:${codepushVersion}`,
integrations: [
httpClientIntegration({
failedRequestStatusCodes: [[500, 599]],
}),
],
});
});
}
const codePushOptions = {
checkFrequency: CodePush.CheckFrequency.MANUAL,
};
LogBox.ignoreLogs(['ViewPropTypes', 'FirmwarePrepareModule', 'code-push']);
const queryClient = new QueryClient();
const NavigationDefaultTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
primary: 'red',
background: '#f1f2f3',
},
};
const NavigationDarkTheme = {
...DarkTheme,
colors: {
...DarkTheme.colors,
primary: 'blue',
background: '#1C1D23',
},
};
function App(): React.JSX.Element {
// const isDarkMode = true; // 다크 고정
// const { isLogin } = useUserInfoStore();
// const { podAllList, setRecentPodVersion } = usePodStore();
// const { setIsLoading, setLoadingMsg } = useLoadingStore();
// const { setIsSplash, setCodePushProgress, setIsViewCodePushProgress } = useInitStore();
// useEffect(() => {
// setIsLoading(false);
// setLoadingMsg('');
// setIsSplash(true);
// setCodePushProgress({
// receivedBytes: 0,
// totalBytes: 0,
// });
// setIsViewCodePushProgress(false);
// }, []);
// useEffect(() => {
// if (!isLogin) return;
// // podAllList의 length가 0이여도 기존에 실행되고있는 ble를 취소해야함으로 실행한다.
// handleBackgroundBleTask();
// }, [podAllList]);
// const handleBackgroundBleTask = async () => {
// try {
// console.log('\x1b[31m[podAllList]\x1b[0m', podAllList);
// const isEmulator = await DeviceInfo.isEmulator();
// if (isEmulator) return;
// const credentials = await Keychain.getGenericPassword({ service: 'access_token' });
// const accessToken = credentials ? credentials.password : '';
// accessToken && startBackground(accessToken);
// } catch (error) {
// console.log(error);
// }
// };
// const startBackground = (accessToken: string) => {
// Platform.OS === 'android' ? NativeModules.BleController.handleAndBleBackground(podAllList, accessToken) : NativeModules.BluetoothModule.handleBluetoothScan(podAllList, accessToken);
// };
// useEffect(() => {
// const serverHealthCheck = async () => {
// const { status, data } = await axiosRequest.get<{ ios: number; android: string; status: string; iosBuildNumber: number; androidBuildNumber: number; pod: string }>({ url: '/api/status' });
// if (status === 200) {
// console.log('===============[ Health Check Start ]===============');
// console.log('https://telepodsee.com 서버 status', status);
// console.log('현재버전', `${VersionCheck.getCurrentVersion()} (${VersionCheck.getCurrentBuildNumber()})`);
// console.log('강제업데이트 버전', data.iosBuildNumber);
// console.log('강제업데이트 버전', data.androidBuildNumber);
// if (data.pod) {
// setRecentPodVersion(data.pod);
// }
// // 임시 버전 체크
// if (Platform.OS === 'ios') {
// // if (VersionCheck.getCurrentBuildNumber() < data.iosBuildNumber) {
// // Alert.alert('업데이트 후 테스트해주세요~!!');
// // }
// } else {
// if (VersionCheck.getCurrentBuildNumber() < data.androidBuildNumber) {
// Alert.alert('업데이트 후 테스트해주세요~!!');
// }
// }
// // const versionCheck = async () => {
// // // 마지막 버전 체크는 마켓버전 없으면 undefined 로 나옴
// // // const versionStatus = await VersionCheck.needUpdate();
// // // console.log('버전 체크', versionStatus);
// // // setLatest(!versionStatus?.isNeeded);
// //
// // console.log('현재버전', VersionCheck.getCurrentVersion());
// // if (VersionCheck.getCurrentBuildNumber() < 52) {
// // Alert.alert('업데이트 후 테스트해주세요~!');
// // }
// // };
// console.log('================[ Health Check End ]================');
// }
// };
// Appearance.setColorScheme('dark'); // 고정
// serverHealthCheck();
// // Push Foreground Message
// const unsubscribe = messaging().onMessage(async remoteMessage => {
// // console.log('message:', remoteMessage);
// await displayNotification(remoteMessage);
// });
// return unsubscribe;
// }, []);
// const linking = {
// // Prefixes accepted by the navigation container, should match the added schemes
// prefixes: ['telepodsee://'],
// // Route config to map uri paths to screens
// config: {
// // Initial route name to be added to the stack before any further navigation,
// // should match one of the available screens
// // initialRouteName: 'InitPermissionScreen' as const,
// // screens: {
// // LoginNJoinNavigation: {
// // initialRouteName: 'LoginScreen',
// // screens: {
// // LoginScreen: 'login',
// // },
// // },
// // },
// },
// } as any;
const linking = {
// Prefixes accepted by the navigation container, should match the added schemes
prefixes: ['telepodsee://'],
// Route config to map uri paths to screens
config: {
// Initial route name to be added to the stack before any further navigation,
// should match one of the available screens
initialRouteName: 'Home' as const,
screens: {
// myapp://home -> HomeScreen
Home: {
path: 'home',
},
// myapp://details/1 -> DetailsScreen with param id: 1
Profile: {
path: 'profile/:id',
parse: {
id: (id: string) => `${id}`,
},
},
Settings: {
path: 'settings',
},
},
},
};
const Stack = createNativeStackNavigator();
return (
<QueryClientProvider client={queryClient}>
<SafeAreaProvider>
<NavigationContainer
ref={navigationRef}
onReady={() => {
BootSplash.hide();
}}
linking={linking}
>
<GestureHandlerRootView style={{ flex: 1 }}>
<OverlayContext>
<GlobalDataFetcher />
{/* <RootStack /> */}
<Stack.Navigator>
<Stack.Screen name="Settings" component={DD} />
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={DetailsScreen} />
</Stack.Navigator>
<PDErrorModal />
<PDLoading />
<Splash />
</OverlayContext>
</GestureHandlerRootView>
</NavigationContainer>
</SafeAreaProvider>
</QueryClientProvider>
);
}
export default __DEV__ ? App : Sentry.wrap(CodePush(codePushOptions)(App));
/**
* https://velog.io/@minwoo129/React-Native%EC%97%90%EC%84%9C-CodePush-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0
*/
function HomeScreen({ navigation }) {
return (
<View>
<Text>Grocery List</Text>
{/* FlatList to render the list of grocery items */}
{/* <FlatList
style={styles.list}
data={groceryItems}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<TouchableOpacity style={{ padding: 10, borderBottomWidth: 1 }} onPress={() => navigation.navigate('Details', { id: item.id })}>
<Text>{item.name}</Text>
</TouchableOpacity>
)}
/> */}
</View>
);
}
function DD({ navigation }) {
return (
<View>
<Text>DDDD List</Text>
{/* FlatList to render the list of grocery items */}
{/* <FlatList
style={styles.list}
data={groceryItems}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<TouchableOpacity style={{ padding: 10, borderBottomWidth: 1 }} onPress={() => navigation.navigate('Details', { id: item.id })}>
<Text>{item.name}</Text>
</TouchableOpacity>
)}
/> */}
</View>
);
}
function DetailsScreen({ route, navigation }) {
return (
<View>
<Text>
Item:
{/* {groceryItems.find(item => item.id === Number(route.params.id))?.name ?? 'Not Found'} */}
</Text>
<Button title="Back" onPress={() => navigation.goBack()} />
</View>
);
}
npx uri-sheme add 해도 ios의 경우 백그라운드나 포그라운드에 있을때 scheme open으로 uri이 바뀌지 않는다. 그때는 https://reactnavigation.org/docs/deep-linking/#set-up-with-bare-react-native-projects 여기대로 Ios 세팅을 바꿔줘야한다.
code push target version이 다르면 암만 배포해도 안된다