React-Redux-Thunk Kullanımına Bir Örnek - 1

Merhaba, 

Bu yazıda React ile Redux State yönetimi kütüphanesini kullanarak asenkron işlemlerine bir örnek vermek istiyorum. 

Ufaktan başlayalım. Peşinen söyleyeyim kaynak kodlarını görmek istiyorsanız buraya tıklayın

Öncelikle bir sunucumuz olmalı. Laravel ile uzun uzadıya bir api yapmaktansa herkesin malumu Fake Rest Api olan Jsonplaceholder kullanacağız. 

Bilgisayarınızda node.js in kurulu olduğunu varsayıyorum. Aşağıdaki kodu cmd ekranınıza yazıp enter a tuşlayarak kurulumu yapabilirsiniz.

npm install -g json-server 

Evet, artık sistemimizde global olarak kurulmuş bir fake rest api mevcut. 

React kodlarınızın yazılacağı dizine gelin ve aşağıdaki kodu yapıştırarak yeni bir proje oluşturalım. 

npx create-react-app redux-thunk-test

Evet, projemiz oluşturuldu. VS Code ile projemizi açalım.

src klasörü altına hemen şu klasörleri de oluşturuverelim. 

  1. actions
  2. components
  3. contants
  4. data
  5. reducers
  6. store
  7. utils ( kullanmayacağız ama elimiz alışsın diye )

Terminal ekranına aşağıdaki kodu girerek kullanacağımız kütüphaneleri sistemimize en başında dahil edelim. 

npm install axios jquery redux react-redux redux-thunk redux-devtools-extension --save

Burada jquery 'i bootstrap da modal oluşturacağımız ve bu modal ı kapatacağımız için kurduk. 

Tabi tarayıcınızda redux-devtools extension kurulu olmalı. Değil ise tarayınıza göre indirip kurabilirsiniz.

Evetttt.

Artık yavaştan başlıyoruz. 

Size bir dummy data vereyim. Şuradan indirin. 

"redux-database.json" adında bir dosya olacak. İçindekileri kopyalayın. 

src altına da "redux-database.json" adında bir dosya oluşturup içine yapıştırın. Bu bizim başlangıç verilerimiz olacak.

Yine ayrı bir cmd ekranı açalım. Kodlarımızın çalıştığı dizin altındaki src dizinine gelelim. Daha sonra şu komutu yazıp enter tuşana basalım ve apimiz hazır!

json-server --port=7000 redux-database.json --watch --delay=500

Evet, yarım saniye gecikmeli cevap verecek bir server başlattık. 

public klasörü içindeki index.html dosyasını açalım ve title etiketinin altına fontawesome ve bootstrapt kütüphanelerini ekleyelim.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fontawesome/5.15.3/css/all.min.css" integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w==" crossorigin="anonymous" referrerpolicy="no-referrer" />

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

Siz dilerseniz cdn üzerinden kendiniz de ekleyebilirsiniz.

Yine index.html dosyasının en altına body etiketinin üzerine şunları ekleyelim.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ=="crossorigin="anonymous" referrerpolicy="no-referrer">
</script>
  
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous">
</script>

Anlaşılacağı üzere jquery ve bootstrap için javascript dosyalarını buraya eklemiş olduk.

Evet, adım adım gidelim.

components altına 2 adet daha klasör açalım ve şu isimleri verelim. 

  • modal
  • users

modals klasörünün içine Modal.jsx dosyasın açalım ve içine aşağıdaki kodları yapıştıralım.

import React, { useState } from 'react'

const Modal = () => {
  let handleAddUser = () => {}

  return (
    <>
      <div
        className="modal fade"
        id="exampleModal"
        aria-labelledby="exampleModalLabel"
        aria-hidden="true"
      >
        <div className="modal-dialog modal-lg">
          <div className="modal-content">
            <div className="modal-header">
              <h5 className="modal-title" id="exampleModalLabel">
                Kullanıcı Ekle
              </h5>
              <button
                type="button"
                className="btn-close"
                data-bs-dismiss="modal"
                aria-label="Close"
              ></button>
            </div>
            <div className="modal-body">
              <div className="mb-3">
                <label htmlFor="name" className="col-form-label">
                  Ad Soyad:
                </label>
                <input type="text" className="form-control" id="name" />
              </div>

              <div className="mb-3">
                <label htmlFor="username" className="col-form-label">
                  Kullanıcı Adı:
                </label>
                <input type="text" className="form-control" id="username" />
              </div>

              <div className="mb-3">
                <label htmlFor="email" className="col-form-label">
                  E-posta:
                </label>
                <input type="text" className="form-control" id="email" />
              </div>

              <div className="mb-3">
                <label htmlFor="phone" className="col-form-label">
                  Telefon:
                </label>
                <input type="text" className="form-control" id="phone" />
              </div>
            </div>
            <div className="modal-footer">
              <button
                type="button"
                className="btn btn-secondary"
                data-bs-dismiss="modal"
              >
                Kapat
              </button>
              <button
                type="button"
                className="btn btn-primary"
                onClick={() => handleAddUser()}
              >
                Kaydet
              </button>
            </div>
          </div>
        </div>
      </div>
    </>
  )
}

export default Modal

Görüldüğü üzere, bir form alanı oluşturduk ve bu değerleri kaydedeceğimiz bir fonksiyon tanımladık o kadar.

users klasörünün altına Users.jsx dosyası oluşturalım ve içine aşağıdaki kodları yapıştıralım. 

import React, { useState, useEffect } from 'react'

// Modal ımızı dahil ediyoruz.
import Modal from '../modal/Modal'

const Users = () => {

  // Kullanıcağımız metotlar
  let handleEditClick = () => {}
  let handleUpdate = () => {}
  let handleDelete = () => {}

  return (
    <>
      <Modal />
      <div className="table-responsive">
        <div className="YeniEkle">
          <button
            data-bs-toggle="modal"
            data-bs-target="#exampleModal"
            data-target=".bd-example-modal-lg"
            className="btn btn-primary float-end"
          >
            Kullacı Ekle
          </button>
        </div>

        <table className="table table-hover">
          <thead>
            <tr>
              <th scope="col">#</th>
              <th scope="col">Ad Soyad</th>
              <th scope="col">Kullanıcı Adı</th>
              <th scope="col">E-posta</th>
              <th scope="col">Telefon</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <th scope="row">1</th>
              <td>Ad Soyad</td>
              <td>Kullanıcı Adı</td>
              <td>test@mail.com</td>
              <td>505-555-5555</td>
              <td className="d-flex justify-content-between">
                <button className="btn btn-sm btn-warning ">
                  <i className="fas fa-user-edit"></i>
                </button>

                <button className="btn btn-sm btn-danger">
                  <i className="fas fa-user-times"></i>
                </button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </>
  )
}

export default Users

Görüldüğü üzere, ilk olarak oluşturduğumuz  Modal.jsx dosyamızı dahil ettik. Daha sonra kullanacağımız metotları tanımladık. return kısmında ise bir tablo oluşturduk. 

App.js dosyamızı açalım ve içerisini şu şekilde düzenleyelim. 

import './App.css'
import Users from './components/users/Users'

function App() {
  return (
    
      <div className="container mt-5">
        <Users />
      </div>
  )
}

export default App

Görüldüğü gibi sadece Users.jsx dosyamızı buraya dahil ettik. Biraz da bootstrap ile süslemişiz işte.

Her şeyi eksiksiz yaptıysak şöyle bir ekran gelmeli.

Hemen action types değerlerimizi tanımlayalım.

constants klasörümüzn altına action-types.js adında bir dosya açalım ve içine aşağıdaki kodu yazalım.

export const FETCH_USER_REQUEST = 'FETCH_USER_REQUEST'
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS'
export const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE'

export const ADD_USERS_REQUEST = 'ADD_USERS_REQUEST'
export const ADD_USER_SUCCESS = 'ADD_USER_SUCCESS'
export const ADD_USER_FAILURE = 'ADD_USER_FAILURE'

export const UPDATE_USER_REQUEST = 'UPDATE_USER_REQUEST'
export const UPDATE_USER_SUCCESS = 'UPDATE_USER_SUCCESS'
export const UPDATE_USER_FAILURE = 'UPDATE_USER_FAILURE'

export const DELETE_USER_REQUEST = 'DELETE_USER_REQUEST'
export const DELETE_USER_SUCCESS = 'DELETE_USER_SUCCESS'
export const DELETE_USER_FAILURE = 'DELETE_USER_FAILURE'

Anlaşılacağı üzere kullanıcıları çekme, yeni kullanıcı ekleme, güncelleme ve silme işlemlerini yapacağız.

actions klasörü altına index.js ve users.js adında 2 adet dosya oluşturalım. 

users.js dosyasının içine aşağıdaki kodu yazalım. 

// Action type değerlerini dahil ettik.
import * as actionTypes from '../constants/action-types'

// Asenkron işlem için axios kütüphanesini kullanacağız.
import axios from 'axios'

// Kullanıcıları çekmek için bu metodu kullanıyoruz.
export const fetchUsers = () => async (dispatch, getState) => {
  
  // Sunucu ile bağlantı kurmak istediğimizi belirtelim.
  // dispatch ile reducer içindeki state değerini değiştiriyor olacağız.
  // Örneğin loader koymak istersek burada kontrol edeceğiz
  dispatch({ type: actionTypes.FETCH_USER_REQUEST })

  try {

    // sunucuya istek gönderelim. Kullanıcıları çekiyoruz.
    const response = await axios.get('http://localhost:7000/users')

    // Gelen değeri reducer içindeki state e gönderiyoruz.
    dispatch({ type: actionTypes.FETCH_USER_SUCCESS, payload: response.data })

  } catch (error) {
    
    // Bir problem olduğunda yine reducer a bildiriyoruz. 
    // Örneğin hata kontrolü yapmak için.
    dispatch({ type: actionTypes.FETCH_USER_FAILURE, payload: error })
  }
}

export const updateUser = (userObject) => async (dispatch, getState) => {

  // Sunucu ile bağlantı kurmak istediğimizi belirtelim.
  // dispatch ile reducer içindeki state değerini değiştiriyor olacağız.
  // Örneğin loader koymak istersek burada kontrol edeceğiz
  dispatch({ type: actionTypes.UPDATE_USER_REQUEST })

  try {

    // Kullanıcı eklerken bilgilerini obje halinde gönderip burada
    // yeni bir obje oluşturuyoruz. Ön tarafta da yapabilirdik. Burada yapmışım.
    let userObject2 = {
      id: userObject.id,
      name: userObject.name,
      username: userObject.username,
      email: userObject.email,
      phone: userObject.phone,
    }

    // Sunucuya update işlemi için istek yapıyoruz.
    const response = await axios.put(
      `http://localhost:7000/users/${userObject.id}`,
      userObject2,
    )

    // Bir hata oluşmamışsa, yeni eklediğimiz kullanıcıyı reducer içindeki
    // state değerine atamak için gönderiyoruz.
    dispatch({
      type: actionTypes.UPDATE_USER_SUCCESS,
      payload: userObject2,
    })

  } catch (error) {
    
    // Hata oluşmuş ise reducer içindeki state imizde bulunan hata değerini 
    // değiştiriyoruz.
    dispatch({ type: actionTypes.UPDATE_USER_FAILURE, payload: error })
  }
}


export const deleteUser = (userId) => async (dispatch, getState) => {
  
  // Sunucu ile bağlantı kurmak istediğimizi belirtelim.
  // dispatch ile reducer içindeki state değerini değiştiriyor olacağız.
  // Örneğin loader koymak istersek burada kontrol edeceğiz
 dispatch({ type: actionTypes.DELETE_USER_REQUEST })

  try {

    // Sunucuya silmek istediğimiz kullanıcının id değerini gönderiyoruz.
    const response = await axios.delete(`http://localhost:7000/users/${userId}`)

    // Bir hata yoksa, reducer içindeki state içinden user ı kendimiz sileceğiz.
    dispatch({
      type: actionTypes.DELETE_USER_SUCCESS,
      payload: userId,
    })

  } catch (error) {

    // Hata oluşmuş ise reducer içindeki state imizde bulunan hata değerini 
    // değiştiriyoruz.
    dispatch({ type: actionTypes.DELETE_USER_FAILURE, payload: error })
  }
}


export const addUser = (user) => async (dispatch, getState) => {
 
   
  // Sunucu ile bağlantı kurmak istediğimizi belirtelim.
  // dispatch ile reducer içindeki state değerini değiştiriyor olacağız.
  // Örneğin loader koymak istersek burada kontrol edeceğiz
  dispatch({ type: actionTypes.ADD_USERS_REQUEST })

  try {

    // yeni kullanıcı eklemek için sunucuya istek gönderiyoruz.
    // buradaki user bize obje olarak gelecek.
    const response = await axios.post('http://localhost:7000/users', user)

    // Eğer sunucuya başarılı bir şekilde eklendiyse, reducer içinde bulunan
    // state değerine kendimiz bu kullanıcıyı ekleyeceğiz.
    dispatch({
      type: actionTypes.ADD_USER_SUCCESS,
      payload: user,
    })

  } catch (error) {
    
    // Hata oluşmuş ise reducer içindeki state imizde bulunan hata değerini
    // değiştiriyoruz.
    dispatch({ type: actionTypes.ADD_USER_FAILURE, payload: error })
  }
}

actions altındaki index.js dosyasını açalım ve içine şu kodları yazalım.

import { fetchUsers, updateUser, deleteUser, addUser } from './users'

let actions = { fetchUsers, updateUser, deleteUser, addUser }

export default actions

Buradaki espri şu: Kullanacağınız action metotlarını tek tek çıkarmak yerine tek bir yerde toplayıp oradan çıkarmak. Sonuçta elimizde users.js dışında başka dosyalar da olabilirdi. 

Devam edelim. 

reducers klasörü altına gelelim ve index.js ile userReducer.js adında 2 adet dosya oluşturalım. 

userReducers.js dosyasının içine şu kodları yapıştıralım. 

// action type değerlerini alıyoruz.
import * as actionTypes from '../constants/action-types'

// Başlangıç state imizi veriyoruz.
const initialState = { data: [], isLoading: false, error: null }

// Espri başlıyor..
export const userReducer = (state = initialState, action) => {
  

switch (action.type) {

    /** action içindeki fetchUser fonksiyonundan dispatch ile gelen değerler . **/

    case actionTypes.FETCH_USER_REQUEST:
      return { data: [], isLoading: true, error: null }

    case actionTypes.FETCH_USER_SUCCESS:
      return { data: action.payload, isLoading: false, error: null }

    case actionTypes.FETCH_USER_FAILURE:
      return { data: state.data, isLoading: false, error: action.payload }

    /** action içindeki updateUser fonksiyonundan dispatch ile gelen değerler . **/
    case actionTypes.UPDATE_USER_REQUEST:
      return { data: state.data, isLoading: true, error: null }

    case actionTypes.UPDATE_USER_SUCCESS:
      let userId = action.payload.id

      let newState = state.data.map((user) => {
        if (user.id === userId) {
          user.name = action.payload.name
          user.email = action.payload.email
          user.phone = action.payload.phone
          user.username = action.payload.username
        }
      })

      return {
        ...state,
        data: state.data,
        isLoading: false,
        error: null,
      }

    case actionTypes.UPDATE_USER_FAILURE:
      return {
        data: state.data,
        isLoading: false,
        error: action.payload,
      }


    /** action içindeki deleteUser fonksiyonundan dispatch ile gelen değerler . **/
    case actionTypes.DELETE_USER_REQUEST:
      return { data: state.data, isLoading: true, error: null }

    case actionTypes.DELETE_USER_SUCCESS:
      return {
        data: state.data.filter((user) => user.id !== action.payload),
        isLoading: false,
        error: null,
      }


    /** action içindeki addUser fonksiyonundan dispatch ile gelen değerler . **/
    case actionTypes.ADD_USERS_REQUEST:
      return { data: state.data, isLoading: true, error: null }

    case actionTypes.ADD_USER_SUCCESS:
      return {
        data: [...state.data, action.payload],
        isLoading: false,
        error: null,
      }

    case actionTypes.ADD_USER_FAILURE:
      return {
        data: state.data,
        isLoading: false,
        error: action.payload,
      }

    default:
      return state
  }
}

Gelen işleme göre state değerlerimizi burada güncelliyoruz. Sanırım anlaşılmıştır.

Gelelim index.js dosyasına. İçerisine şu kodları yazalım. 

// Tüm reducer ları bununla birleştireceğiz.
import { combineReducers } from 'redux'

// Az önce oluşturduğumuz userReducer dosyamızı dahil ediyoruz.
import { userReducer } from './userReducer'

// Tüm reducerları allReducers altında birleştiriyoruz.
// Bizde 1 tanecik var tabiki..
let allReducers = combineReducers({
  userReducer,
})

export default allReducers

Evettt. Biraz uzun bir yazı olacak. İkiye bölmeye karar verdim. 

Takip daha kolay olması açısından buraya tıklayarak devamına erişebilirsiniz. 

 

 

 

1404 Görüntülenme

Yorum Yap