Öncelikle, lütfen soru hazırlarken biraz daha özenli olun.
Kısaca cevap: Hatanın alındığı satıra bakmak gerek.
Uzunca cevap:
Derlenmiş kod üzerinden yorum yapalım.
Model.Products.Count = 0 geldiğini varsayalım.
Bu durumda istemciye iletilen sayfa şu şekilde olur:
<script type="text/javascript">
document.querySelector('#add-item').addEventListener('click', function() {
document.querySelector('#orderTable tbody').innerHTML += `
<tr>
<td>
<select class="form-select" name="OrderDetail.ProductId">
<option>Hiçbir ürün yok</option>
</select>
</td>
<td>
<div class="m-0">
<input class="form-control" name="OrderDetail.Quantity" value="" />
</div>
</td>
<td>
<button type="button" class="btn btn-outline-danger" id="btn_remove">
Öğeyi Sil
</button>
</td>
</tr>
`;
});
document.querySelector('#btn_remove').addEventListener('click', function() {
var row = document.querySelector("#orderTable").rows.length - 1;
document.querySelector('#orderTable tbody').removeChild();
});
</script>
Burada unexpected number hatasını verebilecek, number'larla işi olan gördüğüm kadarıyla sadece şu kod var:
var row = document.querySelector("#orderTable").rows.length - 1;
O halde document.querySelector("#orderTable").rows.length kodu bir number dönmüyor demektir.
.length kodu array'ler için mutlaka bir sayı döner.
O halde document.querySelector("#orderTable").rows değeri bir array değil.
Normalde <table /> elementleri için .rows kodu mutlaka bir array döner. Eğer seçici (document.querySelector("#orderTable")), <table /> dışında bir element seçmiş olsaydı null dönerdi ve null.rows gibi bir komuta dönüşürdü. Bu da Uncaught TypeError: Cannot read properties of null (reading 'rows') gibi bir hata fırlatırdı.
O halde document.querySelector("#orderTable") kodu herhangi bir element dönmüyor. .querySelector() fonksiyonu seçicisi ile bir element bulamazsa null döner.
O halde unexpected number hatası doğrudan değil, dolaylı bir nedenle ortaya çıkıyor.
Yani hatanın alındığı satıra bakmak gerekir. Hata burada oluşmuyor gibi görünüyor.
Kurgusal olarak hatanın nerede olabileceğini düşününce aklıma şu geliyor:
#btn_remove id'li elemente click edildiğinde, DOM'da henüz bir #orderTable id'li element yoktur. Yani henüz DOM'a basılmamıştır.
Bu durumda row değişkeni NaN (Not a Number) değeri alır. Başka bir satırda row değeri ile bir işlem yapılmak istenirse, doğrudan değil ama dolaylı olarak bir hatalar silsilesi oluşup unexpected number hatasıyla karşılaşılabilir. Yani yine hatanın oluştuğu satıra, hatta varsa hatalar silsilesine bakmak gerek.
Bir diğer sorun da, #add-item elementine her basılışta #orderTable tbody elementi içine bir <tr /> elementi eklenmesiyle ilgili.
Çünkü eklenen her satırın içinde #btn_remove id'li bir buton da ekleniyor. Ama bir id değeri sayfa içinde sadece 1 tane olur. Burada, eklenen her satırın içinde aynı id'ye sahip birer buton oluşmuş oluyor.
document.querySelector('#btn_remove') ile seçim yapıldığında aslında her zaman ilk satırdaki buton seçilmiş oluyor.
Ama kodun paylaşılan kısmı için konuşursak, unexpected number hatası doğrudan bu nedenle oluşmuyordur. Ama buna bağlı dolaylı bir nedenle oluşuyor olabilr. Yani yine hatanın oluştuğu satıra bakmak gerek.
Bir diğer sorun da, document.querySelector('#btn_remove') seçicisi çalıştığı sırada henüz DOM'da #btn_remove id'li bir element olmaması.
Yani aslında bu komut çalıştığı sırada DOM'da hiç #btn_remove id'li element yoksa elementi seçmek mümkün değil. Böylece eklenen click olayı dinleyicisi de çalışmayacaktır. Hatta muhtemelen null.addEventListener(...) gibi bir yorumlama yapılacağı için yine bir TypeError alınıyordur. Ama tabi sayfada böyle bir element varsa o ayrı...
Tabi yine kodun paylaşılan kısmı için unexpected number hatası doğrudan bu nedenle oluşmuyordur.
SyntaxError: Unexpected number hatasıyla örneğin şöyle bir kodda karşılaşılır:
console.log(4 8 15 16 23 42);
Yani bu hata javascript'te syntax hatası varsa alınır. Yanlış bir operatör kullanımı veya number ve string arasına boşluk koymak gibi) olduğu durumlarda ortaya çıkar Kod yorumlanırken number beklenmeyen bir yerde number ile karşılaşılmasıyla bu hata fırlatılır.
Paylaştığın kodda syntax hatası oluşturacak bir yere rastlamadım. >>> Hatanın alındığı satıra bakmak gerek.
Öncelikle, lütfen soru hazırlarken biraz daha özenli olun.
Kodun düzenlenmiş hali şu şekilde:
<script type="text/javascript">
document.querySelector("#add-item").addEventListener("click", function()
{
document.querySelector("#orderTable tbody").innerHTML +=
'<tr>'+
'<td>'+
'<select asp-for="@Model.OrderDetail.ProductId" class="form-select" required>'+
@{
if (Model.Products.Count > 0)
{
<option>Bir ürün seçiniz</option>
@foreach (var item in Model.Products)
{
<option value="@item.Id">@item.Name</option>
}
}
else
{
<option>Hiçbir ürün yok</option>
}
}
'</select>'+
'</td>'+
'<td>'+
'<div class="m-0">'+
'<input asp-for="@Model.OrderDetail.Quantity" type="number" class="form-control" value="" />'+
'</div>'+
'</td>'+
'</tr>';
});
document.querySelector("#btn_remove").addEventListener("click", function(){
document.querySelector('#row').remove();
});
</script>
Diyelim ki;
Model.OrderDetail.ProductId = 123456
Model.Products.Count = 0
Model.OrderDetail.Quantity = 2
değerleri gelmiş olsun. Bu durumda sunucu bu dosyayı istemciye göndermeden önce aşağıdaki şekilde derler:
<script type="text/javascript">
document.querySelector("#add-item").addEventListener("click", function()
{
document.querySelector("#orderTable tbody").innerHTML +=
'<tr>'+
'<td>'+
'<select asp-for="123456" class="form-select" required>'+
<option>Hiçbir ürün yok</option>
'</select>'+
'</td>'+
'<td>'+
'<div class="m-0">'+
'<input asp-for="2" type="number" class="form-control" value="" />'+
'</div>'+
'</td>'+
'</tr>';
});
document.querySelector("#btn_remove").addEventListener("click", function(){
document.querySelector('#row').remove();
});
</script>
Görüldüğü gibi, derleme sonrasında <option>Hiçbir ürün yok</option> kodu, string birleştirmelerinin arasına giriyor. Bu kodu da string halde ve kendinden sonraki string'le birleşecek şekilde yazmak gerek.
Bunu yapmanın en garanti yolu string birleştirme yapmak yerine tüm ifadeyi ters tırnaklar arasında yazmak olabilir:
<script type="text/javascript">
document.querySelector('#add-item').addEventListener('click', function()
{
document.querySelector('#orderTable tbody').innerHTML += `
<tr>
<td>
<select asp-for="@Model.OrderDetail.ProductId" class="form-select" required>
@{
if (Model.Products.Count > 0)
{
<option>Bir ürün seçiniz</option>
@foreach (var item in Model.Products)
{
<option value="@item.Id">@item.Name</option>
}
}
else
{
<option>Hiçbir ürün yok</option>
}
}
</select>
</td>
<td>
<div class="m-0">
<input asp-for="@Model.OrderDetail.Quantity" type="number" class="form-control" value="" />
</div>
</td>
</tr>
`;
});
document.querySelector('#btn_remove').addEventListener('click', function(){
document.querySelector('#row').remove();
});
</script>
Bu durumda kod aşağıdaki gibi derlenip istemciye iletilir:
<script type="text/javascript">
document.querySelector('#add-item').addEventListener('click', function()
{
document.querySelector('#orderTable tbody').innerHTML += `
<tr>
<td>
<select asp-for="123456" class="form-select" required>
<option>Hiçbir ürün yok</option>
</select>
</td>
<td>
<div class="m-0">
<input asp-for="2" type="number" class="form-control" value="" />
</div>
</td>
</tr>
`;
});
document.querySelector('#btn_remove').addEventListener('click', function(){
document.querySelector('#row').remove();
});
</script>
$pdo = new PDO('mysql:host=localhost;dbname=veritabani_adi', 'kullanici_adi', 'sifre');
$query = "SELECT bkategoriler.sube, bkategoriler.kategori_adi, users.kullanici_adi FROM bkategoriler LEFT JOIN users ON bkategoriler.sube = users.sube";
$stmt = $pdo->prepare($query);
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($results as $row) {
echo "Kategori: " . $row['kategori_adi'] . " - Sube: " . $row['sube'] . " - Kullanıcı: " . $row['kullanici_adi'] . "<br />";
}
SELECT bkategoriler.sube, bkategoriler.kategori_adi, users.kullanici_adi: bkategoriler tablosundan sube ve kategori_adi sütunlarını, users tablosundan da kullanici_adi sütununu al.
FROM bkategoriler: bkategoriler tablosunu temel alarak işlem yap.
LEFT JOIN users ON bkategoriler.sube = users.sube: users tablosunu da şu eşleşmeye göre getir: bkategories'deki sube değeri ile users tablosundaki sube değeri aynı olmalı.
Böylece bkategoriler tablosundaki tüm satırlar döndürülecek.
users tablosunda da bkategoriler.sube = users.sube eşleşmesini sağlayan tüm satırlar, bkategoriler tablosuyla gelen satırlarla birlikte alınacak.
bkategoriler.sube değerinin users.sube ile eşleşmesinin bulunmadığı bir bkategoriler satırı varsa, users tablosundan gelen veriler NULL olarak gelecek.
Birbiriyle aynı olan sütun adları (id, sube gibi) olabileceği için Select * veya Select bkategoriler.*, users.* gibi seçimler yapamayız. Mecburen hangi tablodan hangi sütunları alacağımızı tek tek belirttik.
Peki aynı isimli sütunları almamız gerekseydi? Mesela iki tabloda da id sütunu varsa, o zaman bunları birer takma ad vererek çağırmalıydık.
Örn: SELECT bkategoriler.id, users.id as user_id, ... şeklinde yazarsak bkategoriler'in id sütunu id key'iyle döner. users'taki id değeri ise user_id key'iyle döner.
Bir post'u beğenmek için kullanıcının oturum açması gerekir.
Oturum açmış kullanıcının verileri elinde olduğu için çerez kullanmaya gerek kalmaz.
Eğer session yoluyla oturum açıyorsan kullanıcının verilerine backend'den ulaşabilirsin.
Yani frontend tarafında sana tek gereken, hangi id'li post'un beğenildiği.
Bunu da beğen butonuna bir attribute ekleyerek tutabilirsin. Örneğin:
<button class="comment-like" data-comment-id="<?=$commentId?>">
Beğen (<?=$commentLikeCount?>)
</button>
Bu butona basıldığında veritabanına kayıt için bir istek atman gerekecek.
const buttons = document.querySelectorAll('.comment-like');
// Her bir beğen butonuna click olay dineliycisi
buttons.forEach(button => {
button.addEventListener('click', function() {
const commentId = this.getAttribute('data-comment-id');
fetch('https://sitem.com/api/likecomment', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ commentId: commentId })
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
})
.catch(error => {
console.error('Error:', error);
});
});
});
Tabi bu "https://sitem.com/api/likecomment" isteğini yakalayacak bir sayfa da lazım.
Bunun için de sitenin kök klasörüne api klasörü altına likecomment klasörü altına index.php dosyası oluşturmak lazım.
<?php // api/likecomment
// Bu sayfamız yalnızca POST metoduyla gelen isteklere yanıt verecek.
// GET, PUT, DELETE gibi metodlarla gelen istekleri engelliyorum.
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header("HTTP/1.0 404 Not Found");
exit("Page not found.");
}
function jsonResponse($code, $result, $statusCode = 200) {
header("Content-Type: application/json");
http_response_code($statusCode);
exit(json_encode(['code' => $code, 'statusCode' => $statusCode, 'result' => $result]));
}
$userId = $_SESSION['user_id'] ?? null;
if(!$userId) jsonResponse('UNAUTHORIZED', null, 401);
$data = json_decode(file_get_contents("php://input"), true);
$commentId = $data['commentId'] ?? null;
if(!$commentId) jsonResponse('MISSING_PARAMETER', null, 400);
// Veritabanı bağlantısı ile ilgili yorumun beğeni sayısının 1 artırılması işlemleri...
jsonResponse('OK', $newCommentLikeCount, 200);
Veritabanı işlemleriyle ilgili kısım için de ChatGPT'den bir örnek istedim:
try {
// Veritabanı bağlantısı
$pdo = new PDO("mysql:host=localhost;dbname=your_database_name", "username", "password");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Kullanıcının bu yorumu daha önce beğenip beğenmediğini kontrol et
$stmt = $pdo->prepare("SELECT * FROM comment_likes WHERE user_id = :userId AND comment_id = :commentId");
$stmt->execute([':userId' => $userId, ':commentId' => $commentId]);
if ($stmt->rowCount() > 0) {
// Kullanıcı daha önce beğenmiş, beğeniyi geri al (LIKE_COUNT azalt)
$pdo->beginTransaction();
$stmt = $pdo->prepare("UPDATE COMMENTS SET LIKE_COUNT = LIKE_COUNT - 1 WHERE id = :commentId");
$stmt->execute([':commentId' => $commentId]);
// comment_likes tablosundan beğeni kaydını sil
$stmt = $pdo->prepare("DELETE FROM comment_likes WHERE user_id = :userId AND comment_id = :commentId");
$stmt->execute([':userId' => $userId, ':commentId' => $commentId]);
$pdo->commit();
// Yeni beğeni sayısını alın
$stmt = $pdo->prepare("SELECT LIKE_COUNT FROM COMMENTS WHERE id = :commentId");
$stmt->execute([':commentId' => $commentId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$newCommentLikeCount = $row['LIKE_COUNT'];
jsonResponse('OK', $newCommentLikeCount, 200); // Yanıtı döndür
} else {
// Kullanıcı daha önce beğenmemiş, beğeniyi ekle (LIKE_COUNT artır)
$pdo->beginTransaction();
$stmt = $pdo->prepare("UPDATE COMMENTS SET LIKE_COUNT = LIKE_COUNT + 1 WHERE id = :commentId");
$stmt->execute([':commentId' => $commentId]);
// comment_likes tablosuna yeni beğeni kaydı ekle
$stmt = $pdo->prepare("INSERT INTO comment_likes (user_id, comment_id) VALUES (:userId, :commentId)");
$stmt->execute([':userId' => $userId, ':commentId' => $commentId]);
$pdo->commit();
// Yeni beğeni sayısını alın
$stmt = $pdo->prepare("SELECT LIKE_COUNT FROM COMMENTS WHERE id = :commentId");
$stmt->execute([':commentId' => $commentId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$newCommentLikeCount = $row['LIKE_COUNT'];
jsonResponse('OK', $newCommentLikeCount, 200); // Yanıtı döndür
}
} catch (PDOException $e) {
$pdo->rollBack(); // Hata durumunda işlemleri geri al
jsonResponse('DATABASE_ERROR', $e->getMessage(), 500);
}
Yine ChatGPT'den aldım: Bu php sayfasının dönüşlerine göre yanıt verebilecek şekilde frontend'de oluşturduğum js kodunu da güncellemek gerek:
const buttons = document.querySelectorAll('.comment-like');
// Her bir beğen butonuna click olay dinleyicisi
buttons.forEach(button => {
button.addEventListener('click', function() {
const commentId = this.getAttribute('data-comment-id');
// Fetch ile POST isteği gönder
fetch('https://sitem.com/api/likecomment', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ commentId: commentId })
})
.then(response => {
if (!response.ok) {
// Hata durumunda status code'u kontrol et ve cevabı JSON olarak dön
return response.json().then(errorData => {
throw new Error('Request failed with status ' + response.status);
});
}
return response.json(); // Başarılı istek, JSON cevabı dön
})
.then(data => {
console.log('Success:', data);
// Beğeni sayısını başarılı istek sonrası butonun text'ine güncelle
if (data.code === 'OK') {
const newLikeCount = data.result;
this.textContent = `Beğen (${newLikeCount})`;
} else {
console.error('Error: Unexpected response code');
}
})
.catch(error => {
// Hata yönetimi
console.error('Error:', error);
alert('Bir hata oluştu: ' + error.message);
});
});
});
Umarım örnek olarak yardımcı olur :)
Evet, bu kez kayıt olabildim.
Aslında oturum başarılı şekilde açılıyor. Dashboard ekranına gitmek istiyor. Ama sonra oturumu doğrulayamayıp tekrar login ekranına redirect ediyor gibi görünüyor.
Bu sorun sanıyorum bir middleware'dan kaynaklanıyordur. Bir yandan da localhost'ta düzgün çalıştığını söylüyorsun.
Production ortamında domain farklı olabilir (localhost yerine vercel.app), bu yüzden corsOptions kısmında doğru domain'i ayarladığından emin olmak gerek.
process.env.CLIENT_HOST değerinin ne olduğuna bakmak gerekebilir. Bunu login ekranına yazdırıp görmeyi deneyebilirsin. Bu değer boş da olabilir, production ortamındaki domain'inden farklı da olabilir. Kontrol etmek gerek.
Bu domain farkı yüzünden cors sorunları yaşayıp dashboard sayfanda cookie'yi okuyamıyor olabilirsin.
const corsOptions = {
origin: process.env.CLIENT_HOST || 'https://socializee-app.vercel.app',
credentials: true,
};
Pek de Next.js tecrübem olmadığından böyle adım adım deneye yanıla ilerleyeceğiz artık :)
Aslında production ortamındaki tüm process.env değerlerini görebilsek güzel olurdu.
Gerçi ChatGPT sanırım Next.js'in son versiyonlardaki dosya hiyerarşisini henüz bilmiyor ama ChatGPT'nin aşağıda önerdiği yol işe yaramıyor mu?
Eğer www.domain.com/tr/urunler ve www.domain.com/tr/products URL'lerini aynı dosyadan yönetmek istiyorsanız, Next.js ve next-intl kullanarak bu iki farklı yolu tek bir bileşen dosyasına yönlendirebilirsiniz.
Bunu yapmak için next-intl'in sunduğu yönlendirme özelliklerini kullanarak URL'leri yeniden yazabilirsiniz.
Adım Adım Çözüm:
Dosya Yapısı:
İlk olarak, her iki URL'yi de yönetecek dosya yapısını oluşturun. Örneğin, pages/[locale]/products/page.tsx dosyasını oluşturabilirsiniz.
Yönlendirme Yapılandırması:
src/i18n/routing.ts dosyasında defineRouting fonksiyonunu kullanarak yönlendirme yapılandırmasını tanımlayın. Burada, /tr/urunler ve /tr/products URL'lerinin aynı bileşene yönlendirilmesi için pathnames özelliğini kullanacağız.
Yönlendirme Dosyası:
// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
locales: ['tr', 'en'],
defaultLocale: 'tr',
pathnames: {
'/products': {
tr: '/urunler', // "urunler" ve "products" aynı bileşene yönlendirilir
en: '/products'
}
}
});
Bu yapılandırma, /tr/urunler ve /tr/products URL'lerinin her ikisinin de products sayfasına yönlendirilmesini sağlar.
Sayfa Bileşeni:
Artık, tek bir sayfa bileşeniyle her iki URL'yi de yönetebilirsiniz. pages/[locale]/products/page.tsx dosyasını oluşturun:
// pages/[locale]/products/page.tsx
import { useRouter } from 'next/router';
export default function ProductsPage() {
const router = useRouter();
const { locale } = router;
return (
<div>
<h1>{locale === 'tr' ? 'Ürünler' : 'Products'}</h1>
{/* Burada yerel ayara göre farklı içerik veya işlevler gösterebilirsiniz */}
</div>
);
}
Bu örnekte, locale parametresini kullanarak, Türkçe ve İngilizce içerik arasında dinamik olarak geçiş yapabilirsiniz.
Sonuç:
Bu adımları izleyerek, www.domain.com/tr/urunler ve www.domain.com/tr/products URL'lerini aynı bileşen dosyasından yönetebilir ve bu bileşenin içeriğini yerel ayara göre değiştirebilirsiniz. Bu sayede kod tekrarını azaltır ve uluslararasılaştırma süreçlerini daha verimli hale getirirsiniz.
Aşağıdaki bilgilerle kayıt olmayı denedim.
{
"name": "Proto",
"surname": "Test",
"username": "prototurkebykdrms",
"email": "[email protected]",
"password": "123456"
}
Console'da şu hatayı aldım:
Access to XMLHttpRequest at 'https://socializee-backend.vercel.app/api/auth/register' from origin 'https://socializee-app.vercel.app' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Yani CORS hatası alıyorsun. Bu da isteği atan subdomain socializee-app iken istek atılan subdomain socializee-backend olduğu için oluyor. Localhost'ta CORS'a düşmüyor olabilirsin.
Ben bu aşamada takıldığım için cookie ile ilgili durumu gözlemleyemedim.
Bu sorunu çözmek için -backend kodunu bilemiyorum ama- kabaca aşağıdaki düzenlemeyi yapabilirsin:
const express = require('express');
const cors = require('cors');
const app = express();
// Aşağıdaki origin'den gelen isteklere izin veriyoruz:
app.use(cors({origin: 'https://socializee-app.vercel.app'}));
// ...diğer kodlar ve middleware'lar...
app.listen(80, () => {
console.log('Sunucu çalışıyor...');
});
Üst Edit: Çok uzun bir yazı oldu. Nihai düşüncem son paragrafta. Aradakiler tamamen iç dökme :)
Flutter'da durum nedir bilmiyorum ama React Native'de zırt pırt güncelleme geliyor. Kullandığınız paketler yeni sürüme destek getirinceye kadar siz uygulamanızı -azmetmezseniz- yeni sürüme geçiremiyorsunuz. Üstelik bazı sürüm yükseltmeleri öyle temelden yapılıyor ki, native kodları da düzenleyerek eklediğiniz paketlerin yeni sürümde nasıl yer edinmesi gerektiğini çözmekle uğraşıyorsunuz. Sırf bu yüzden mümkün olduğunca -hazır paket bulunmasına rağmen- ihtiyacımı kendi bileşenimi oluşturarak gideriyorum. Bu iyi de oluyor tabi. Benim kontrolümde, sadece benim ihtiyacım olan kod kalabalığına sahip bileşenlerim oluyor. Ama işin Swift veya Kotlin/Java tarafına çok hakim olmadığım için burada da kısıtlanıyorum tabi.
Yani React Native tarafında sürekli versiyon yükseltmeleri oluyor ama 3. taraf paketler buna uyum sağlayamadığından sorunlar yaşıyorsunuz.
Bunun haricinde React Native ile kod yazmak -özellikle Typescript desteğiyle- çok keyifli.
Mobil yazmaya karar verirken hem Flutter'ı hem React Native'i başlangıç düzeyinde denemiştim. Flutter'a bir türlü ısınamamıştım. En azından o dönemde, çokça çözümleri olsa bile beni aslında bu çözümler içinde bir konfor alanı yaratıp aslında kısıtlıyor gibi hissetmiştim. Aslında felsefesi hoşuma gitmişti. Aslında çok da native bileşenlere bağımlı olmayan, oluşturulan bileşenleri ekranda kendisi resim çizer gibi çizen, aslında bütün işleri web'deki bir <canvas> üzerine çizim yapar gibi halleden bir yapı kurmuşlardı. Bence bu harika bir çözümdü ve ekrana mükemmel bir hakimiyet kurmamı sağlayacağını, gerekirse kendi yamuk yumuk görünen bileşenlerimi bile oluşturabileceğimi düşünmüştüm. Yani aslında geçmişten özlediğim Flash ve ActionScript ile uygulama geliştirir gibi uygulama geliştirebileceğim bir yapı ile karşılaşacağımı ummuştum. Ama bunun yerine, hazır bileşenleri prop'lar vasıtasıyla özelleştirebileceğim bir yapı ile karşılaştım. Başlangıçta header'ı, footer'ı, yan menüsü falan olan gelişmiş bir bileşen ile başlıyordum ve bunun üzerinde geliştirmeler yapıp özelleştiriyordum. Ama bu bana bootstrap geliştirilmiş web sitesinde css ile overload yaparak geliştirme yapar gibi hissettirdi. İşin kötüsü, okuduğum dokümanlar ve izlediğim eğitim videoları bunun harika bir özellik olduğundan, işleri çok hızlandıracağından bahsediyorlardı. Evet, başlangıç düzeyinde insanı cezbediyor hızlıca gelişmiş bir görünüm elde edebilmek. Ama beni cezbetmedi çünkü beklentilerim bambaşkaydı. Ben de bu işler böyle yürüyorsa -muhtemelen yanıldım ama- Flutter'ın bana göre olmadığına karar verdim. Bu sırada React Native ise başlangıçta bana bomboş bir ekran sunuyordu. "Zaten React biliyorsundur, al web sitesi tasarlar gibi tasarla" diyordu. Ben o sırada pek React da bilmiyordum aslında ama bu boş ekranda geliştirme yapmak bana daha çekici geldi. Bu sıralarda React Native de eski class yapısından kurtulup fonksiyonel bir yapıya dönüyordu ve yeni yapısında bileşen oluşturmak sıradan bir javascript fonksiyonu oluşturmak gibi kolaydı. Bir bileşenin stateful/stateless olup olmadığını belirtmek falan da gerekmiyordu. Bir state kullanıyorsan otomatikman stateful oluyordu. Kullanmazsan stateless devam ediyordu. Çok basit, anlaşılır ve sıfırdan özelleştirilebilir bir yapı. Evet, hayal ettiğim gibi Flash rahatlığında tasarım oluşturamam belki ama neredeyse Web rahatlığındayım. Bir index.html dosyası oluşturmuşum ve imleç ilk satırda yanıp sönüyor. Buradan nereye gidebileceğim benim hayal gücüme kalmış. Flutter'da ise gelişmiş bir html istekeleti ile başlıyorum ve bunu özelleştirip nereye gideceğim yine bana kalmış. Yani birinde bana özel bir yapı kuruyorken diğerinde ihtiyacım olabilecek her şeyi barındıran bir yapıyı bana özgü hale getirmek için çalışıyorum. Kişiden kişiye değişir tabi ama ben React Native'in sunduğu yapıyı daha esnek bulduğum için o yolu tercih ettim. Dediğim gibi muhtemelen yanılmışımdır. Sadece başlangıç düzeyinde Flutter inceledim sonuçta.
ChatGPT'ye şu prompt'u verdim: "ekrana merhaba dünya yazan bir bileşenin react native ve flutter örneklerini ver."
Flutter için şu örneği oluşturdu:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Merhaba Dünya App'),
),
body: const Center(
child: Text(
'Merhaba Dünya',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
),
);
}
}
React Native için ise şu:
import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
const App = () => (
<View style={styles.container}>
<Text style={styles.text}>Merhaba Dünya</Text>
</View>
);
const styles = StyleSheet.create({
container: {flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#fff'},
text: {fontSize: 20, fontWeight: 'bold'},
});
export default App;
Bu iki örneğe bakınca React Native için web'e yakın düzeyde bir tasarım esnekliğim var. Flutter'da ise elementi ortalamak için Center gibi başka bileşenler kullanmam gerekiyor. Yani ben yazının ortada değil de sağa yaslı olmasını istesem stil değiştirmek yerine bileşen değiştirmem gerekecek. Ya da adı Center olan bir bileşene, içerikleri sağa yaslı olsun diye style vermem gerekecek.
Bir de aynı iş için React Native'de kullandığım 2 bileşene karşılık Flutter'da Widget > Material App > Scaffhold > Center'ın da child prop'unu kullanarak 4 widget kullandı. Muhtemelen 1-2 widget'la da çözebilirdi aslında işi. Ama işte ChatGPT de o konfor alanında kalmayı seçti. Kodda yaratılan arrow anti-pattern durumu da -aslında böyle olmak zorunda değildi biliyorum- ama ister istemez oluşuveriyor.
Aslında bakınca Flutter'ın Center gibi bileşenler kullanması native koda yakınlık açısından çok daha doğru ve kodun native koda çevrilmesi açısından daha verimli gibi görünüyor. Ama Flutter'ın asıl felsefesi "ekrana piksel piksel çiziyorum" olduğu için bu durum bana itici geliyor. Aslında native'e uyumluluk açısından aynı şeyi React Native de yapabilirdi ama yapmamayı tercih etmişler. Böylece React Native "bırak böyle native uyumluluk gibi şeylerin hesabını ben yapayım, sen kodununun en sade haliyle işlevine odaklan" diyerek gönülümü almış oldu. Tabi dediğim gibi, belki 2 hafta daha Flutter'ın üzerine gitsem bu düşüncelerimin tamamen yanlış olduğu kanısına varırdım. Ama işte iletişimde ilk temasın önemi. :) Ben kendi adıma C# dilinin keskinliği ve Visual Studio'nun yüzlerce imkânı ile web sitesi geliştirmek yerine PHP'nin veya Node.js'in esnekliği ve Visual Studio Code'un rahatlığında web sitesi geliştirmeyi tercih ediyorum. Evet, güvenlik için belki daha çok çaba gerektiriyor. Belki performans için kodu daha dikkatli yazmam gerekiyor. Ama beni kalıplardan uzak tutuyor. PHP'de bile bir framework kullanırken Laravel yerine Codeigniter kullanmayı tercih ederdim. Çünkü Laravel'in aksine Codeigniter'da PHP özgürlüğünü hissedebiliyorsunuz. Bu tamamen yazılımcının tercih meselesi. Zaten Codeigniter da gitgide Laravel'e benzemeye başlayınca PHP'den de uzaklaşmaya başladım. Şu sıralar Node.js'çiyim diyebilirim. :) Bir de sırf makine öğrenmesi veya derin öğrenme üzerine merakım olduğundan ufak ufak Python çalışmalarım oluyor ama profesyonel anlamda web tarafında React, mobil tarafta React Native ile para kazanıyorum.
Bana sorarsan web tarafında da React yerine Svelte kullanmak çok daha heyecan verici. Ama işte Svelte'in de geliştiricisi az olduğundan pek hızlı gelişemiyor. Hatta x = x; gibi garip kodlar yazmak gerekebiliyor ki bu benim için göz kanatan bir kod. :D Ve Svelte de bu gibi durumların önüne geçmek için günden güne React'a benzemeye başlıyor gibi bir izlenim uyandı bende.
Eskiden harika bir Flash'ımız ve ActionScript'imiz vardı. Google'ın başını çektiği bir çok firma el birliğiyle Adobe'nin bu projesini güvenlik açıkları nedeniyle durdurdular. Tarayıcılarda Flash uygulamalarını engellediler. Zamanla Flash geliştiricileri web'den el çekmek zorunda kaldı. Mobil'de Flash'la halen -özellikle oyun alanında- harika işler ortaya çıkarılabiliyordu. Şu an orada son durumu bilmiyorum ama orada da sanırım Microsoft'un Unity projesi -ki çok beğenirim kendisini- baskın geldi. Ben Google'dan Adobe'ye karşı bu tutumunun üzerine ondan daha iyisini Flutter ile başaracağını ummuştum ama umduğumu bulamadım. Flutter ile yazmak -temelde- React Native ile yazmaktan çok da farklı değildi. Birinin Component dediğine biri Widget diyordu. Flutter'da hayal ettiğim tasarım ortamını bulamadım. Hal böyleyken, yukarıda da bahsettiğim nedenlerle Flutter yerine React Native'i tercih ettim.
Aslında aklımın bir köşesinde Flutter için hep bir "acaba" var ama az önce ChatGPT'nin örneğinde gördüğüm extends StatelessWidget ifadesi halen orada olduğu için bu acaba'yı yine ertelemek istedim mesela. React Native bu ifadeden (extends React.Component) kurtulalı yıllar oldu. Ayrıca nesne tabanlı kod geliştirmek iyidir hoştur ama gerekli gereksiz her yerde nesne tabanlı kod yazmayı da doğru bulmuyorum. React Native de böyle düşünmüş olacak ki -zaten Javascript'e pek uygun da olmadığından- başlangıçta nesne tabanlı gitmesine rağmen sonradan fonksiyonel tabanlı bir yapıya döndüler. Ben de tam bu dönüş sırasında mobile geçiş yaptığımdan React Native beni yakalamış oldu.
Özetle, Flutter'ı pek bilmediğim için karşılaştırma yapamayacağım. Ama React Native'den, yükseltme problemleri hariç, memnunum. Kod yazarken keyif alıyorum.
Sorun bunlardan mı kaynaklanıyor bilmiyorum ama deneyebilirsin:
toolbar.js dosyasını kullanıyorsan event.preventDefault(); kodları silebilirsin.
toolbar.min.js dosyasını kullanıyorsan a.preventDefault() yazan yerleri undefined olarak değiştirebilirsin.
toolbar'a basılınca gerçekleşecek olayları kontrol etmek için kullanıcının yaptığı diğer tıklama olaylarını geçersiz kılıyor. Belki link'lere tıklama olayları bu yüzden geçersiz kalıyordur.