Önemli: Diğer yazılarımla direkt bağlantılı bir yazıdır. İlk olarak bu yazıdan okumaya başladıysanız, eğitim serisine kısaca bir göz atmanızı tavsiye ederim.
Merhabalar.
Bu yazımla beraber Post metodu ile API’a veri göndermeyi ve Post metodunun yaptığı işleri yapmayı öğreneceğiz.
Get istekleri boyunca oluşturduğumuz Blog Post uygulamasına, bir post ekleme alanı ekleyeceğiz. Post’un bizden istediği değerler var bunları Form’dan alıp, Post isteği atarak datayı göndermeyi öğreneceğiz. Sonra gelen cevap üzerinden elimizdeki var olan Post listesini güncelleyeceğiz.
Güncelleme sonrası uygulamamız şuna dönüşecek.
Amacımız
Yapacağımız işlemler şunlar olacak; öncelikle bir Add Post isminde bir buton koyacağız. Alt kısma post ekleme formunu koyacağız. Başlangıçta burası kapalı gelecek. Add Post butonuna tıklayınca alan açılacak.
Alanın içinde Title ve Body bilgilerini alacağımız alanlar ve bunları Post isteği ile gönderecek Add Post butonu olacak. Bu alanı kapatmak için alanın sağ üst köşesine close butonu koyacağız. Tıkladığımızda alan tekrar gizlenecek.
Geliştirme Kodlarımız
BlogPost.jsx
import React, { useState } from 'react'
import './BlogPost.css'
import loadingGif from "../../Assets/Images/loading.gif"
const BlogPost = () => {
const [posts, setPosts] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isData, setIsData] = useState(false);
const [isError, setIsError] = useState(null);
const [showError, setShowError] = useState(false);
const [searchId, setSearchId] = useState('');
const [comments, setComments] = useState([]);
const [isAddSection, setIsAddSection] = useState(false);
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const loadPosts = async () => {
try {
setComments([]);
setIsLoading(true);
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (response.ok) {
const data = await response.json();
setPosts(data);
setIsLoading(false);
if (data.length === 0 ? setIsData(false) : setIsData(true));
openMessageModal(`${data.length} posts found`);
} else {
setIsLoading(false);
setIsData(false);
openMessageModal('There was an error!');
}
} catch (error) {
setIsData(false);
openMessageModal('There was an error!');
}
};
const loadComments = async (id) => {
try {
setIsLoading(true);
const response = await fetch(`https://jsonplaceholder.typicode.com/comments?postId=${id}`);
if (response.ok) {
const data = await response.json();
setComments(data);
setIsLoading(false);
if (data.length === 0 ? setIsData(false) : setIsData(true));
openMessageModal(`${data.length} comments found`);
} else {
setIsLoading(false);
setIsData(false);
openMessageModal('There was an error!');
}
} catch (error) {
setIsData(false);
openMessageModal('There was an error!');
}
};
const openMessageModal = (info) => {
setIsError(info);
setShowError(true);
setTimeout(() => setShowError(false), 5000);
};
const getSearchById = async () => {
try {
setComments([]);
if (searchId === '') {
loadPosts();
}
else {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${searchId}`);
if (response.ok) {
const data = await response.json();
setPosts([data]);
setIsLoading(false);
if (data.length === 0 ? setIsData(false) : setIsData(true));
if ([data].length === 0) {
openMessageModal('Data not found');
}
openMessageModal(`${[data].length} posts found`);
} else {
setPosts([]);
openMessageModal('Data not found');
}
}
} catch (error) {
console.error('There was an error!', error);
}
}
const handleSearch = (e) => {
if (e.target.value === '') {
loadPosts();
}
setSearchId(parseInt(e.target.value));
}
const addPost = async (event) => {
event.preventDefault();
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify({
title: title,
body: body,
userId: 1,
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
});
const data = await response.json();
setPosts([data, ...posts]);
setTitle('');
setBody('');
};
return (
<div className="blogPost">
{showError && <div className="error">{isError}</div>}
<div className='search'>
<input type="number" name="searchId" value={searchId} onChange={handleSearch} placeholder="Search by ID" />
<button type='button' onClick={getSearchById}>Search</button>
<button onClick={() => setIsAddSection(true)} >Add Post</button>
<button onClick={loadPosts}>Load Posts</button>
</div>
{isAddSection &&
<form onSubmit={addPost}>
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Title" required />
<textarea value={body} onChange={(e) => setBody(e.target.value)} placeholder="Body" required />
<button type="submit">Add Post</button>
<button className='close' type="button" onClick={ () => setIsAddSection(false)}>x</button>
</form>
}
{isLoading &&
<div className="center">
<img src={loadingGif} width={100} alt="Loading" />
</div>
}
{!isData &&
<div className="center">
<h3>There is no data</h3>
</div>
}
{isData &&
<div className="post-container">
<div className='posts'>
<h2>Posts</h2>
{isData &&
posts.map((post) => (
<div key={post.id} className="post">
<h2>{post.title}</h2>
<p>{post.body}</p>
<button onClick={() => loadComments(post.id)}>Load Comments</button>
</div>
))
}
</div>
<div className='comments'>
<h2>Comments</h2>
{
comments.map((comment) => (
<div key={comment.id} className="comment">
<h3>{comment.name}</h3>
<p>{comment.body}</p>
<p>{comment.email}</p>
</div>
))
}
</div>
</div>
}
</div>
);
}
export default BlogPost
BlogPost.css
.blogPost {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
button {
padding: 10px 20px;
margin: 20px 0;
border: none;
border-radius: 5px;
background-color: #007BFF;
color: white;
cursor: pointer;
}
.center {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
color: #ffffff;
}
.error {
width: 300px;
height: 50px;
border-radius: 20px;
position: fixed;
top: 20px;
right: 20px;
padding: 10px;
background-color: rgb(255, 0, 0);
color: white;
display: flex;
font-size: 15px;
font-weight: bold;
justify-content: center;
align-items: center;
z-index: 100;
}
.search {
margin-top: 20px;
display: flex;
direction: column;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 20px;
width: 50%;
}
.search input {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
flex-grow: 1;
margin-right: 10px;
}
.search input, .search button {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
.search button {
padding: 10px;
border: none;
background-color: #007BFF;
color: white;
cursor: pointer;
flex-basis: 10%;
margin-right: 5px;
}
.search button:hover {
background-color: #0056b3;
}
.post-container {
display: flex;
direction: column;
justify-content: space-between;
flex-wrap: wrap;
border-radius: 20px;
}
.posts {
flex-basis: 70%;
color: #ffffff;
padding: 20px;
}
.post button{
position:absolute;
bottom: -10px;
right: 10px;
padding: 4px;
}
.post button:hover{
background-color: cadetblue;
}
.post {
position: relative;
border: 1px solid #ccc;
border-radius: 5px;
padding: 20px;
margin-top: 20px;
position: relative;
background-color: #ffffff;
color: rgb(45, 71, 187);
box-shadow: 0 0 10px rgba(114, 109, 109, 0.5);
}
.comments {
flex-basis: 25%;
width: 200px;
color: rgb(255, 255, 255);
margin-top: 20px;
}
.comment{
border: 1px solid #ccc;
border-radius: 5px;
padding: 20px;
margin-top: 20px;
position: relative;
background-color: #ffffff;
color: rgb(45, 71, 187);
box-shadow: 0 0 10px rgba(114, 109, 109, 0.5);
}
form {
position: relative;
display: flex;
flex-direction: column;
margin-bottom: 20px;
margin-top: 20px;
border: 1px solid #ccc;
width: 50%;
padding: 20px;
border-radius: 20px;
}
form input,
form textarea {
margin-bottom: 10px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
width: 95%;
}
form button {
padding: 10px 20px;
border: none;
background-color: #007BFF;
color: white;
cursor: pointer;
border-radius: 4px;
width: 97%;
}
form button:hover {
background-color: #1c4066;
}
.close {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
position: absolute;
top: 0px;
right: 15px;
cursor: pointer;
width: 30px;
height: 30px;
padding: 10px;
background-color: red;
font-size: 15px;
font-weight: bold;
}
State Ekle
AddPost bölümünün açılıp kapanmasını isAddSection state’ini ekliyoruz. Blog Post eklerken gönderilecek title ve body state’lerini oluşturdum.
const [isAddSection, setIsAddSection] = useState(false);
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
Add Post Section Jsx Kodları
Blog ekleme bölümümüz aşağıdaki gibi.
{isAddSection &&
<form onSubmit={addPost}>
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Title" required />
<textarea value={body} onChange={(e) => setBody(e.target.value)} placeholder="Body" required />
<button type="submit">Add Post</button>
<button className='close' type="button" onClick={ () => setIsAddSection(false)}>x</button>
</form>
}
Burada görünürlüğünü isAddSection’ın true olmasına bağlı tuttum. Form Add Post butonuyla submit olduğunda, addPost fonksiyonu çağırılır.
Close butonu setIsAddSection state’ini false olarak güncelliyor. Alan gizleniyor.
addPost Fonksiyonu
const addPost = async (event) => {
event.preventDefault();
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify({
title: title,
body: body,
userId: 1,
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
});
const data = await response.json();
setPosts([data, ...posts]);
setTitle('');
setBody('');
};
Burada form ögesi submit olduğu için sayfa yenilenir. Ancak biz sayfa yenilenmesini istemeyiz. e.preventDefault() sayfa yenilemesini engellemek için.
Post İsteği Gönderme
Bir post isteği aşağıdaki yapıda gönderilir.
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify({
title: title,
body: body,
userId: 1,
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
});
Bu isteği incelersek;
fetch
fonksiyonu kullanılarak belirtilen URL’ye bir HTTP isteği gönderiliyor. İlk parametre isteğin gönderileceği URL’yi belirtir.
fetch
fonksiyonunun ikinci parametresi bir konfigürasyon nesnesidir. method
özelliği, bu isteğin bir POST isteği olduğunu belirtir. body
özelliği ise isteğin gövdesini oluşturur. Burada JSON.stringify
kullanılarak JavaScript nesnesi olan veri, JSON formatına dönüştürülüyor. Bu durumda, bir gönderi oluşturuluyor ve title
, body
ve userId
alanlarıyla dolduruluyor.
İsteğin başlıkları (headers) belirleniyor. Content-type
başlığı, gönderilen verinin türünü belirtir. Bu durumda, application/json
olarak ayarlanmıştır. Ayrıca, karakter seti olarak UTF-8 belirtilmiştir.
fetch
fonksiyonu asenkron bir fonksiyon olduğu için await
ile beklenir. Bu, isteğin tamamlanmasını ve API tarafından dönen yanıtın response
değişkenine atanmasını sağlar.
Geri kalan kodlarımızı artık biliyoruz.
Uygulama Test
Bu yazımızda böyleydi.
Bir sonraki yazımda görüşmek üzere.