Skip to content

Commit

Permalink
feat: refacto with tanstack and mercure
Browse files Browse the repository at this point in the history
  • Loading branch information
29Hido committed Mar 28, 2024
1 parent 6f5967b commit 666215e
Show file tree
Hide file tree
Showing 25 changed files with 510 additions and 353 deletions.
55 changes: 22 additions & 33 deletions src/generators/ReactNativeGeneratorV2.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,25 @@ export default class extends BaseGenerator {
this.registerTemplates(`react-native-v2/`, [
"app/(tabs)/foos.tsx",
"app/_layout.tsx.dist",
"lib/hooks.ts",
"lib/store.ts",
"lib/hooks/data.ts",
"lib/hooks/mercure.ts",
"lib/hooks/modal.ts",
"lib/hooks/notifications.ts",
"lib/types/ApiResource.ts",
"lib/types/HydraView.ts",
"lib/types/Logs.ts",
"lib/types/HydraResponse.ts",
"lib/types/foo.ts",
"lib/factory/logFactory.ts",
"lib/slices/fooSlice.ts",
"lib/utils/Logs.ts",
"lib/utils/mercure.ts",
"lib/utils/icons.tsx",
"lib/api/fooApi.ts",
"components/Main.tsx",
"components/Navigation.tsx",
"components/StoreProvider.tsx",
"components/ConfirmModal.tsx",
"components/foo/CreateEditModal.tsx",
"components/foo/Form.tsx",
"components/foo/LogsRenderer.tsx",
"components/foo/Context.ts",
]);

handlebars.registerHelper("compare", hbhComparison.compare);
Expand All @@ -45,25 +49,6 @@ export default class extends BaseGenerator {
resource.title
);

console.log("You must now configure the lib/store.ts");
console.log(
chalk.green(`
// imports for ${titleLc}
import ${titleLc}Slice from './slices/${titleLc}Slice';
import { ${titleLc}Api } from './api/${titleLc}Api';
// reducer for ${titleLc}
reducer: {
...
${titleLc}: ${titleLc}Slice,
[${titleLc}Api.reducerPath]: ${titleLc}Api.reducer,
}
// middleware for ${titleLc}
getDefaultMiddleware().concat(..., ${titleLc}Api.middleware)
`)
);

console.log(
"You should replace app/_layout.tsx by the generated one and add the following route:"
);
Expand Down Expand Up @@ -112,22 +97,26 @@ export default class extends BaseGenerator {
`${dir}/components/${lc}`,
`${dir}/lib`,
`${dir}/lib/api`,
`${dir}/lib/factory`,
`${dir}/lib/slices`,
`${dir}/lib/types`,
`${dir}/lib/hooks`,
`${dir}/lib/utils`,
].forEach((dir) => this.createDir(dir, false));

// static files
[
"lib/hooks.ts",
"lib/store.ts",
"lib/types/ApiResource.ts",
"lib/types/HydraView.ts",
"lib/types/Logs.ts",
"lib/factory/logFactory.ts",
"lib/types/HydraResponse.ts",
"lib/hooks/data.ts",
"lib/hooks/mercure.ts",
"lib/hooks/modal.ts",
"lib/hooks/notifications.ts",
"lib/utils/Logs.ts",
"lib/utils/mercure.ts",
"lib/utils/icons.tsx",
"components/Main.tsx",
"components/Navigation.tsx",
"components/StoreProvider.tsx",
"components/ConfirmModal.tsx",
].forEach((file) => this.createFile(file, `${dir}/${file}`));

// templated files ucFirst
Expand All @@ -139,8 +128,8 @@ export default class extends BaseGenerator {
[
"app/(tabs)/%ss.tsx",
"app/_layout.tsx.dist",
"lib/slices/%sSlice.ts",
"lib/api/%sApi.ts",
"components/%s/Context.ts",
"components/%s/CreateEditModal.tsx",
"components/%s/Form.tsx",
"components/%s/LogsRenderer.tsx",
Expand Down
135 changes: 90 additions & 45 deletions templates/react-native-v2/app/(tabs)/foos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,54 @@ import Main from "@/components/Main";
import Navigation from "@/components/Navigation";
import CreateEditModal from "@/components/{{{lc}}}/CreateEditModal";
import LogsRenderer from "@/components/{{{lc}}}/LogsRenderer";
import { useLazyGetAllQuery } from "@/lib/api/{{{lc}}}Api";
import { useAppDispatch, useAppSelector } from "@/lib/hooks";
import { setCurrentData, setData, setModalIsEdit, setModalIsVisible, setPage, setView } from "@/lib/slices/{{{lc}}}Slice";
import {{{ucf}}} from "@/lib/types/{{{ucf}}}";
import { useLocalSearchParams } from "expo-router";
import { useLocalSearchParams, Link } from "expo-router";
import { useEffect } from "react";
import { useQuery } from '@tanstack/react-query'
import { Pressable, ScrollView, Text, View } from "react-native";
import { getAll } from "@/lib/api/{{{lc}}}Api";
import { HydraResponse } from "@/lib/types/HydraResponse";
import { {{{ucf}}}Context, {{{ucf}}}ContextData } from "@/components/{{{lc}}}/Context";
import { useMercure } from "@/lib/hooks/mercure";
import { useData } from "@/lib/hooks/data";
import { useNotifications } from "@/lib/hooks/notifications";
import { useModal } from "@/lib/hooks/modal";
import { Icon } from "@/lib/utils/icons";

export default function {{{ucf}}}s() {
const datas = useAppSelector(state => state.{{lc}}.data);
const view = useAppSelector(state => state.{{{lc}}}.view);
export default function Books() {
const { page = '1' } = useLocalSearchParams<{ page: string }>();
const { id = undefined } = useLocalSearchParams<{ id: Nullable<string> }>();

const dispatch = useAppDispatch();
const [getAll] = useLazyGetAllQuery();
const { member, setMember, processMercureData, view, setView, currentData, setCurrentData } = useData<{{{ucf}}}>();
const { notifications, addNotification, clearNotifications } = useNotifications();
const { isModalEdit, isModalVisible, toggleEditModal, toggleCreateModal, setIsModalVisible } = useModal();

const toggleEditModal = (data: {{{ucf}}}) => {
dispatch(setCurrentData(data));
dispatch(setModalIsVisible(true));
dispatch(setModalIsEdit(true));
};
useMercure(['/{{{lc}}}s'], processMercureData);

const toggleCreateModal = () => {
dispatch(setModalIsVisible(true));
dispatch(setModalIsEdit(false));
}
const { isSuccess, data, isLoading, error } = useQuery<HydraResponse<{{{ucf}}}>>({
queryKey: ['getAll{{{ucf}}}s', page],
queryFn: () => getAll(page),
});

useEffect(() => {
const intPage = parseInt(page);
if (intPage < 0) return;
dispatch(setPage(intPage));
getAll(intPage)
.unwrap()
.then(fulfilled => {
dispatch(setView(fulfilled["hydra:view"]));
dispatch(setData(fulfilled["hydra:member"]));
})
}, [page]);
if (isSuccess) {
setMember(data["hydra:member"]);
setView(data['hydra:view']);
}
}, [isSuccess, data]);

useEffect(() => {
if (!id) return;

const data = member.find(item => item["@id"].includes(id) == true);
if (data) {
setCurrentData(data);
toggleEditModal();
}
}, [member, id])

const providerValues: {{{ucf}}}ContextData = { notifications, addNotification, clearNotifications, isModalVisible, isModalEdit, setIsModalVisible, currentData };
const viewButtonStyle = { width: "5vw", minWidth: '100px', height: "100%" };

return (
<Main>
Expand All @@ -49,24 +59,59 @@ export default function {{{ucf}}}s() {
<Text className="bg-cyan-500 cursor-pointer text-white text-sm font-bold py-2 px-4 rounded">Create</Text>
</Pressable>
</View>
<ScrollView>
<LogsRenderer />
<View>
{
datas.map(data => (
<Pressable onPress={() => toggleEditModal(data)} key={data["@id"]}>
<View className="flex flex-column my-2 block max-w p-6 bg-white border border-gray-300 rounded shadow">
<Text>ID: {data['@id']}</Text>
{{#each fields}}
<Text>{{{name}}}: {data["{{{name}}}"]}</Text>
{{/each}}
<{{{ucf}}}Context.Provider value={providerValues}>
<ScrollView>
<LogsRenderer />
<View>
{
member && member.length < 1 &&
<View className="flex flex-row justify-between p-4 mb-4 text-sm rounded-lg bg-cyan-300" role="alert">
<Text className="text-1xl">{isLoading ? 'Loading data...' : 'No data found'}</Text>
</View>
}
{
error &&
<View className="flex flex-row justify-between p-4 mb-4 text-sm rounded-lg bg-red-300" role="alert">
<Text className="text-1xl">{error.message}</Text>
</View>
}
{
member && member.map((item: {{{ucf}}}) => (
!item.deleted &&
<View key={item['@id']} className="flex relative my-2 block max-w p-6 bg-white border border-gray-300 rounded shadow">
<View>
<Text>ID: {item['@id']}</Text>
{{#each fields}}
{{#if isReferences}}
<Text>{{{name}}}:</Text>
{item['{{{name}}}'].map((ref: any) => <Link className="text-blue-500" href={`/{{{reference.name}}}?id=${ref}`} key={ref}>{ref}</Link>)}
{{else if reference}}
<Text>{{{name}}}: <Link className="text-blue-500" href={`/{{{reference.name}}}?id=${item["{{{name}}}"]}`} key={item["{{{name}}}"]}>{item["{{{name}}}"]}</Link></Text>
{{else if isEmbeddeds}}
<Text>{{{name}}}:</Text>
{item['{{{name}}}'].map((emb: any) => <Link className="text-blue-500" href={`/{{{embedded.name}}}?id=${emb["@id"]}`} key={emb["@id"]}>{emb["@id"]}</Link>)}
{{else if embedded}}
<Text>{{{name}}}: <Link className="text-blue-500" href={`/{{{embedded.name}}}?id=${item["{{{name}}}"]["@id"]}`} key={item["{{{name}}}"]["@id"]}>{item["{{{name}}}"]["@id"]}</Link></Text>
{{else}}
<Text>{{{name}}}: {item["{{{name}}}"]}</Text>
{{/if}}
{{/each}}
</View>
<View className="flex justify-center items-center absolute bottom-0 right-0 bg-cyan-500" style={viewButtonStyle}>
<Pressable onPress={() => {
setCurrentData(item);
toggleEditModal();
}}>
<Icon name="eye" />
</Pressable>
</View>
</View>
</Pressable>
))
}
</View>
<CreateEditModal />
</ScrollView>
))
}
</View>
<CreateEditModal />
</ScrollView>
</{{{ucf}}}Context.Provider>
<Navigation view={view} />
</Main >
);
Expand Down
13 changes: 9 additions & 4 deletions templates/react-native-v2/app/_layout.tsx.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import React from 'react';
import { FontAwesome } from "@expo/vector-icons";
import "../global.css";
import { Tabs } from "expo-router";
import StoreProvider from '@/components/StoreProvider';
import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'

function TabBarIcon(props: {
name: React.ComponentProps<typeof FontAwesome>['name'];
Expand All @@ -13,15 +16,17 @@ function TabBarIcon(props: {
const iconMargin = { marginBottom: -3 }

export default function Layout() {
const queryClient = new QueryClient();

return (
<StoreProvider>
<QueryClientProvider client={queryClient}>
<Tabs screenOptions={options.tabsContainer}>
<Tabs.Screen
name="index"
options={options.tabs.home}
/>
</Tabs>
</StoreProvider>
</QueryClientProvider>
)
}

Expand All @@ -36,4 +41,4 @@ const options = {
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
},
}
};
};
29 changes: 29 additions & 0 deletions templates/react-native-v2/components/ConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Modal, Pressable, Text, View } from "react-native";

export default function ConfirmModal(props: { isVisible: boolean, onDecline: Function, onAccept: Function }) {
const { isVisible, onDecline, onAccept } = props;

return (
<Modal
visible={isVisible}
transparent
animationType="fade"
>
<View className="flex flex-column items-center justify-center" style={containerStyle} >
<View className="flex flex-column items-center border rounded py-4 px-12 bg-cyan-800">
<Text className="text-white text-1xl font-bold">Are you sure ?</Text>
<View className="flex flex-row mt-5 gap-3 justify-center">
<Pressable onPress={() => onAccept()}>
<Text className="bg-green-500 cursor-pointer text-white text-sm font-bold py-2 px-4 rounded">Yes</Text>
</Pressable>
<Pressable onPress={() => onDecline()}>
<Text className="bg-red-500 cursor-pointer text-white text-sm font-bold py-2 px-4 rounded">Cancel</Text>
</Pressable>
</View>
</View>
</View >
</Modal>
)
}

const containerStyle = { height: '100%', width: '100%' }
17 changes: 0 additions & 17 deletions templates/react-native-v2/components/StoreProvider.tsx

This file was deleted.

15 changes: 15 additions & 0 deletions templates/react-native-v2/components/foo/Context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {{{ucf}}} from "@/lib/types/{{{ucf}}}";
import { Log, addNotificationFunction, clearNotificationsFunction } from "@/lib/utils/Logs";
import { createContext } from "react";

export type {{{ucf}}}ContextData = {
notifications: Log[];
addNotification: addNotificationFunction,
clearNotifications: clearNotificationsFunction,
isModalVisible: boolean;
isModalEdit: boolean;
setIsModalVisible: (visible: boolean) => void;
currentData?: {{{ucf}}};
}

export const {{{ucf}}}Context = createContext<{{{ucf}}}ContextData>(null);
Loading

0 comments on commit 666215e

Please sign in to comment.