묻고 답해요
161만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨한 입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지
React 컴포넌트 생성 시 속성 할당 필요 문제
-
해결됨웹 애니메이션을 위한 GSAP 가이드 Part.03
GSAP 플러그인의 무료화 관련
안녕하세요, 파트1부터 3까지 완강한 1인입니다. 파트3을 듣던 당시에는 GSAP ScrollSmoother 플러그인이 유료였는데, 얼마전에 모든 플러그인이 무료로 전환되었더라고요~ 그래서 강의 당시에 일부 플러그인들을 대체해서 강의해주신 부분이 있었는데,혹시 해당 부분에 대해서 추후 내용이 업데이트될 예정이 있을지 문의드립니다. ps. 모든 플러그인이 무료로 전환되면서 좀더 다양한 애니메이션이 가능해지니, 추후 파트4도 매우 기대가 되는 중입니다.
-
해결됨한 입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지
"Create-투두 추가하기" 강의에서 onKeyDown에 관련된 질문
"Create-투두 추가하기" 강의 11분쯤에 나오는 엔터로도 버튼 실행하기에 관해 질문이 있습니다. onKeyDown 함수를 추가하기전엔 input박스에서 한버튼을 꾹 누르고있으면 연속해서 값이 입력 되었었는데,강의와 같이 onKeyDown 함수를 추가하고 나면 한가지 키를 꾹 누르고 있어도 하나만 입력 됩니다.(ex. E버튼을 꾹 누르는 경우,"EEEEEEEEEEEEE" 이렇게 입력 되었었는데,onKeyDown을 추가하면 "E" 이렇게 하나만 입력됩니다.) 엔터로 버튼을 실행하면서한가지 키를 꾹 눌러서 연속적으로 값도 입력할 수 있는 방법이 있는지 궁금합니다. 감사합니다.
-
미해결처음 만난 리액트(React)
존재하지 않는 수업이라고 떠요
이라고 뜨고 첫번째 수업으로 이동도 되지않아요
-
미해결한 입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지
5.8) State로 사용자 입력 관리하기 1
강의 6분쯤에 "" 으로 공백이었던 useState의 초깃값을 "이름"으로 바꿔주면서 value={name} 구문을 추가해주셨는데요, 공백일 때에는 같은 문자열임에도 해당 구문 없이도 잘 작동했는데 "이름"으로 설정했을 때에는 해당 구문이 필요한 이유는 무엇인가요? 지피티에 물어보니 Uncontrolled와 controlled input이라는 새로운 개념을 언급하길래 잘 이해가 안 되어 여쭤봅니다!
-
해결됨한 입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지
npm init이 안됩니다 ㅠ
해당 오류 발생하여 아래와 같이 power shell에서 RemoteSigned 으로 변경하였습니다..다른 방법이 있을까요 ㅠ
-
미해결TS/JS 디자인 패턴 with Canvas: 제로초에게 제대로 배우기
커맨드 패턴 적용
디자인 패턴을 공부하면서 실제 구현중인 서비스에 적용해보려고 노력중인데(위 이미지는 예시 코드)예시 처럼 작성했을 때의 실효성이 invoker에서 audit log 같은 공통 코드 추출하는것 이외에 잘 느껴지지 않는데, 적절하지 않은 부분에 적용하려해서 그런것일까요?-> 단축키 예시처럼 해당 커맨드를 다른곳에서'도' 사용한다면 유용할것도 같네요!!추가로 ValidateLeadFieldCommand, CreateLeadCommand 이런식으로 여러 커맨드가 순차로 실행해야하는 경우에 invoker도 커맨드마다 만들어야할까?하는 고민도 듭니다!
-
미해결자바스크립트 알고리즘 문제풀이 입문(코딩테스트 대비)
반복문 최소화하고 indexOf 사용해서 풀어봤습니다
export default function solution(arr) { let answer = 0; const m = arr.length; const n = arr[0].length; for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { let cnt = 0; for (let k = 0; k < m; k++) { if (arr[k].indexOf(arr[0][i]) < arr[k].indexOf(arr[0][j])) cnt++; if (cnt === m) answer++; } } } return answer; }
-
해결됨이거 하나로 종결-스프링 기반 풀스택 웹 개발 무료 강의
몇가지 질문드립니다.
안녕하세요 우선 좋은 강의 정말 감사합니다. 풀스텍 강의라서 더 좋은거 같은데, 메일로 알람을 받았는데 선생님 강의 25%할인은 언제까지인가요? 지금 유료 강의 25%해서 24000원대 할인 기간이 안나와 있어서 기간이 궁금합니다. 아직 들어보지 못해서 무료 강의 한번 들어보고 유료강의도 진행하고 싶어서요 그리고 프로그램은 이클립스만 사용하시는거 같은데, 저는 인텔리제이 사용중인데 프로그램이 달라서인텔리제이 사용자도 괜찮을까요?
-
미해결한 입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지
npm run build 시 search 쪽에서 에러로 인해 build가 안됩니다.
git : https://github.com/ture403/next_appRoute 입니다.현재버전 :에러:이건 search/page.tsx 입니다.import BookItem from "@/components/book-item"; import BookListSeletion from "@/components/skeleton/book-list-seletion"; import { BookData } from "@/types"; import { delay } from "@/util/delay"; import { Suspense } from "react"; async function SearchResult({ q }: { q: string }) { await delay(1500); const res = await fetch(`${process.env.NEXT_PUBLIC_API_SEVER_URL}/book/search?q=${q}`); if (!res.ok) { return <div>오류가 발생했습니다.</div>; } const searchDatas: BookData[] = await res.json(); console.log(searchDatas); return ( <div> {searchDatas.map((book) => ( <BookItem key={book.id} {...book} /> ))} </div> ); } export default async function Page({ searchParams }: { searchParams: Promise<{ q?: string }> }) { const params = await searchParams; // searchParams Promise를 await 합니다. const query = params.q || ""; // q 값을 추출하고, 없으면 빈 문자열을 사용합니다. return ( <Suspense key={query} fallback={<BookListSeletion count={3} />}> <SearchResult q={query} /> </Suspense> ); } 봐주시면 감사겠습니다.
-
미해결프론트엔드 개발의 Kick, Web API (feat. React)
강의 영상 질문
5,7,8,10,11,12,14,15,17,18,19번 강의가음성만 있고 화면에 보여지는게 없는데,의도된걸까요..?저만 그런건지,,,ㅠㅠ
-
해결됨한 번에 끝내는 자바스크립트: 바닐라 자바스크립트로 SPA 개발까지
최종프로젝트 상세페이지 출력 오류 문의드립니다!
세션 9. 최종 프로젝트cityDetail 개발-2 에서https://trip-wiki-api.vercel.app/ 강사님이 알려주셨던 링크에는 상세페이지 이미지와 받는 정보가 없습니다! 어떻게 상세페이지를 출력할 수 있나요? Japan을 검색창에서 검색하고 상세페이지를 눌렀지만 아무런 정보가 출력되지 않고 있습니다! import Header from "./components/Header.js"; import RegionList from "./components/RegionList.js"; import CityList from "./components/CityList.js"; import CityDetail from "./components/CityDetail.js"; import { request, requestCityDetail } from "./components/api.js"; export default function App($app) { const getSortBy = () => { if (window.location.search) { return window.location.search.split("sort=")[1].split("&")[0]; } return "total"; }; const getSearchWord = () => { if (window.location.search && window.location.search.includes("search=")) { return window.location.search.split("search=")[1]; } return ""; }; this.state = { startIdx: 0, sortBy: getSortBy(), region: window.location.pathname.replace("/", ""), searchWord: getSearchWord(), cities: "", currentPage: window.location.pathname, }; const renderHeader = () => { new Header({ $app, initialState: { currentPage: this.state.currentPage, sortBy: this.state.sortBy, searchWord: this.state.searchWord, }, handleSortChange: async (sortBy) => { const pageUrl = `/${this.state.region}?sort=${sortBy}`; history.pushState( null, null, this.state.searchWord ? pageUrl + `&search=${this.state.searchWord}` : pageUrl ); const cities = await request( 0, this.state.region, sortBy, this.state.searchWord ); this.setState({ ...this.state, startIdx: 0, sortBy: sortBy, cities: cities, }); }, handleSearch: async (searchWord) => { history.pushState( null, null, `/${this.state.region}?sort=${this.state.sortBy}&search=${searchWord}` ); const cities = await request( 0, this.state.region, this.state.sortBy, searchWord ); this.setState({ ...this.state, startIdx: 0, cities: cities, searchWord: searchWord, }); }, }); }; const renderRegionList = () => { new RegionList({ $app, initialState: this.state.region, handleRegion: async (region) => { history.pushState(null, null, `/${region}?sort=total`); const cities = await request(0, region, "total"); this.setState({ ...this.state, startIdx: 0, sortBy: "total", region: region, cities: cities, searchWord: "", currentPage: `/${region}`, }); }, }); }; const renderCityList = () => { new CityList({ $app, initialState: this.state.cities, handleItemClick: async (id) => { history.pushState(null, null, `/city/${id}`); this.setState({ ...this.state, currentPage: `/city/${id}`, }); }, handleLoadMore: async () => { const newStartIdx = this.state.startIdx + 40; const newCities = await request( newStartIdx, this.state.region, this.state.sortBy ); this.setState({ ...this.state, startIdx: newStartIdx, cities: { ...this.state.cities, cities: [...this.state.cities.cities, ...newCities.cities], isEnd: newCities.isEnd, }, }); }, }); }; const renderCityDetail = async (cityId) => { try { const cityDetailData = await requestCityDetail(cityId); new CityDetail({ $app, initialState: cityDetailData }); } catch (err) { console.log(err); } }; const render = async () => { const path = this.state.currentPage; $app.innerHTML = ""; // 상세 페이지로 이동 if (path.startsWith("/city/")) { const cityId = path.split("/city/")[1]; renderHeader(); renderCityDetail(cityId); } else { renderHeader(); renderRegionList(); renderCityList(); } }; this.setState = (newState) => { this.state = newState; render(); }; const init = async () => { const path = this.state.currentPage; // 메인 페이지 if (!path.startsWith("/city")) { const cities = await request( this.state.startIdx, this.state.region, this.state.sortBy, this.state.searchWord ); this.setState({ ...this.state, cities: cities, }); } // 상세페이지 else { render(); } }; window.addEventListener("popstate", async () => { const urlPath = window.location.pathname; const prevRegion = urlPath.replace("/", ""); const prevPage = urlPath; const prevSortBy = getSortBy(); const prevSearchWord = getSearchWord(); const prevStartIdx = 0; const prevCities = await request( prevStartIdx, prevRegion, prevSortBy, prevSearchWord ); this.setState({ ...this.state, startIdx: prevStartIdx, sortBy: prevSortBy, region: prevRegion, currentPage: prevPage, searchWord: prevSearchWord, cities: prevCities, }); }); init(); } //API.js 코드입니다!export default function CityDetail({ $app, initialState }) { this.state = initialState; this.$target = document.createElement("div"); this.$target.className = "city-detail"; $app.appendChild(this.$target); const getScoreColor = (score) => { // let scoreNumber = parseInt(score); if (score >= 4) return "green"; if (score >= 3) return "yellow"; return "red"; }; this.template = () => { let cityData = this.state.CityDetail; let temp = ``; if (cityData) { temp = `<div class="image-banner"> <img src="${cityData.image}"/> <div class="city-name"> <div class="city">${cityData.city}</div> <div class="country">${cityData.region} / ${cityData.country}</div> </div> </div> <div class="progress-container"> <div class="info-item"> <div class="label">⭐ Total Score</div> <div class="progress-bar" score-color="${getScoreColor( cityData.total )}" style="--score:${cityData.total * 20}%"></div> </div> <div class="info-item"> <div class="label">💵 Cost</div> <div class="progress-bar" score-color="${getScoreColor( cityData.info.cost )}" style="--score:${cityData.info.cost * 20}%"></div> </div> <div class="info-item"> <div class="label">😆 Fun</div> <div class="progress-bar" score-color="${getScoreColor( cityData.info.fun )}" style="--score:${cityData.info.fun * 20}%"></div> </div> <div class="info-item"> <div class="label">🚓 Safety</div> <div class="progress-bar" score-color="${getScoreColor( cityData.info.safety )}" style="--score:${cityData.info.safety * 20}%"></div> </div> <div class="info-item"> <div class="label">🌐 Internet</div> <div class="progress-bar" score-color="${getScoreColor( cityData.info.internet )}" style="--score:${cityData.info.internet * 20}%"></div> </div> <div class="info-item"> <div class="label">💨 Air Condition</div> <div class="progress-bar" score-color="${getScoreColor( cityData.info.air )}" style="--score: ${cityData.info.air * 20}%"></div> </div> <div class="info-item"> <div class="label">🍜 Food</div> <div class="progress-bar" score-color="${getScoreColor( cityData.info.food )}" style="--score: ${cityData.info.food * 20}%"></div> </div> </div> `; } return temp; }; this.render = () => { this.$target.innerHTML = this.template(); }; /* this.setState = (newState) => { this.state = newState; this.render(); }; */ this.render(); } //cityDetail.js 상세페이지 코드입니다!
-
미해결TailwindCSS 완벽 마스터: 포트폴리오부터 어드민까지!
모바일 사이즈에 대해 질문있습니다.
안녕하세요 테일윈드 강의에서 반응형 부분의 강의를 보다가 궁금한 점이 생겨 여쭤보고 싶어 글을 남깁니다. 예를 들어 모바일 청첩장처럼 모바일사이즈만 사용할 경우 리액트에서는 처음에 라이브러리를 사용하지 않고 모바일 사이즈로만 설정하는 방법이 궁금합니다. 그리고 테일윈드에서 모바일 사이즈만 할 경우에는 sm 이 부분만 사용을 설정하면 되는지 궁금합니다.
-
해결됨한 입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지
vercel 터미널 설치 문제
터미널에 vercel을 설치하려는데자꾸 이렇게 뜨고제가 맥os를 사용중이라 sudo npm i -g vercel 이것도 넣어봤더니 패스워드 입력하라고 뜨는데 입력이 아예 안 되네요.. 그냥 엔터 누르면 다시 시도하라고 뜨는데어떻게 해야 설치 가능한가요?ㅜㅜ
-
미해결JavaScript 베이스캠프
마지막 api안되서 인터넷으로 찾아서 해봤는데 안되네요 이게 코드가 뭐가 문제일까요?
async function getData() { const jsonData = [ { id: 1, productName: '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', price: 12500, stokeCount: 100, thumbnailImg: 'assets/img1.jpeg', }, { id: 2, productName: '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', price: 12500, stokeCount: 100, thumbnailImg: 'assets/img2.jpeg', }, { id: 3, productName: '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', price: 12500, stokeCount: 100, thumbnailImg: 'assets/img3.jpeg', }, { id: 4, productName: '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', price: 12500, stokeCount: 100, thumbnailImg: 'assets/img4.jpeg', }, { id: 4, productName: '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', price: 12500, stokeCount: 100, thumbnailImg: 'assets/img4.jpeg', }, { id: 5, productName: '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', price: 12500, stokeCount: 100, thumbnailImg: 'assets/img5.jpeg', }, { id: 6, productName: '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', price: 12500, stokeCount: 100, thumbnailImg: 'assets/img6.jpeg', }, ]; const response = new Response(JSON.stringify(jsonData), { status: 200, statusText: 'OK', headers: { 'Content-Type': 'application/json', }, }); fetch('https://test.api.weniv.co.kr/mall') .then((res) => { if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } return re.json(); //응답을 json으로 파싱 }) .then((data) => console.log(data)) .catch((error) => console.log('Error:', error)); const productData = await response.json(); return productData; } function createProductCard(product) { const card = document.createElement('section'); card.classList.add('card'); card.innerText = 'hello world'; return card; } function render(product) { const container = document.querySelector('.container'); container.innerHTML = ''; products.forEach((product) => { const card = createProductCard(product); container.appendChild(card); }); } async function main() { const productData = await getData(); render(productData); }
-
해결됨웹 프론트엔드를 위한 자바스크립트 첫걸음
북마크바 디폴트가 "북마크 열기"로 뜹니다.
강의 : 5. 북마크바 만들기북마크바 디폴트 화면이 '북마크 열기'(bookmark-close)로 뜨면서 북마크가 열려있는 상태(?)로 나옵니다.그리고 북마크 열기를 클릭하면, 의도한 바처럼 '북마크 열기'(bookmark-close)가 뜨면서 북마크가 닫힌 상태로 바뀌고, 다시 클릭 시 '북마크 닫기'(bookmark-open)가 뜨고 북마크가 열린 상태로 나옵니다.즉, 두번째 액션부터 정상적으로 작동이 되는데요 ㅠㅠ [디폴트 화면] 효빈 강사님께서 작성한 예시 그대로 작업했는데 뭐가 문제인걸까요??<!-- 북마크 열린 상태 --> <div class="bookmark-open" id="bookmark-open"> <div id="bookmark-open-btn">북마크 닫기</div> </div> <!-- 북마크 닫힌 상태 --> <div class="bookmark-close" id="bookmark-close"> <div id="bookmark-close-btn">북마크 열기</div> </div> <div class="bookmark-bar" id="bookmark-bar"> <!-- 북마크 바 --> </div>.bookmark-open { display: flex; justify-content: flex-end; position: absolute; top: 0; right: 0; width: 240px; height: 20px; padding: 15px 30px; font-size: var(--xsmall); background-color: rgba(0, 0, 0, 0.5); color: #fff; } .bookmark-close { display: flex; justify-content: flex-end; position: absolute; top: 0; right: 0; width: 240px; height: 20px; padding: 15px 30px; font-size: var(--xsmall); background-color: rgba(0, 0, 0, 0.5); color: #fff; } .bookmark-open div { cursor: pointer; } .bookmark-close div { cursor: pointer; } .bookmark-bar { position: absolute; top: 50px; right: 0; min-height: calc(100vh - 70px); width: 240px; padding: 10px 30px; background-color: rgba(0, 0, 0, 0.5); color: #fff; }const bookMarkBar = document.getElementById("bookmark-bar"); const Open = document.getElementById("bookmark-open"); const Close = document.getElementById("bookmark-close"); // 북마크 바 열기 및 닫기 const bookmarkBarToggle = () => { const isBookMarkBarOpen = localStorage.getItem("isBookMarkBarOpen"); if (isBookMarkBarOpen) { if (isBookMarkBarOpen === "open") { localStorage.setItem("isBookMarkBarOpen", "close"); bookMarkBar.style.display = "none"; Open.style.display = "none"; Close.style.display = "flex"; } else { localStorage.setItem("isBookMarkBarOpen", "open"); bookMarkBar.style.display = "block"; Open.style.display = "flex"; Close.style.display = "none"; } } else { localStorage.setItem("isBookMarkBarOpen", "close"); bookMarkBar.style.display = "none"; Open.style.display = "none"; Close.style.display = "flex"; } }; document .getElementById("bookmark-open-btn") .addEventListener("click", bookmarkBarToggle); document .getElementById("bookmark-close-btn") .addEventListener("click", bookmarkBarToggle);
-
미해결JavaScript 베이스캠프
마지막 과제 api 접속이 안되서 fetch.json만들어서 했는데 안되네요ㅜ 어떻게 해야하죠?
[ { "id": 1, "productName": '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', "price": 12500, "stokeCount": 100, "thumbnailImg": 'assets/img1.jpeg' }, { "id": 2, "productName": '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', "price": 12500, "stokeCount": 100, "thumbnailImg": 'assets/img2.jpeg' }, { "id": 3, "productName": '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', "price: 12500", "stokeCount": 100, "thumbnailImg": 'assets/img3.jpeg' }, { "id": 4, "productName": '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', "price": 12500, "stokeCount": 100, "thumbnailImg": 'assets/img4.jpeg' }, { "id": 4, "productName": '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', "price": 12500, "stokeCount": 100, "thumbnailImg": 'assets/img4.jpeg' }, { "id": 5, "productName": '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', "price": 12500, "stokeCount": 100, "thumbnailImg": 'assets/img5.jpeg' }, { "id": 6, "productName": '버그를 Java라 버그잡는 개리씨 키링 개발자키링 금속키링', "price": 12500, "stokeCount": 100, "thumbnailImg": 'assets/img6.jpeg' }, ];
-
해결됨한 입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지
사용자 입력값으로 input 수정시 문자열을 date객체로 바꾸는 이유
const getStringedDate = (targetDate) => { let year = targetDate.getFullYear(); let month = targetDate.getMonth() + 1; let date = targetDate.getDate(); if (month < 10) { month = `0${month}`; } if (date < 10) { date = `0${date}`; } return `${year}-${month}-${date}`; };초기값 설정 할 때 input 태그가 date 객체를 해석하지 못해서 위의 getStringedDate() 함수로 문자열로 변환하여 value 속성으로 넣어줘야 한다고 하셨는데요,사용자 입력값을 넣을 때는 input을 수정하면 문자열로 들어가기 때문에 다시 date 객체로 바꿔주는 함수를 새로 사용해야 하던데<input name="createdDate" onChange={onChangeInput} value={getStringedDate(input.createdDate)} type="date" />이미 여기서 getStringedDate()로 객체에서 문자열 변환하는 함수를 사용해서 값을 나타내야하기 때문에 onChangeInput() 함수를 사용해야 하는건가요?이 흐름이 맞는 거 같긴한데 왠지 좀 번거로운거 같아서 제가 이해한게 맞는지 궁금해서요 참고로 섹션13 12.14)강의의10:22 부분을 듣고 생긴 질문입니다!
-
미해결Vue.js 중급 강좌 - 웹앱 제작으로 배워보는 Vue.js, ES6, Vuex
Chrome 개발자 모드 확장이 안됨
Vue 3 시작하기 강의에서 알려주신 legacy 버전 설치해도 개발자 모드에서 vue 탭이 안보이고 아래와 같은 에러가 발생합니다.
-
해결됨한 번에 끝내는 자바스크립트: 바닐라 자바스크립트로 SPA 개발까지
CityList.js에서 api.js 파일의 API_URL이 렌더링이 안되는 오류가 있습니다!
세션 9. 여행지정보 최종프로젝트 작성 중...CityList 개발 13:25분에서 CityList.js를 작성한 후 localhost:3000을 새로고침해도 전혀 변화없이 빈 화면만 출력이 됩니다!api.js에서 API_URL로 전달받을 https://trip-wiki-api.vercel.app/ 에서 도시 정보 40개 정보가 없더라고요, 그래서 웹 화면에 아무것도 출력되지 않아서 문의드립니다! server.js 파일도 /*splat 로 변경해서 "start server" 노드 명령어로 터미널에서 출력되도록 수정 완료했지만, 홈페이지 이미지가 출력되지 않고 있습니다!const express = require("express"); const path = require("path"); const app = express(); const PORT = 3000; app.use(express.static(path.join(__dirname, ".."))); app.get("/*splat", (req, res) => { res.sendFile(path.join(__dirname, "..", "index.html")); }); app.listen(PORT, () => { console.log("START SEVER"); }); export default function CityList({ $app, initialState }) { this.state = initialState; this.$target = document.createElement("div"); this.$target.className = "city-list"; $app.appendChild(this.$target); this.template = () => { let temp = `<div class="city-items-container">`; if (this.state) { this.state.cities.forEach((elm) => { temp += ` <div class="city-item" id=${elm.id}> <img src=${elm.image}></img> <div class="city-item-info">${elm.city}, ${elm.country}</div> <div class="city-item-score">⭐ ${elm.total}</div> </div> `; }); temp += `</div>`; } return temp; }; this.render = () => { this.$target.innerHTML = this.template(); }; this.setState = (newState) => { this.state = newState; this.render(); }; this.render(); }