article

Thursday, January 16, 2025

ReactJS 18 Node Express CRUD | React Query | MongoDB Atlas | TailwindCSS (Create Read Update and Delete)

ReactJS 18 Node Express CRUD | React Query | MongoDB Atlas | TailwindCSS (Create Read Update and Delete)
https://expressjs_com/
Express JS
Fast, unopinionated, minimalist web framework for Node.js

$ npm install express --savev PS C:\nodeproject> npm install express --save
https://expressjs.com/en/starter/hello-world.html

mongoose
mongoosejs/docs/
npm install mongoose --save

cors
CORS is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options.
npmjs-com/package/cors
PS C:\nodeproject>npm i cors

run PS C:\nodeproject> node index.js
index.js
//index.js
const express = require('express')
const mongoose = require('mongoose')
const cors = require('cors')
const UserModel = require('./User')
  
const app = express()
const port = 3001
 
app.use(cors())
app.use(express.json())
  
main().catch(err => console.log(err));
 
async function main() {
    try {                     //"mongodb+srv://"username":"password"@cluster0.x45tgvn.mongodb.net/"databasename"?retryWrites=true&w=majority&appName=Cluster0"
        await mongoose.connect('mongodb+srv://cairocoders:123456@cluster0.x45tgvn.mongodb.net/expressdb?retryWrites=true&w=majority&appName=Cluster0', {});
        console.log("CONNECTED TO DATABASE SUCCESSFULLY");
    } catch (error) {
        console.error('COULD NOT CONNECT TO DATABASE:', error.message);
    }
}
 
app.get('/hello', (req, res) => {
  res.send('Hello World!')
})
 
app.get('/users', (req, res) => {
    UserModel.find()
    .then(users => res.json(users))
    .catch(err => res.json(err))
})
  
  
app.get('/users/get/:id', (req, res) => {
    const id = req.params.id
    UserModel.findById({_id: id})
    .then(post => res.json(post))
    .catch(err => console.log(err))
})
  
app.post('/users/create', (req, res) => {
    UserModel.create(req.body)
    .then(user => res.json(user))
    .catch(err => res.json(err))
})
  
app.put('/users/update/:id', (req, res) => {
    const id = req.params.id;
    UserModel.findByIdAndUpdate({_id: id}, {
        name: req.body.name,
        email: req.body.email,
        age: req.body.age
    }).then(user => res.json(user))
    .catch(err => res.json(err))
})
  
app.delete('/users/delete/:id', (req, res) => {
    const id = req.params.id;
    UserModel.findByIdAndDelete({_id: id})
    .then(response => res.json(response))
    .catch(err => res.json(err))
})
  
app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})
User.js
//User.js
const mongoose = require('mongoose')
  
const UserSchema = new mongoose.Schema({
    name: String,
    email: String,
    age: Number
})
  
const UserModel = mongoose.model("users", UserSchema)
  
module.exports = UserModel;
react.dev
Create react project
Run the following command in your terminal: vite_dev/guide/
npm create vite@latest
tanstack/react-query
Hooks for fetching, caching and updating asynchronous data in React, Solid, Svelte and Vue

npm i @tanstack/react-query

react-query-devtools
npm i @tanstack/react-query-devtools

Tailwind CSS with Vite
tailwindcss_com/docs/guides/vite

Install npm i uuid

C:\react-js\my-app\src\App.jsx
//src\App.jsx
import { Route, Routes } from "react-router-dom" 
import UserLists from "./pages/UserLists"
import User from "./pages/User"
import EditUser from "./pages/EditUser"

function App() { tailwindcss_com/docs/guides/vite
  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="flex items-center justify-between gap-1 mb-5">
            <h1 className="text-4xl font-bold">React.js 18 Node Express CRUD (Create Read Update and Delete) | React Query MongoDB Atlas</h1>
        </div> 
        <Routes>
          <Route path="/" element={<UserLists />} />
          <Route path="/user/:id" element={<User />} />
          <Route path="/user/:id/edit" element={<EditUser />} />
        </Routes>
    </div>
  )
}

export default App
C:\react-js\my-app\src\main.jsx
//src\main.jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools' //npm i @tanstack/react-query-devtools
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import { BrowserRouter } from "react-router-dom";
import App from './App.jsx'

// create a client
const queryClient = new QueryClient();

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <BrowserRouter>
      <QueryClientProvider client={queryClient}>
        <App />
        <ReactQueryDevtools initialIsOpen={false} />
      </QueryClientProvider>
    </BrowserRouter>
  </StrictMode>,
);
C:\react-js\my-app\src\pages\UserLists.jsx
//src\pages\UserLists.jsx
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; //npm i @tanstack/react-query 
import { useNavigate } from "react-router-dom";
import { deleteUser, fetchUsers } from "../api/users";
import AddUser from "../components/AddUser";

const UserLists = () => {
    const navigate = useNavigate();
    const queryClient = useQueryClient();

    const {
      isLoading,
      isError,
      data: users,  
      error,
    } = useQuery({
      queryKey: ["users"],
      queryFn: fetchUsers,
    });

    //console.log(users);

    const deleteUserMutation = useMutation({
      mutationFn: deleteUser,
      onSuccess: () => {
        queryClient.invalidateQueries({ queryKey: ['users']});
      }
    });

    const handleDelete = (id) => {
      deleteUserMutation.mutate(id)
    }

    if (isLoading) return "loading...";
    if (isError) return `Error: ${error.message}`;

  return (
    <div className="overflow-x-auto py-10">
        <AddUser />
        <table className="table table-zebra">
            <thead className="text-sm text-gray-700 uppercase bg-gray-50">
                <tr>
                    <th className="py-3 px-6">#</th>
                    <th className="py-3 px-6">Name</th>
                    <th className="py-3 px-6">Email</th>
                    <th className="py-3 px-6">Age</th>
                    <th className="py-3 px-6 text-center">Actions</th>
                </tr>
            </thead>
            <tbody>
                {users.map((user) => (
                    <tr key={user._id} className="bg-white border-b">
                        <td className="py-3 px-6">{user._id}</td>
                        <td className="py-3 px-6">{user.name}</td>
                        <td className="py-3 px-6">{user.email}</td>
                        <td className="py-3 px-6">{user.age}</td>
                        <td className="flex justify-center gap-1 py-3">
                            <button
                                onClick={() => navigate(`/user/${user._id}`)}
                                className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">
                                Read
                            </button>
                            <button className="focus:outline-none text-white bg-yellow-400 hover:bg-yellow-500 focus:ring-4 focus:ring-yellow-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:focus:ring-yellow-900"
                                onClick={() => navigate(`/user/${user._id}/edit`)}
                            >
                                Edit
                            </button>
                            <button onClick={() => handleDelete(user._id)} className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">
                                Delete</button>
                        </td>
                    </tr>
                ))}
            </tbody>
        </table>
    </div>
  );
};

export default UserLists;
C:\react-js\my-app\src\pages\User.jsx
//src\pages\User.jsx
import { useQuery } from "@tanstack/react-query"; //npm i @tanstack/react-query
import { useNavigate, useParams } from "react-router-dom";
import { fetchUser } from "../api/users";

const User = () => {
  const navigate = useNavigate();
  const { id } = useParams();
  const {
    isLoading,
    isError,
    data: user,
    error,
  } = useQuery({
    queryKey: ["users", id],
    queryFn: () => fetchUser(id),
  });

  if (isLoading) return "loading...";
  if (isError) return `Error: ${error.message}`;


  return (
    <div className="overflow-x-auto py-10">
      <button onClick={() => navigate("/")}>back to list users</button>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <p>{user.age}</p>
    </div>
  )
}

export default User
C:\react-js\my-app\src\pages\User.jsx
//src\pages\User.jsx
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useNavigate, useParams } from "react-router-dom";
import { fetchUser, updateUser } from "../api/users";
import UserForm from "../components/UserForm"

const EditUser = () => {
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const { id } = useParams();
  const {
    isLoading,
    isError,
    data: user,
    error,
  } = useQuery({
    queryKey: ["users", id],
    queryFn: () => fetchUser(id),
  });
  const updateUserMutation = useMutation({
    mutationFn: updateUser,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users']});
      navigate("/")
    }
  })

  if (isLoading) return "loading...";
  if (isError) return `Error: ${error.message}`;

  const handleSubmit = (updatedUser) => {
    updateUserMutation.mutate({id, ...updatedUser})
  }


  return (
    <div className="overflow-x-auto py-10">
      <UserForm onSubmit={handleSubmit} initialValue={user} />
    </div>
  )
}

export default EditUser
C:\react-js\my-app\src\components\AddUser.jsx
//src\components\AddUser.jsx
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { createUser } from "../api/users"
import UserForm from "./UserForm"
import { v4 as uuidv4 } from 'uuid'; //npm i uuid 

const AddUser = () => {
  const queryClient = useQueryClient();

  const createUserMutation = useMutation({
    mutationFn: createUser,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users']});
      console.log("success!")
    }
  });

  const handleAddPost = (user) => {
    createUserMutation.mutate({
      id: uuidv4(),
      ...user
    })
  }

  return (
    <div className="max-w-md mx-auto mt-5">
      <h1 className="text-2xl text-center mb-2">Add New User</h1>
      <UserForm onSubmit={handleAddPost} initialValue={{}} />
    </div>
  )
}

export default AddUser
C:\react-js\my-app\src\components\UserForm.jsx
//src\components\UserForm.jsx
import { useState } from "react"

const UserForm = ({ onSubmit, initialValue }) => {
  const [user, setUser] = useState({
    name: initialValue.name || "",
    email: initialValue.email || "",
    age: initialValue.age || ""
  });

  const handleChangeInput = (e) => {
    setUser({
      ...user,
      [e.target.name]: e.target.value
    })
  }

  const renderField = (label) => (
    <div>
      <label className="block text-sm font-medium text-gray-900">{label}</label>
      <input onChange={handleChangeInput} type="text" name={label.toLowerCase()} value={user[label.toLowerCase()]} 
        className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500blue-500"
      />
    </div>
  );

  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(user);
    setUser({
      name: "",
      email: "",
      age: ""
    })

  }

  return (
    <form onSubmit={handleSubmit}>
      <div className="mb-5">{renderField('Name')}</div>
      <div className="mb-5">{renderField('Email')}</div>
      <div className="mb-5">{renderField('Age')}</div>
      <button type="submit"
        className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
      >
        Submit</button>
    </form>
  )
}

export default UserForm
C:\react-js\my-app\src\api\users.jsx
//src\api\users.jsx
export async function fetchUsers() {
  const response = await fetch('http://localhost:3001/users');
  return response.json()
}

export async function fetchUser(id) {
  const response = await fetch(`http://localhost:3001/users/get/${id}`);
  return response.json()
}

export async function createUser(newUser) {
  const response = await fetch(`http://localhost:3001/users/create`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(newUser)
  });
  return response.json()
}

export async function updateUser(updatedUser) {
  const response = await fetch(`http://localhost:3001/users/update/${updatedUser.id}`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(updatedUser)
  });
  return response.json()
}

export async function deleteUser(id) {
  const response = await fetch(`http://localhost:3001/users/delete/${id}`, {
    method: "DELETE",
  });
  return response.json()
}
Run
C:\react-js\my-app> npm run dev

Monday, January 13, 2025

Nextjs Data fetching using SWR

Nextjs Data fetching using SWR

Install nextjs npx create-next-app@latest https://nextjs.org/docs/getting-started/installation

Install the following

SWR React Hooks for Data Fetching
npm i swr
https://swr.vercel.app/docs/getting-started
app\page.tsx
//app\page.tsx
import Post from "./components/post";

export default function Home() {
  return (
    <div className="max-w-7xl flex flex-col gap-10 mx-auto p-10">
      <div className="flex justify-between items-center">
        <h1 className="text-4xl font-bold">Nextjs Data fetching using SWR</h1>
      </div>
      <Post />
    </div>
  );
}
app\components\post.tsx
//app\components\post.tsx
"use client";

import useSWR from "swr"; //npm i swr https://swr.vercel.app/docs/getting-started

const fetcher = (url) => fetch(url).then((res) => res.json());

export default function Home() {
  const {
    data: posts,
    isLoading,
    isError: error,
  } = useSWR(
    'https://jsonplaceholder.typicode.com/posts',
    fetcher,
    { revalidateOnFocus: false, revalidateOnReconnect: false }
  );

  if (error) {
    return <p className='text-secondary'>Failed to fetch</p>;
  }

  return (
    <>
      <header className='p-7 text-center'>
        <h1 className='text-secondary font-bold text-xl'>Basic</h1>
      </header>
      <main className='max-w-[1100px] mx-auto mt-10 pb-10 px-4'>
        {isLoading ? (
          <p className='text-secondary'>Loading posts</p>
        ) : (
          <ul className='grid grid-cols-3 gap-x-3 gap-y-3'>
            {posts.map((post, index) => (
              <li className='bg-primary p-10 rounded-lg shadow-md border border-gray-200' key={index}>
                <span className='block mb-4 font-bold text-lg text-secondary capitalize'>
                  {post.title}
                </span>
                <span className='text-secondary'>{post.body}</span>
                <h2>{post.id}</h2>
              </li>
            ))}
          </ul>
        )}
      </main>
    </>
  );
}
app\infinite-loading\page.tsx
//app\infinite-loading\page.tsx
"use client";

import useSWRInfinite from 'swr/infinite';

const fetcher = (url) => fetch(url).then((res) => res.json());

const getKey = (pageIndex, previousPageData) => {
  if (pageIndex && !previousPageData.length) return null;
  return `https://jsonplaceholder.typicode.com/posts?_page=${pageIndex}&_limit=6`;
};

export default function Home() {
  const { data, size, setSize, isLoading } = useSWRInfinite(getKey, fetcher);

  return (
    <div className="max-w-7xl flex flex-col gap-10 mx-auto p-10">
      <div className="flex justify-between items-center">
        <h1 className="text-4xl font-bold">Nextjs Data fetching using SWR</h1>
      </div>
      <header className='p-7 text-center'>
        <h1 className='text-secondary font-bold text-xl'>Infinite Loading</h1>
      </header>
      <main className='max-w-[1100px] mx-auto mt-10 pb-10 px-4'>
        {isLoading ? (
          <p className='text-secondary'>Loading posts</p>
        ) : (
          <ul className='grid grid-cols-3 gap-x-3 gap-y-3'>
            {data.map((posts) => {
              return posts.map((post, index) => (
                <li
                  className='bg-primary p-10 rounded-lg shadow-md border border-gray-200'
                  key={index}
                >
                  <span className='block mb-4 font-bold text-lg text-secondary capitalize'>
                    {post.title}
                  </span>
                  <span className='text-secondary'>{post.body}</span>
                </li>
              ));
            })}
          </ul>
        )}
        <div className='max-w-[600px] mx-auto my-10 flex justify-center'>
          <button
            className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
            onClick={() => setSize(size + 1)}
          >
            Load more
          </button>
        </div>
      </main>
    </div>
  );
}
app\prevnext-pagination\page.tsx
//app\prevnext-pagination\page.tsx
"use client";

import { useState } from 'react';
import useSWR from 'swr';

const fetcher = (url) => fetch(url).then((res) => res.json());
const Page = ({ pageIndex }) => {
    const { data: posts, isLoading } = useSWR(
      `https://jsonplaceholder.typicode.com/posts?_page=${pageIndex}&_limit=6`,
      fetcher
    );

  return (
    isLoading ? (
          <p className='text-secondary'>Loading posts</p>
        ) : (
          <ul className='grid grid-cols-3 gap-x-3 gap-y-3'>
            {posts.map((post, index) => (
              <li className='bg-primary p-10 rounded-lg shadow-md border border-gray-200' key={index}>
                <span className='block mb-4 font-bold text-lg text-secondary capitalize'>
                  {post.title}
                </span>
                <span className='text-secondary'>{post.body}</span>
                <h2>{post.id}</h2>
              </li>
            ))}
          </ul>
        )
  )
}

export default function Home() {
  const [pageIndex, setPageIndex] = useState(1);

  return (
    <div className="max-w-7xl flex flex-col gap-10 mx-auto p-10">
      <div className="flex justify-between items-center">
        <h1 className="text-4xl font-bold">Nextjs Data fetching using SWR</h1>
      </div>
      <header className="p-7 text-center">
        <h1 className="text-secondary font-bold text-xl">Next Prev Pagination</h1>
      </header>
      <main className="max-w-[1100px] mx-auto mt-10 pb-10 px-4">
        <Page pageIndex={pageIndex} />
        <div className="hidden">
          <Page pageIndex={pageIndex + 1} />
        </div>
        <div className="max-w-[600px] mx-auto my-10 grid grid-cols-2 gap-x-3">
          <button
            className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
            onClick={() => setPageIndex((_currentPage) => _currentPage - 1)}
          >
            Prev
          </button>
          <button
            className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
            onClick={() => setPageIndex((_currentPage) => _currentPage + 1)}
          >
            Next
          </button>
        </div>
      </main>
    </div>
  );
}
run C:\nextjs>npm run dev

Monday, January 6, 2025

React.js Data Fetching With React SWR

React.js Data Fetching With React SWR
React JS
https://react.dev/learn/start-a-new-react-project

npx create-next-app@latest
npx create-react-app@latest {project name}
Create Project
C:\react-js>npx create-react-app@latest my-app

Run
C:\react-js\my-app> npm start
npm i react-router-dom
https://www.npmjs.com/package/react-router-dom

Install React SWR:
npm i swr
https://www.npmjs.com/package/swr
src\App.js
//src\App.js
import { BrowserRouter, Routes, Route } from "react-router-dom"; //npm i react-router-dom https://www.npmjs.com/package/react-router-dom
import Home from "./elements/Home";
import PostList from "./elements/PostList";
import PostDetails from "./elements/PostDetails";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/postswr" element={<PostList />} />
        <Route path="/post-detail/:postId" element={<PostDetails />} />
      </Routes>
    </BrowserRouter>
  );
}
 
export default App;
src\elements\Home.js
//src\elements\Home.js
import React  from 'react';
import Posts from './Posts'
import { Suspense } from "react";

function Home() {

  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="flex items-center justify-between gap-1 mb-5">
            <h1 className="text-4xl font-bold">Basic example fetch dummy data</h1>
        </div> 
        <div className="overflow-x-auto py-10">
            <Suspense fallback="Loading...">
                <Posts/> 
            </Suspense>
        </div>
    </div>
  )
}

export default Home
src\elements\Posts.js
//src\elements\Posts.js
import { useEffect, useState } from "react";
const base_url = 'https://dummyjson.com';

function Post() {
    const [posts, setPosts] = useState([]);
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState(null);
    useEffect(() => {
        const fetchData = async (url) => {
            setIsLoading(true);
            try {
                const data = await fetch(base_url + url)
                .then(res => res.json());
                setPosts(data ? data.posts : []);
            } catch(error) {
                setError('Failed to load');
            }
            
            setIsLoading(false);
        }
        fetchData('/posts');
    }, []);

    return (
        <div className='w-1/2 py-10 m-auto flex justify-between items-center align-middle flex-wrap gap-10'>
            {
                error ? <div>{error}</div> : null
            }
            {
                isLoading ? <div>loading...</div> : null
            }
            {
                posts ? posts.map(post => {
                    return (
                        <div key={post.id} className='btn-primary p-2 bg-blue-500 text-white text-lg rounded-lg hover:shadow-lg disabled:opacity-50'>
                            <h3><a>{post.title}</a></h3>
                            <p>{post.body}</p>
                            <span>Views: {post.views}</span>
                            <div>Tags: {post.tags.map(tag => <span key={tag}>{tag}</span>)}</div>
                        </div>
                    )
                }) : null
            }
        </div>
    );
}

export default Post;
src\elements\PostList.js
//src\elements\PostList.js
import React from "react";
import { Link } from "react-router-dom";
import useSWR from 'swr'; //npm i swr https://www.npmjs.com/package/swr

function PostList()
{
    const base_url = 'https://dummyjson.com';

    const fetcher = (url) => {
        return fetch(base_url + url).then(res => res.json());
    };

    const { data, error, isLoading } = useSWR('/posts', fetcher);

    const style = {margin: '2px', background: '#000000', padding: '2px 7px', borderRadius: '6px'};

    return (
        <div className="w-screen py-20 flex justify-center flex-col items-center">
            <div className="flex items-center justify-between gap-1 mb-5">
                <h1 className="text-4xl font-bold">React.js Data Fetching With React SWR</h1>
            </div> 
            <div className="overflow-x-auto py-1">
                <div className='w-1/2 py-10 m-auto flex justify-between items-center align-middle flex-wrap gap-10'>
                    {
                        error ? <div>failed to load</div> : null
                    }

                    {
                        isLoading ? <div>loading...</div> : null
                    }

                    {
                        data ? data.posts.map(post => {
                            return (
                                <div key={post.id} className='btn-primary p-2 bg-blue-500 text-white text-lg rounded-lg hover:shadow-lg disabled:opacity-50'>
                                    <h3><Link to={`/post-detail/${post.id}`}>{post.title}</Link></h3>
                                    <p>{post.body}</p>
                                    <span>Views: {post.views}</span>
                                    <div>Tags: {post.tags.map(tag => <span key={tag} style={style}>{tag}</span>)}</div>
                                </div>
                            )
                        }) : null
                    }
                </div>
            </div>
        </div>        
    )
}

export default PostList
src\elements\PostDetails.js
//src\elements\PostDetails.js
import React from "react";
import { useParams } from 'react-router-dom';
import useSWR from 'swr';

const base_url = 'https://dummyjson.com';

function PostDetails()
{
    let { postId } = useParams();

    const fetcher = async ([url, postId]) => {

        const res = await fetch(base_url + url + `/${postId}`);

        if (!res.ok) {
            const error = new Error('An error occurred while fetching the data.')
            error.status = res.status
            throw error
          }
         
          return res.json()
    }

    const { data: post, error,  isLoading } = useSWR(['/posts', postId], fetcher);

    const style = {margin: '2px', background: '#d7d4d4', padding: '2px 7px', borderRadius: '6px'};

    return (
        <div className='w-1/2 py-10 m-auto flex justify-between items-center align-middle flex-wrap gap-10'>
            {
                error ? <div>{error.message}</div> : null
            }

            {
                isLoading ? <div>loading...</div> : null
            }

            {
                post && (
                    <div className='btn-primary p-2 bg-blue-500 text-white text-lg rounded-lg hover:shadow-lg disabled:opacity-50'>
                            <h3>{post.title}</h3>
                            <p>{post.body}</p>
                            <span>Views: {post.views}</span>
                            <div>Tags: {post.tags.map(tag => <span key={tag} style={style}>{tag}</span>)}</div>
                            <div>Likes: 👍 {post.reactions.likes}</div>
                            <div>Deslikes: 👎 {post.reactions.likes}</div>
                        </div>
                )
            }
        </div>
    )
}

export default PostDetails;
Run C:\react-j\my-app>npm start
http://localhost:3000/

Wednesday, December 11, 2024

Nextjs How to connect mongoDB Atlas using mongoose | Insert Data Server Actions

Nextjs How to connect mongoDB Atlas using mongoose | Insert Data
Install nextjs npx create-next-app@latest https://nextjs.org/docs/getting-started/installation

Install the following

Mongoose
npm install mongoose
https://www.npmjs.com/package/mongoose
app\addProduct\page.tsx
//app\addProduct\page.tsx
"use client";
 
import { useState } from "react";
import { useRouter } from "next/navigation";
 
export default function AddProduct() {
    const [name, setName] = useState("");
    const [image, setImage] = useState("");
    const [price, setPrice] = useState("");
    const [category, setCategory] = useState("");
 
    const router = useRouter();
 
    const handleSubmit = async (e) => {
        e.preventDefault();
 
        if (!name || !image) {
            alert("Name and image are required.");
            return;
        }
 
        try {
            const res = await fetch("/api/products", {
                method: "POST",
                headers: {
                    "Content-type": "application/json",
                },
                body: JSON.stringify({ name, image, price, category }),
            });
 
            if (res.ok) {
                router.push("/products");
            } else {
                throw new Error("Failed to create a Product");
            }
        } catch (error) {
            console.log(error);
        }
    };
 
    return (
        <div className="max-w-screen-lg mx-auto py-14">
        <h1 className="text-4xl font-bold">Nextjs How to connect mongoDB Atlas using mongoose | Insert Data</h1>
        <form onSubmit={handleSubmit} className="max-w-sm mx-auto">
        <div className="mb-5">
            <label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Name</label>
            <input
                onChange={(e) => setName(e.target.value)}
                value={name}
                className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                type="text"
                placeholder="Product Name"
            />
        </div>
        <div className="mb-5">
            <label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Image</label>
            <input
                onChange={(e) => setImage(e.target.value)}
                value={image}
                className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                type="text"
                placeholder="/images/1.jpg"
                defaultValue="/images/1.jpg"
            />
        </div>
        <div className="mb-5">
            <label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Price</label>
            <input
                onChange={(e) => setPrice(e.target.value)}
                value={price}
                className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                type="number"
                placeholder="1"
                defaultValue="1"
            />
        </div>
        <div className="mb-5">
            <label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Category</label>
            <input
                onChange={(e) => setCategory(e.target.value)}
                value={category}
                className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                type="text"
                placeholder="Product Category"
            />
        </div>
        <button type="submit" className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Add Product</button>
        </form>
        </div>
    );
}
app\api\products\route.ts
//app\api\products\route.ts
import connectMongoDB from "@/libs/mongodb";
import Product from "@/models/ProductModel";
import { NextResponse } from "next/server";
 
export async function POST(request) {
    const { name, image,price,category } = await request.json();
    await connectMongoDB();
    await Product.create({ name, image, price, category });
    return NextResponse.json({ message: "Product Created" }, { status: 201 });
}
libs\mongodb.ts
//libs\mongodb.ts
import mongoose from "mongoose"; //npm install mongoose https://www.npmjs.com/package/mongoose
 
const connectMongoDB = async () => { //mongodb+srv://<username>:<password>@firstcluster.4rc4s.mongodb.net/<dbname>?retryWrites=true&w=majority
    try {
        await mongoose
        .connect(process.env.MONGODB_URI, {
            useNewUrlParser: true,
            useUnifiedTopology: true,
        })
        .then(() => {
            console.log('Connected to the Database.');
        })
        .catch(err => console.error(err));
        //console.log("Connected to MongoDB.");
    } catch (error) {
        console.log(error);
    }
};
 
export default connectMongoDB;
models\ProductModel.ts
//models\ProductModel.ts
import mongoose, { Schema } from "mongoose";
 
const topicSchema = new Schema(
    {
        name: { type: String, required: true },
        category: { type: String, required: true },
        image: { type: String, required: true },
        price: { type: Number, required: true },
    },
    {
        timestamps: true,
    }
);
 
const ProductModel = mongoose.models.Product || mongoose.model("Product", topicSchema);
 
export default ProductModel;
.env
//.env
MONGODB_URI= "mongodb+srv://"username":"password"@cluster0.x45tgvn.mongodb.net/"databasename"?retryWrites=true&w=majority&appName=Cluster0"
run C:\nextjs>npm run dev

Friday, December 6, 2024

Laravel 11 CRUD with Upload Image Search and Pagination

Laravel 11 CRUD with Upload Image Search and Pagination
Download Laravel App
https://laravel.com/docs/11.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel11dev
DB_USERNAME=root
DB_PASSWORD=root
Database Migration

php artisan make:model Product -m
C:\xampp\htdocs\laravel\my-app>php artisan make:model Product -m
database/migrations/create_products_table.php
//database/migrations/create_products_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('sku');
            $table->double('price', 10, 2);
            $table->text('description')->nullable();
            $table->string('image')->nullable();;
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};
Create Controller

php artisan make:controller ProductController
C:\xampp\htdocs\laravel\my-app>php artisan make:controller ProductController
app/Http/Controllers/ProductController.php
//app/Http/Controllers/ProductController.php
<?php

namespace App\Http\Controllers;

use App\Models\Product; //php artisan make:model Product -m 
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\DB;

class ProductController extends Controller
{
    public function index() {
        //$products = Product::paginate(2); https://laravel.com/docs/11.x/pagination
        $products = Product::orderBy('created_at', 'DESC')->paginate(3);
        //$products = Product::orderBy('created_at','DESC')->get(); 

        return view('products.list',[
            'products' => $products
        ]);
        
    }

    public function search(Request $request)
    {
        if (!empty($request)) {
            $search = $request->input('search');

            $products = Product::where(
                'name',
                'like',
                "$search%"
            )
                ->orWhere('sku', 'like', "$search%")
                ->paginate(2);

            return view('products.list', compact('products'));
        }

        $products = DB::table('products')
        ->orderBy('id', 'DESC')
        ->paginate(5);
        return view('products.list', compact('products'));
    }

    public function create() {
        return view('products.create');
    }

    public function store(Request $request) {
        $rules = [
            'name' => 'required|min:5',
            'sku' => 'required|min:3',
            'price' => 'required|numeric'            
        ];

        if ($request->image != "") {
            $rules['image'] = 'image';
        }

        $validator = Validator::make($request->all(),$rules);

        if ($validator->fails()) {
            return redirect()->route('products.create')->withInput()->withErrors($validator);
        }

        // insert product
        $product = new Product();
        $product->name = $request->name;
        $product->sku = $request->sku;
        $product->price = $request->price;
        $product->description = $request->description;
        $product->save();

        if ($request->image != "") {
            // store image
            $image = $request->image;
            $ext = $image->getClientOriginalExtension();
            $imageName = time().'.'.$ext; // Unique image name

            // Save image to products directory
            $image->move(public_path('uploads/products'),$imageName);

            // Save image name
            $product->image = $imageName;
            $product->save();
        }        

        return redirect()->route('products.index')->with('success','Product added successfully.');
    }

    public function edit($id) {
        $product = Product::findOrFail($id);
        return view('products.edit',[
            'product' => $product
        ]);
    }

    public function update($id, Request $request) {

        $product = Product::findOrFail($id);

        $rules = [
            'name' => 'required|min:5',
            'sku' => 'required|min:3',
            'price' => 'required|numeric'            
        ];

        if ($request->image != "") {
            $rules['image'] = 'image';
        }

        $validator = Validator::make($request->all(),$rules);

        if ($validator->fails()) {
            return redirect()->route('products.edit',$product->id)->withInput()->withErrors($validator);
        }

        // update product
        $product->name = $request->name;
        $product->sku = $request->sku;
        $product->price = $request->price;
        $product->description = $request->description;
        $product->save();

        if ($request->image != "") {

            // delete old image
            File::delete(public_path('uploads/products/'.$product->image));

            // store image
            $image = $request->image;
            $ext = $image->getClientOriginalExtension();
            $imageName = time().'.'.$ext; // Unique image name

            // Save image to products directory
            $image->move(public_path('uploads/products'),$imageName);

            // Save image name
            $product->image = $imageName;
            $product->save();
        }        

        return redirect()->route('products.index')->with('success','Product updated successfully.');
    }

    public function destroy($id) {
        $product = Product::findOrFail($id);

       // delete image
       File::delete(public_path('uploads/products/'.$product->image));

       // delete product
       $product->delete();

       return redirect()->route('products.index')->with('success','Product deleted successfully.');
    }
}
View Blade File resources/views/products/list.blade.php
//resources/views/products/list.blade.php
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel 11 CRUD with Upload Image Search and Pagination | Cairocoders</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>

<body>
    <div class="bg-primary py-3">
        <h3 class="text-white text-center">Laravel 11 CRUD with Upload Image Search and Pagination</h3>
    </div>
    <div class="container">
        <div class="row justify-content-center mt-4">
            <div class="col-md-10 d-flex justify-content-end">
                <form method="GET" action="/products/search">
                    <div class="input-group" style="margin-right:5px;">
                        <div class="form-outline" data-mdb-input-init>
                            <input class="form-control" name="search" placeholder="Searh..." value="{{ request()->input('search') ? request()->input('search') : '' }}">
                        </div>
                        <button type="submit" class="btn btn-success">Search</button>
                    </div>
                </form>
                <a href="{{ route('products.create') }}" class="btn btn-primary">Create</a>
            </div>
        </div>
        <div class="row d-flex justify-content-center">
            @if (Session::has('success'))
            <div class="col-md-10 mt-4">
                <div class="alert alert-success">
                    {{ Session::get('success') }}
                </div>
            </div>
            @endif
            <div class="col-md-10">
                <div class="card borde-0 shadow-lg my-4">
                    <div class="card-header bg-primary">
                        <h3 class="text-white">Products</h3>
                    </div>

                    <div class="card-body">
                        <table class="table">
                            <tr>
                                <th>ID</th>
                                <th></th>
                                <th>Name</th>
                                <th>Sku</th>
                                <th>Price</th>
                                <th>Created at</th>
                                <th>Action</th>
                            </tr>
                            @if ($products->isNotEmpty())
                            @foreach ($products as $product)
                            <tr>
                                <td>{{ $product->id }}</td>
                                <td>
                                    @if ($product->image != "")
                                    <img width="50" src="{{ asset('uploads/products/'.$product->image) }}" alt="">
                                    @endif
                                </td>
                                <td>{{ $product->name }}</td>
                                <td>{{ $product->sku }}</td>
                                <td>${{ $product->price }}</td>
                                <td>{{ \Carbon\Carbon::parse($product->created_at)->format('d M, Y') }}</td>
                                <td>
                                    <a href="{{ route('products.edit',$product->id) }}" class="btn btn-info">Edit</a>
                                    <a href="#" onclick="deleteProduct({{ $product->id  }});" class="btn btn-danger">Delete</a>
                                    <form id="delete-product-from-{{ $product->id  }}" action="{{ route('products.destroy',$product->id) }}" method="post">
                                        @csrf
                                        @method('delete')
                                    </form>
                                </td>
                            </tr>
                            @endforeach

                            @endif

                        </table>

                        {!! $products->withQueryString()->links('pagination::bootstrap-5') !!}
                    </div>

                </div>
            </div>
        </div>
    </div>
    <script>
        function deleteProduct(id) {
            if (confirm("Are you sure you want to delete product?")) {
                document.getElementById("delete-product-from-" + id).submit();
            }
        }
    </script>
</body>

</html>
resources/views/products/create.blade.php
//resources/views/products/create.blade.php
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel 11 CRUD with Upload Image Search and Pagination | Cairocoders</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>

<body>
    <div class="bg-primary py-3">
        <h3 class="text-white text-center">Laravel 11 CRUD with Upload Image Search and Pagination</h3>
    </div>
    <div class="container">
        <div class="row justify-content-center mt-4">
            <div class="col-md-10 d-flex justify-content-end">
                <a href="{{ route('products.index') }}" class="btn btn-primary">Back</a>
            </div>
        </div>
        <div class="row d-flex justify-content-center">
            <div class="col-md-10">
                <div class="card borde-0 shadow-lg my-4">
                    <div class="card-header bg-primary">
                        <h3 class="text-white">Create Product</h3>
                    </div>
                    <form enctype="multipart/form-data" action="{{ route('products.store') }}" method="post">
                        @csrf
                        <div class="card-body">
                            <div class="mb-3">
                                <label for="" class="form-label h5">Name</label>
                                <input value="{{ old('name') }}" type="text" class="@error('name') is-invalid @enderror form-control-lg form-control" placeholder="Name" name="name">
                                @error('name')
                                <p class="invalid-feedback">{{ $message }}</p>
                                @enderror
                            </div>
                            <div class="mb-3">
                                <label for="" class="form-label h5">Sku</label>
                                <input value="{{ old('sku') }}" type="text" class="@error('sku') is-invalid @enderror form-control form-control-lg" placeholder="Sku" name="sku">
                                @error('sku')
                                <p class="invalid-feedback">{{ $message }}</p>
                                @enderror
                            </div>
                            <div class="mb-3">
                                <label for="" class="form-label h5">Price</label>
                                <input value="{{ old('price') }}" type="text" class="@error('price') is-invalid @enderror form-control form-control-lg" placeholder="Price" name="price">
                                @error('price')
                                <p class="invalid-feedback">{{ $message }}</p>
                                @enderror
                            </div>
                            <div class="mb-3">
                                <label for="" class="form-label h5">Description</label>
                                <textarea placeholder="Description" class="form-control" name="description" cols="30" rows="5">{{ old('description') }}</textarea>
                            </div>
                            <div class="mb-3">
                                <label for="" class="form-label h5">Image</label>
                                <input type="file" class="form-control form-control-lg" placeholder="Price" name="image">
                            </div>
                            <div class="d-grid">
                                <button class="btn btn-lg btn-primary">Submit</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>

</body>

</html>
resources/views/products/edit.blade.php
//resources/views/products/edit.blade.php
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel 11 CRUD with Upload Image Search and Pagination | Cairocoders</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>

<body>
    <div class="bg-primary py-3">
        <h3 class="text-white text-center">Laravel 11 CRUD with Upload Image Search and Pagination</h3>
    </div>
    <div class="container">
        <div class="row justify-content-center mt-4">
            <div class="col-md-10 d-flex justify-content-end">
                <a href="{{ route('products.index') }}" class="btn btn-primary">Back</a>
            </div>
        </div>
        <div class="row d-flex justify-content-center">
            <div class="col-md-10">
                <div class="card borde-0 shadow-lg my-4">
                    <div class="card-header bg-primary">
                        <h3 class="text-white">Edit Product</h3>
                    </div>
                    <form enctype="multipart/form-data" action="{{ route('products.update',$product->id) }}" method="post">
                        @method('put')
                        @csrf
                        <div class="card-body">
                            <div class="mb-3">
                                <label for="" class="form-label h5">Name</label>
                                <input value="{{ old('name',$product->name) }}" type="text" class="@error('name') is-invalid @enderror form-control-lg form-control" placeholder="Name" name="name">
                                @error('name')
                                <p class="invalid-feedback">{{ $message }}</p>
                                @enderror
                            </div>
                            <div class="mb-3">
                                <label for="" class="form-label h5">Sku</label>
                                <input value="{{ old('sku',$product->sku) }}" type="text" class="@error('sku') is-invalid @enderror form-control form-control-lg" placeholder="Sku" name="sku">
                                @error('sku')
                                <p class="invalid-feedback">{{ $message }}</p>
                                @enderror
                            </div>
                            <div class="mb-3">
                                <label for="" class="form-label h5">Price</label>
                                <input value="{{ old('price',$product->price) }}" type="text" class="@error('price') is-invalid @enderror form-control form-control-lg" placeholder="Price" name="price">
                                @error('price')
                                <p class="invalid-feedback">{{ $message }}</p>
                                @enderror
                            </div>
                            <div class="mb-3">
                                <label for="" class="form-label h5">Description</label>
                                <textarea placeholder="Description" class="form-control" name="description" cols="30" rows="5">{{ old('description',$product->description) }}</textarea>
                            </div>
                            <div class="mb-3">
                                <label for="" class="form-label h5">Image</label>
                                <input type="file" class="form-control form-control-lg" placeholder="Price" name="image">

                                @if ($product->image != "")
                                <img class="w-50 my-3" src="{{ asset('uploads/products/'.$product->image) }}" alt="">
                                @endif
                            </div>
                            <div class="d-grid">
                                <button class="btn btn-lg btn-primary">Update</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>

</body>

</html>
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController; //php artisan make:controller ProductController 

Route::get('/', function () {
    return view('welcome');
});

Route::controller(ProductController::class)->group(function () {
    Route::get('/products', 'index')->name('products.index');
    Route::get('/products/create', 'create')->name('products.create');
    Route::post('/products', 'store')->name('products.store');
    Route::get('/products/{product}/edit', 'edit')->name('products.edit');
    Route::put('/products/{product}', 'update')->name('products.update');
    Route::delete('/products/{product}', 'destroy')->name('products.destroy');
    Route::get('/products/search', 'search')->name('search');
});
Run C:\xampp\htdocs\laravel\my-app>php artisan serve
Starting Laravel development server: http://127.0.0.1:8000

Tuesday, December 3, 2024

Laravel Dynamic Dependent Dropdown | Country State City | Jquery AJAX

Laravel Dynamic Dependent Dropdown | Country State City | Jquery AJAX
Download Laravel App
https://laravel.com/docs/11.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel11dev
DB_USERNAME=root
DB_PASSWORD=root
Database Migration

php artisan make:model Country -m
C:\xampp\htdocs\laravel\my-app>php artisan make:model Country -m
database/migrations/create_countries_table.php
Country
//database/migrations/create_countries_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up() 
    {
        Schema::create('countries', function (Blueprint $table) {
            $table->id();
            $table->string('name');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('countries');
    }
};
State
php artisan make:model State -m
C:\xampp\htdocs\laravel\my-app>php artisan make:model State -m
database/migrations/create_states_table.php
//<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up()
    {
        Schema::create('states', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->integer('country_id');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('states');
    }
};
City
php artisan make:model City -m
C:\xampp\htdocs\laravel\my-app>php artisan make:model City -m
database/migrations/create_cities_table.php
//database/migrations/create_cities_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up()
    {
        Schema::create('cities', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->integer('state_id');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('cities');
    }
};
SQL Database Table Counrty State and City : https://github.com/cairocodes/cairocoders/blob/main/country-state-city.sql

Create Controller

php artisan make:controller AccountController
C:\xampp\htdocs\laravel\my-app>php artisan make:controller AccountController
app/Http/Controllers/AccountController.php
//app/Http/Controllers/AccountController.php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\DB;

class AccountController extends Controller
{
    public function index(){
        $countries = \DB::table('countries')->orderBy('name','ASC')->get();
        $data['countries'] = $countries;
        return view('users.create',$data);
    }

    public function fetchStates($country_id = null) {
        $states = \DB::table('states')->where('country_id',$country_id)->get();

        return response()->json([
            'status' => 1,
            'states' => $states
        ]);
    }

    public function fetchCities($state_id = null) {
        $cities = \DB::table('cities')->where('state_id',$state_id)->get();

        return response()->json([
            'status' => 1,
            'cities' => $cities
        ]);
    }

    public function save(Request $request){

        $validator = Validator::make($request->all(),[
            'name' => 'required',
            'email' => 'required|email'
        ]);

        if ($validator->passes()) {

            User::create([
                'name' => $request->name,
                'email' => $request->email,
                'password' => "123456",
                'country' => $request->country,
                'state' => $request->state,
                'city' => $request->city,
            ]);

            $request->session()->flash('success','User added successfully.');

            return response()->json([
                'status' =>  1                
            ]);

        } else {
            // return error 

            return response()->json([
                'status' =>  0,
                'errors' => $validator->errors()
            ]);
        }

    }

    public function list() {
        $users = DB::table('users')->get();
        $data['users'] = $users;
        return view('users.list',$data);
    }

    public function edit($id) {
        $user = User::where('id',$id)->first();

        $countries = \DB::table('countries')->orderBy('name','ASC')->get();
        $data['countries'] = $countries;


        $states = \DB::table('states')->where('country_id',$user->country)->orderBy('name','ASC')->get();        
        $data['states'] = $states;

        $cities = \DB::table('cities')->where('state_id',$user->state)->orderBy('name','ASC')->get();        
        $data['cities'] = $cities;

        if ($user == null ) {
            return redirect(url('/list'));
        }

        $data['user'] = $user;

        return view('users.edit',$data);
    }


    public function update($id, Request $request){

        $user = User::find($id);

        if($user == null) {

            $request->session()->flash('error','Enither user deleted or not found.');

            return response()->json([
                'status' =>  '400'                
            ]);
        }

        $validator = Validator::make($request->all(),[
            'name' => 'required',
            'email' => 'required|email'
        ]);

        if ($validator->passes()) {
            $user->name = $request->name;
            $user->email = $request->email;
            $user->country = $request->country;
            $user->state = $request->state;
            $user->city = $request->city;
            $user->save(); 

            $request->session()->flash('success','User updated successfully.');

            return response()->json([
                'status' =>  1                
            ]);

        } else {
            // return error 

            return response()->json([
                'status' =>  0,
                'errors' => $validator->errors()
            ]);
        }

    }
}
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AccountController;

Route::get('/', function () {
    return view('welcome');
});

Route::get('/create', [AccountController::class, 'index']);
Route::post('/fetch-states/{id}', [AccountController::class, 'fetchStates']);
Route::post('/fetch-cities/{id}', [AccountController::class, 'fetchCities']);

Route::post('/save', [AccountController::class, 'save']);
Route::get('/list', [AccountController::class, 'list']);

Route::get('/edit/{id}', [AccountController::class, 'edit']);
Route::post('/edit/{id}', [AccountController::class, 'update']);
View Blade File resources/views/users/create.blade.php
//resources/views/users/create.blade.php
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Dynamic Dependent Dropdown</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <meta name="_token" content="{{ csrf_token() }}">
</head>

<body>
    <div class="bg p-3 shadow-lg text-center">
        <h4>Laravel Dynamic Dependent Dropdown | Country State City | Jquery AJAX</h4>
    </div>
    <div class="container mt-3">
        <div class="row d-flex justify-content-center">
            <div class="col-md-6">
                <a href="{{ url('/list') }}" class="btn btn-primary mb-3">Back</a>
                <div class="card card-primary p-4 border-0 shadow-lg">
                    <form action="" id="frm" name="frm" method="post">
                        <div class="card-body">
                            <h3>Create User</h3>
                            <div class="mb-3">
                                <input type="text" class="form-control-lg form-control" name="name" id="name" placeholder="Enter Name">
                                <p class="invalid-feedback" id="name-error"></p>
                            </div>
                            <div class="mb-3">
                                <input type="text" class="form-control-lg form-control" name="email" id="email" placeholder="Enter Email">
                                <p class="invalid-feedback" id="email-error"></p>
                            </div>
                            <div class="mb-3">
                                <select name="country" id="country" class="form-control-lg form-select">
                                    <option value="">Select Country</option>
                                    @if (!empty($countries))
                                    @foreach ($countries as $country)
                                    <option value="{{ $country->id }}">{{ $country->name }}</option>
                                    @endforeach
                                    @endif
                                </select>
                            </div>

                            <div class="mb-3">
                                <select name="state" id="state" class="form-control-lg form-select">
                                    <option value="">Select State</option>
                                </select>
                            </div>

                            <div class="mb-3">
                                <select name="city" id="city" class="form-control-lg form-select">
                                    <option value="">Select City</option>
                                </select>
                            </div>

                            <div class="d-grid">
                                <button class="btn btn-primary btn-lg">Create</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <!--https://blog.jquery.com/2023/08/28/jquery-3-7-1-released-reliable-table-row-dimensions/ https://getbootstrap.com/ -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
    <script>
        $.ajaxSetup({
            headers: {
                'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')
            }
        });

        $(document).ready(function() {
            $("#country").change(function() {
                var country_id = $(this).val();

                if (country_id == "") {
                    var country_id = 0;
                }
                //alert(country_id);
                $.ajax({
                    url: '{{ url("/fetch-states/") }}/' + country_id,
                    type: 'post',
                    dataType: 'json',
                    success: function(response) {
                        $('#state').find('option:not(:first)').remove();
                        $('#city').find('option:not(:first)').remove();

                        if (response['states'].length > 0) {
                            $.each(response['states'], function(key, value) {
                                $("#state").append("<option value='" + value['id'] + "'>" + value['name'] + "</option>")
                            });
                        }
                    }
                });
            });


            $("#state").change(function() {
                var state_id = $(this).val();

                //console.log(state_id);

                if (state_id == "") {
                    var state_id = 0;
                }

                $.ajax({
                    url: '{{ url("/fetch-cities/") }}/' + state_id,
                    type: 'post',
                    dataType: 'json',
                    success: function(response) {
                        $('#city').find('option:not(:first)').remove();

                        if (response['cities'].length > 0) {
                            $.each(response['cities'], function(key, value) {
                                $("#city").append("<option value='" + value['id'] + "'>" + value['name'] + "</option>")
                            });
                        }
                    }
                });
            });

        });

        $("#frm").submit(function(event) {
            event.preventDefault();
            $.ajax({
                url: '{{ url("/save") }}',
                type: 'post',
                data: $("#frm").serializeArray(),
                dataType: 'json',
                success: function(response) {
                    alert(response['status']);
                    if (response['status'] == 1) {
                        window.location.href = "{{ url('list') }}";
                    } else {
                        if (response['errors']['name']) {
                            $("#name").addClass('is-invalid');
                            $("#name-error").html(response['errors']['name']);
                        } else {
                            $("#name").removeClass('is-invalid');
                            $("#name-error").html("");
                        }

                        if (response['errors']['email']) {
                            $("#email").addClass('is-invalid');
                            $("#email-error").html(response['errors']['email']);
                        } else {
                            $("#email").removeClass('is-invalid');
                            $("#email-error").html("");
                        }
                    }
                }
            });
        })
    </script>
</body>

</html>
resources/views/users/edit.blade.php
//resources/views/users/edit.blade.php
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Dynamic Dependent Dropdown</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <meta name="_token" content="{{ csrf_token() }}">
</head>

<body>
    <div class="bg p-3 text-white shadow-lg text-center">
        <h4>Laravel Dynamic Dependent Dropdown | Country State City | Jquery AJAX</h4>
    </div>
    <div class="container mt-3">
        <div class="row d-flex justify-content-center">
            <div class="col-md-6">
                <a href="{{ url('/list') }}" class="btn btn-primary mb-3">Back</a>
                <div class="card card-primary p-4 border-0 shadow-lg">
                    <form action="" id="frm" name="frm" method="post">
                        <div class="card-body">
                            <h3>Edit User</h3>
                            <div class="mb-3">
                                <input type="text" class="form-control-lg form-control" name="name" id="name" placeholder="Enter Name" value="{{ $user->name }}">
                                <p class="invalid-feedback" id="name-error"></p>
                            </div>
                            <div class="mb-3">
                                <input type="text" class="form-control-lg form-control" name="email" id="email" placeholder="Enter Email" value="{{ $user->email }}">
                                <p class="invalid-feedback" id="email-error"></p>
                            </div>
                            <div class="mb-3">
                                <select name="country" id="country" class="form-control-lg form-select">
                                    <option value="">Select Country</option>
                                    @if (!empty($countries))
                                    @foreach ($countries as $country)
                                    <option {{ ($user->country == $country->id) ? 'selected' : '' }} value="{{ $country->id }}">{{ $country->name }}</option>
                                    @endforeach
                                    @endif
                                </select>
                            </div>

                            <div class="mb-3">
                                <select name="state" id="state" class="form-control-lg form-select">
                                    <option value="">Select State</option>
                                    @if (!empty($states))
                                    @foreach ($states as $state)
                                    <option {{ ($user->state == $state->id) ? 'selected' : '' }} value="{{ $state->id }}">{{ $state->name }}</option>
                                    @endforeach
                                    @endif
                                </select>
                            </div>

                            <div class="mb-3">
                                <select name="city" id="city" class="form-control-lg form-select">
                                    <option value="">Select City</option>
                                    @if (!empty($cities))
                                    @foreach ($cities as $city)
                                    <option {{ ($user->city == $city->id) ? 'selected' : '' }} value="{{ $city->id }}">{{ $city->name }}</option>
                                    @endforeach
                                    @endif
                                </select>
                            </div>

                            <div class="d-grid">
                                <button class="btn btn-primary btn-lg">Update</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>

    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <!--https://blog.jquery.com/2023/08/28/jquery-3-7-1-released-reliable-table-row-dimensions/ https://getbootstrap.com/ -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
    <script>
        $.ajaxSetup({
            headers: {
                'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')
            }
        });

        $(document).ready(function() {
            $("#country").change(function() {
                var country_id = $(this).val();

                if (country_id == "") {
                    var country_id = 0;
                }

                $.ajax({
                    url: '{{ url("/fetch-states/") }}/' + country_id,
                    type: 'post',
                    dataType: 'json',
                    success: function(response) {
                        $('#state').find('option:not(:first)').remove();
                        $('#city').find('option:not(:first)').remove();

                        if (response['states'].length > 0) {
                            $.each(response['states'], function(key, value) {
                                $("#state").append("<option value='" + value['id'] + "'>" + value['name'] + "</option>")
                            });
                        }
                    }
                });
            });


            $("#state").change(function() {
                var state_id = $(this).val();

                console.log(state_id);

                if (state_id == "") {
                    var state_id = 0;
                }

                $.ajax({
                    url: '{{ url("/fetch-cities/") }}/' + state_id,
                    type: 'post',
                    dataType: 'json',
                    success: function(response) {
                        $('#city').find('option:not(:first)').remove();

                        if (response['cities'].length > 0) {
                            $.each(response['cities'], function(key, value) {
                                $("#city").append("<option value='" + value['id'] + "'>" + value['name'] + "</option>")
                            });
                        }
                    }
                });
            });

        });

        $("#frm").submit(function(event) {
            event.preventDefault();
            $.ajax({
                url: '{{ url("/edit/".$user->id) }}',
                type: 'post',
                data: $("#frm").serializeArray(),
                dataType: 'json',
                success: function(response) {
                    if (response['status'] == 1) {
                        window.location.href = "{{ url('list') }}";
                    } else {
                        if (response['errors']['name']) {
                            $("#name").addClass('is-invalid');
                            $("#name-error").html(response['errors']['name']);
                        } else {
                            $("#name").removeClass('is-invalid');
                            $("#name-error").html("");
                        }

                        if (response['errors']['email']) {
                            $("#email").addClass('is-invalid');
                            $("#email-error").html(response['errors']['email']);
                        } else {
                            $("#email").removeClass('is-invalid');
                            $("#email-error").html("");
                        }
                    }
                }
            });
        })
    </script>
</body>

</html>
resources/views/users/list.blade.php
//resources/views/users/list.blade.php
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Dynamic Dependent Dropdown</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <meta name="_token" content="{{ csrf_token() }}">
</head>

<body>
    <div class="bg p-3 text-white shadow-lg text-center">
        <h4>Laravel Dynamic Dependent Dropdown | Country State City | Jquery AJAX</h4>
    </div>
    <div class="container mt-3">
        <div class="row d-flex justify-content-center">
            <div class="col-md-6">
                <a href="{{ url('/create') }}" class="btn btn-primary mb-3">Create</a>
                <div class="card card-primary p-4 border-0 shadow-lg">

                    @if(Session::has('success'))
                    <div class="alert alert-success">
                        {{ Session::get('success') }}
                    </div>
                    @endif

                    @if(Session::has('error'))
                    <div class="alert alert-danger">
                        {{ Session::get('error') }}
                    </div>
                    @endif

                    <div class="card-body">
                        <h3>Users</h3>
                        <table class="table">
                            <thead>
                                <tr>
                                    <th>ID</th>
                                    <th>Name</th>
                                    <th>Email</th>
                                    <th>Edit</th>
                                </tr>

                                @if (!empty($users))
                                @foreach ($users as $user)
                                <tr>
                                    <td>{{ $user->id }}</td>
                                    <td>{{ $user->name }}</td>
                                    <td>{{ $user->email }}</td>
                                    <td>
                                        <a href="{{ url('edit/'.$user->id) }}" class="btn btn-success">Edit</a>
                                    </td>
                                </tr>
                                @endforeach
                                @endif

                            </thead>
                        </table>
                    </div>

                </div>
            </div>
        </div>
    </div>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <!--https://blog.jquery.com/2023/08/28/jquery-3-7-1-released-reliable-table-row-dimensions/ https://getbootstrap.com/ -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
    </body> 
    </html>
Run C:\xampp\htdocs\laravel\my-app>php artisan serve
Starting Laravel development server: http://127.0.0.1:8000

Monday, November 25, 2024

React.js WordPress Rest API Pagination with Featured Image and View Single Page | Tailwind CSS

React.js WordPress Rest API Pagination with Featured Image and View Single Page | Tailwind CSS
React JS
https://react.dev/learn/start-a-new-react-project

npx create-next-app@latest
npx create-react-app@latest {project name}
Create Project
C:\react-js>npx create-react-app@latest my-app
Run
C:\react-js\my-app> npm start

Install tailwindcss https://tailwindcss.com/docs/guides/create-react-app
npm install -D tailwindcss
npx tailwindcss
init Install

react-router-dom
https://www.npmjs.com/package/react-router-dom Install axios
src\App.js
//src\App.js 
import { BrowserRouter, Routes, Route } from "react-router-dom"; //npm i react-router-dom https://www.npmjs.com/package/react-router-dom
import Home from "./elements/Home";
import ViewPage from "./elements/ViewPage";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/posts/:slug" element={<ViewPage />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;
src\elements\Home.js
//src\elements\Home.js
import React  from 'react';
import Posts from './Posts'
import { Suspense } from "react";
import { IoSearch } from "react-icons/io5"; //https://www.npmjs.com/package/react-icons npm install react-icons --save

function Home() {

  return (
    <div className="w-screen py-20 flex justify-center flex-col items-center">
        <div className="flex items-center justify-between gap-1 mb-5">
            <h1 className="text-4xl font-bold">React.js WordPress Rest API Pagination with Featured Image and View Single Page | Tailwind CSS</h1>
        </div> 
        <div className="overflow-x-auto py-10">
            <div className="mb-2 w-full text-right">
                <div className="relative flex flex-1">
                <input
                    type="text"
                    className="w-full border border-gray-200 py-2 pl-10 text-sm outline-2 rounded-sm"
                    placeholder="Search..."
                />
                <IoSearch className="absolute left-3 top-2 h-5 w-5 text-gray-500" />
                </div>
            </div>
            <Suspense fallback="Loading...">
                <Posts/>
            </Suspense>
        </div>
    </div>
  )
}

export default Home
src\elements\Posts.js
//src\elements\Posts.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';

export default function Posts() {
    const [posts, setPosts] = useState([]);

    const [currentPage, setCurrentPage] = useState(1);
    const [totalPages, setTotalPages] = useState(1);

    const perPage = 3;

    useEffect(() => {
        let url = `http://localhost:8888/cairocoders/wp-json/wp/v2/posts?per_page=${perPage}&page=${currentPage}&_embed`;
        axios.get(url).then((res) => {
            const { data, headers } = res;
            setTotalPages(Number(headers['x-wp-totalpages']));
            setPosts(data);
            //console.log("Headers", headers['x-wp-totalpages']);
        });
    }, [currentPage])
    //console.log('posts', posts);

  return (
    <div>
      <div className="grid md:grid-cols-3 gap-5 mt-10">
        {posts.map((post, index) => (
          <div key={index} className="max-w-sm border border-gray-200 rounded-md shadow">
            <div className="relative aspect-video">
              <img src={post._embedded["wp:featuredmedia"][0].media_details.sizes.full.source_url}
                alt={post.title.rendered}
                sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
                className="rounded-t-md object-cover"/>
            </div>
            <div className="p-5">
              <h1>
                <a
                  href={`/posts/${post.slug}`}>
                  {post.title.rendered}
                </a>         
              </h1>
            </div>
          </div>
        ))}
      </div>
      { posts.length > 0 && (
        <div className='w-1/2 py-10 m-auto flex justify-between items-center align-middle flex-wrap gap-10'>
          <button className='btn-primary p-2 bg-blue-500 text-white text-lg rounded-lg hover:shadow-lg disabled:opacity-50'
            disabled={currentPage === 1}
            onClick={() => setCurrentPage(currentPage - 1)}
          >
          Previous
          </button>

          <span className='text-lg'>{currentPage} of {totalPages}</span>

          <button className='btn-primary p-2 bg-blue-500 text-white text-lg rounded-lg hover:shadow-lg disabled:opacity-50'
            disabled={currentPage === totalPages}
            onClick={() => setCurrentPage(currentPage + 1)}
          >
          Next
          </button>
        </div>
        )
      }
    </div>                          
 );
}
src\elements\ViewPage.js
//src\elements\ViewPage.js
import axios from 'axios';
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom';

const Single = () => {
    const { slug } = useParams();
    const [post, setPost] = useState({});

    useEffect(()=>{
        // axios 
        let url = "http://localhost:8888/cairocoders/wp-json/wp/v2/posts?_embed&slug="+slug;
        axios.get(url).then(res => {
            console.log('res', res);
            //console.log(res.data[0].id);
            setPost(res.data);
        }).catch(err => {
            console.log('error:', err.message);
        });
    }, []);
    return (
    <div className="min-h-screen flex items-center justify-center bg-slate-100">
      <div className="bg-white rounded-sm shadow p-8">
            {
                post.length ? (
                <div>
                  <h1 className="text-2xl font-bold mb-5">{post[0].title.rendered}</h1>
                    <div className="mb-4">            
                      <div 
                        dangerouslySetInnerHTML={{ __html: post[0]['content']['rendered'] }}
                      />   
                    </div>
                </div>
                ) : ('Loading....')
            
            }
      </div>
    </div>
    )
}
export default Single;
themes\cairocoders\functions.php
//themes\cairocoders\functions.php
<?php
/**
 * Theme functions and definitions
 *
 * @package cairocoders
 */

add_action('rest_api_init', 'register_rest_images' );

function register_rest_images(){
    register_rest_field( array('post'),
        'fimg_url',
        array(
            'get_callback'    => 'get_rest_featured_image',
            'update_callback' => null,
            'schema'          => null,
        )
    );
}

function get_rest_featured_image( $object, $field_name, $request ) {
    if( $object['featured_media'] ){
        $img = wp_get_attachment_image_src( $object['featured_media'], 'app-thumb' );
        return $img[0];
    }
    return false;
}
Run C:\react-j\my-app>npm start
http://localhost:3000/

Related Post