Skip to content

Commit

Permalink
Add redux
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyle Zarazan committed Nov 19, 2024
1 parent 1202754 commit 42727c1
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 58 deletions.
21 changes: 13 additions & 8 deletions app/javascript/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import React from 'react';
import { Routes, Route } from "react-router-dom";
import { Provider } from 'react-redux'
import { store } from '../store/store'

import Layout from "./Layout";
import Home from "./Home";
import Search from "./Search";
import RecipeTable from "./RecipeTable";
import FoodsTable from "./FoodsTable";

const App: React.FC = () => {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="search" element={<Search />} />
<Route path="recipes" element={<RecipeTable />} />
<Route path="ingredients" element={<RecipeTable />} />
</Route>
</Routes>
<Provider store={store}>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="search" element={<Search />} />
<Route path="recipes" element={<RecipeTable />} />
<Route path="ingredients" element={<FoodsTable />} />
</Route>
</Routes>
</Provider>
);
};

Expand Down
58 changes: 58 additions & 0 deletions app/javascript/components/FoodsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState, useEffect } from 'react';

interface Food {
id: number;
name: string;
created_at: string;
modified_at: string;
}

const FetchDataComponent: React.FC = () => {
const [foods, setFoods] = useState<Food[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);

// Fetch data when the component mounts
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('http://localhost:3000/api/foods');
if (!response.ok) {
throw new Error('Failed to fetch data');
}
const result: Food[] = await response.json();
setFoods(result); // Set the fetched data to the state
setLoading(false); // Data loaded, set loading to false
} catch (err: any) {
setError(err.message); // If error occurs, set the error message
setLoading(false); // Stop loading in case of error
}
};

fetchData();
}, []); // Empty dependency array to run only once on mount

if (loading) {
return <div>Loading...</div>;
}

if (error) {
return <div>Error: {error}</div>;
}

return (
<div>
<h2>Fetched Data:</h2>
<ul>
{foods.map((item) => (
<li key={item.id}>
{item.id}: {item.name}
{item.created_at}
</li>
))}
</ul>
</div>
);
};

export default FetchDataComponent;
6 changes: 3 additions & 3 deletions app/javascript/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ const Layout = (): JSX.Element => {
return (
<div className="m-2">
<div className="flex m-6">
<h1>ICON</h1>
<h1 className="text-orange-500">ICON</h1>
<Nav />
<h1 className="ml-auto">PROFILE</h1>
<h1 className="ml-auto text-white">PROFILE</h1>
</div>

<div className="m-6">
<div className="m-6 text-gray-400">
<Outlet />
</div>
</div>
Expand Down
82 changes: 36 additions & 46 deletions app/javascript/components/RecipeTable.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,48 @@
import React, { useState, useEffect } from 'react';
import React from 'react';
import { useEffect } from 'react'
import { useAppDispatch, useAppSelector } from '../store/hooks'
import { fetchRecipes } from '../store/recipesSlice'

interface Recipe {
id: number;
name: string;
created_at: string;
modified_at: string;
}

const FetchDataComponent: React.FC = () => {
const [recipes, setRecipes] = useState<Recipe[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const RecipeTable: React.FC = () => {
const dispatch = useAppDispatch()
const { items: recipes, status, error } = useAppSelector((state) => state.recipes)

// Fetch data when the component mounts
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('http://localhost:3000/api/recipes');
if (!response.ok) {
throw new Error('Failed to fetch data');
}
const result: Recipe[] = await response.json();
setRecipes(result); // Set the fetched data to the state
setLoading(false); // Data loaded, set loading to false
} catch (err: any) {
setError(err.message); // If error occurs, set the error message
setLoading(false); // Stop loading in case of error
}
};
dispatch(fetchRecipes())
}, [dispatch])

fetchData();
}, []); // Empty dependency array to run only once on mount

if (loading) {
return <div>Loading...</div>;
if (status === 'loading') {
return <div>Loading...</div>
}

if (error) {
return <div>Error: {error}</div>;
if (status === 'failed') {
return <div>Error: {error}</div>
}

return (
<div>
<h2>Fetched Data:</h2>
<ul>
{recipes.map((item) => (
<li key={item.id}>
{item.id}: {item.name}
{item.created_at}
</li>
))}
</ul>
<table>
<thead>
<tr>
<th>Recipe Name</th>
<th>Ingredients</th>
</tr>
</thead>
<tbody>
{recipes.map((recipe) => (
<tr key={recipe.id}>
<td>{recipe.name}</td>
<td>
{recipe.ingredients?.map((ingredient) => (
<span key={ingredient.id}>{ingredient.food_name}</span>
))}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
)
}

export default FetchDataComponent;
export default RecipeTable
5 changes: 5 additions & 0 deletions app/javascript/store/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'

export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
55 changes: 55 additions & 0 deletions app/javascript/store/recipesSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'

export const fetchRecipes = createAsyncThunk(
'recipes/fetchRecipes',
async () => {
const response = await fetch('/api/recipes')
return response.json()
}
)

interface Ingredient {
id: number;
food_name: string;
measurement: string;
}

interface Recipe {
id: number;
name: string;
ingredients: Ingredient[];
}

interface RecipesState {
items: Recipe[]
status: 'idle' | 'loading' | 'succeeded' | 'failed'
error: string | null
}

const initialState: RecipesState = {
items: [],
status: 'idle',
error: null
}

const recipesSlice = createSlice({
name: 'recipes',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchRecipes.pending, (state) => {
state.status = 'loading'
})
.addCase(fetchRecipes.fulfilled, (state, action) => {
state.status = 'succeeded'
state.items = action.payload
})
.addCase(fetchRecipes.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message || null
})
},
})

export default recipesSlice.reducer
11 changes: 11 additions & 0 deletions app/javascript/store/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { configureStore } from '@reduxjs/toolkit'
import recipesReducer from './recipesSlice'

export const store = configureStore({
reducer: {
recipes: recipesReducer,
},
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
2 changes: 2 additions & 0 deletions app/models/recipe.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
class Recipe < ApplicationRecord
has_many :ingredients

accepts_nested_attributes_for :ingredients
end
2 changes: 1 addition & 1 deletion app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<%= javascript_include_tag "application", type: "module" %>
</head>

<body class="bg-neutral-900 text-orange-600">
<body class="bg-neutral-900">
<%= yield %>
</body>
</html>

0 comments on commit 42727c1

Please sign in to comment.