Nedávno jsem napsal příspěvek o TypeGraphQL a o tom, jak používat framework pro neuvěřitelné výhody ve vašem GraphQL API s Typescriptem. Pro doplnění vám tentokrát ukážu něco o URQL, klientovi GraphQL. S ním spotřebujeme API receptury – což je v tomto případě stejné API, jaké jsem vytvořil ve svém předchozím příspěvku.
K tomu použijeme React.js k vytvoření našeho CRUD 😊
Nejprve vám dlužím krátké představení tohoto klienta.
URQL je klient GraphQL se zaměřením na použitelnost a přizpůsobivost, s rychlým nastavením a snadným používáním, který je schopen podporovat velmi pokročilé infrastruktury v GraphQL.
urql dokumentace
POJĎME KÓDOVAT!
Nejprve vytvořte nový projekt.
Vytvoříme složku pro projekt. (Jméno je na vás, abyste se rozhodli)
mkdir urql-basics
cd urql-basics
Pojďme inicializovat projekt pomocí šablony React.js s Typescriptem. Můžete použít npx nebo příze . Použiji přízi .
yarn create react-app . --template typescript
Po inicializaci projektu nainstalujme URQL.
yarn add urql graphql
Nyní, když je vše nainstalováno, můžeme odstranit některé soubory, které nebudeme používat.
Budeme potřebovat pouze následující:
/public
index.html
/src
App.tsx
index.tsx
index.css
react-app-env.d.ts
yarn start
Aplikace musí běžet na portu 3000 👍🏼
V příkladech budu používat styled-components pomoci se stylem aplikace. Pokud chcete jinak, žádný problém.
btw CSS v JS =💘
yarn add styled-components @typed/styled-components -D
Pomocí stylizovaných komponent můžeme ve skutečnosti vytvořit komponentu React se všemi jejími styly. Z "Tagged Model Literals" vytvoříme celý styl komponenty. Toto označení je jednoduše CSS/Sass.
Více zde:
styled-components:Basics
Nejprve nakonfigurujeme URQL a vytvoříme našeho poskytovatele.
Ve složce ./api, Vytvořil jsem soubor s názvem urql.ts.
V tomto souboru vyexportujeme klienta
import { createClient } from 'urql';
export const urqlClient = createClient({
url: 'http://localhost:4000/',
});
Aby vše fungovalo, předáme objekt s nějakým nastavením funkci, která vrací Klient .
V našem případě předáme pouze minimum, což by byla url z našeho GraphQL API
Nyní pro začátek vytvoříme poskytovatele pro naši aplikaci, abychom mohli využívat klienta.
Vzhledem k tomu, že tento poskytovatel používá kontextové rozhraní API, zabalíme do něj naši aplikaci.
V naší app.tsx
import { Provider } from 'urql';
import { urqlClient } from './api/urql';
const App: FunctionComponent = () => {
return (
<Provider value={urqlClient}>
<Wrapper>
//...App
</Wrapper>
</Provider>
);
};
Ve své aplikaci jsem nakonec vytvořil Wrapper komponenty, abych vycentroval obsah uprostřed obrazovky
Všechny mé komponenty budou ve složce ./components, A každý z nich ve složce s vlastními styly.
Aby tento příspěvek nebyl příliš dlouhý, projdu si styling a zaměřím se více na URQL. Ale nebojte se, vše zpřístupním v úložišti na Github 😎
Nyní, když máme našeho klienta nakonfigurovaného, vytvoříme náš první dotaz, který bude načítat recepty z mého API.
Uvnitř ./src Vytvořím složku ./graphql. Do něj můžeme vložit naše Mutace a dotazy
.src/graphql/queries/recipesQuery.ts
export const recipesQuery = `
query {
recipes {
id
name
description
ingredients
}
}
`;
Jednoduše můj dotaz je String , se syntaxí GraphQL.
Chcete-li spustit náš dotaz, vytvořte komponentu, která bude vypisovat všechny naše recepty.
./components/RecipeList.component.tsx
import React, { FunctionComponent } from 'react';
import RecipeCard from '../recipeCard/RecipeCard.component';
import RecipesWrapper from './styles';
import { useQuery } from 'urql';
import { recipesQuery } from '../../graphql/queries/recipesQuery';
interface RecipesListProps {}
const RecipesList: FunctionComponent<RecipesListProps> = () => {
const [recipesResult, reexecuteQuery] = useQuery({
query: recipesQuery,
});
const { data, fetching, error } = recipesResult;
if (fetching) return <p>Carregando...</p>;
if (error) return <p>Algo deu errado... {error.message}</p>;
return (
<RecipesWrapper>
{data.recipes.map((recipe: any) => (
<RecipeCard
id={recipe.id}
key={recipe.id}
name={recipe.name}
description={recipe.description}
ingredients={[...recipe.ingredients]}
/>
))}
</RecipesWrapper>
);
};
export default RecipesList;
Pomocí háčku useQuery poskytnutý samotným URQL, odešleme náš dotaz, který přinese n-tici obsahující objekt s výsledkem dotazu a funkci opětovného spuštění.
Tento objekt bude obsahovat:
- data ⇒ Data získaná z rozhraní API
- načítání ⇒ Indikace načítání dat.
- chyba ⇒ Chyby připojení nebo dokonce GraphQLErrors
Takže pomocí dat zobrazíme na obrazovce všechny existující receptury.
K tomu jsem vytvořil komponentu RecipeCard, která je naplněna informacemi o receptech.
./components/RecipeCard.component.tsx
import React, { FunctionComponent, useContext } from 'react';
interface RecipeCardProps {
id?: string;
name: string;
description: string;
ingredients: Array<string>;
}
const RecipeCard: FunctionComponent<RecipeCardProps> = ({
id,
name,
description,
ingredients,
}) => {
return (
<Card>
<TextWrapper>
<TextLabel>Receita</TextLabel>
<Title>{name}</Title>
</TextWrapper>
<TextWrapper>
<TextLabel>Descrição</TextLabel>
<Description>{description}</Description>
</TextWrapper>
<TextWrapper>
<TextLabel>Ingredientes</TextLabel>
{ingredients.map((ingredient, index) => (
<Ingredient key={index}>{ingredient}</Ingredient>
))}
</TextWrapper>
<TextWrapper>
<TextLabel>Opções</TextLabel>
<ActionsWrapper>
<UpdateButton>Atualizar</UpdateButton>
<DeleteButton>Deletar</DeleteButton>
</ActionsWrapper>
</TextWrapper>
</Card>
);
};
export default RecipeCard;
Neuvěřitelný! 🚀
Nyní přidáme mutaci vytvořit nový recept.
Pojďme vytvořit createRecipeMutation.ts
./graphql/mutations/createRecipeMutation.ts
export const createRecipeMutation = `
mutation(
$name: String!,
$description: String!,
$ingredients: [String!]!
) {
createRecipe(data: {
name: $name,
description: $description,
ingredients: $ingredients
}) {
recipe {
id
}
error {
message
}
}
}
`;
V případě API receptur musíme poslat název, popis a seznam ingrediencí, přičemž každou uvedeme na začátku naší mutace.
S naší createRecipeMutation připravenou, pojďme vytvořit formulář pro registraci receptu. K tomu použiji Formik, což je knihovna pro správu formulářů.
Pokud nevíte, doporučuji se podívat:
Formik
Aby byla aplikace čistší a jednodušší, použiji jeden formulář, a to jak pro aktualizaci, tak pro vytvoření.
Pro otevření formuláře Vytvořit jsem vytvořil tlačítko a přidal ho do app.tsx
<Provider value={urqlClient}>
<Wrapper>
<Title>myRecipes</Title>
<RecipesList />
<Recipeform />
<CreateRecipeButton />
</Wrapper>
</Provider>
Pro sdílení toho, který formulář je otevřený a který je zavřený, jsem použil Context API ke sdílení dvou atributů, které označují, který z formulářů se otevře. Buď Vytvořit, nebo Aktualizovat.
Uvnitř ./context jsem vytvořil kontext aplikace.
./context/context.ts
import { createContext } from 'react';
interface AppContextType {
isCreateRecipeFormOpen: boolean;
isUpdateRecipeFormOpen: boolean;
}
export const initialAppContext: AppContextType = {
isCreateRecipeFormOpen: false,
isUpdateRecipeFormOpen: false,
};
export const AppContext = createContext<
[AppContextType, React.Dispatch<React.SetStateAction<AppContextType>>]
>([initialAppContext, () => {}]);
Abych zkontroloval stav formulářů, vytvořil jsem komponentu, která vykreslí pouze formulář, který byl požadován.
./components/RecipeForm.component.tsx
import React, { FunctionComponent, useContext } from 'react';
import { AppContext } from '../../context/context';
import Form from '../form/Form.component';
const Recipeform: FunctionComponent = () => {
const [appContext] = useContext(AppContext);
if (appContext.isCreateRecipeFormOpen) {
return <Form btnName="Criar" formType="create" title="Criar receita" />;
}
if (appContext.isUpdateRecipeFormOpen) {
return (
<Form btnName="Atualizar" formType="update" title="Atualizar receita" />
);
}
return null;
};
export default Recipeform;
A náš formulář vypadá takto:
./components/Form.component.tsx
import React, { FunctionComponent, useContext } from 'react';
import { FormikValues, useFormik } from 'formik';
import { FormField, Title, InputsWrapper, Input, FinishButton } from './styles';
interface FormProps {
title: string;
btnName: string;
formType: 'update' | 'create';
}
const Form: FunctionComponent<FormProps> = ({ formType, title, btnName }) => {
const formik = useFormik({
initialValues: {
name: '',
description: '',
ingredients: '',
},
onSubmit: (formikValues) => handleForm(formikValues),
});
const update = async (formikValues: FormikValues) => {
// TODO Update Recipe Mutation
};
const create = async (formikValues: FormikValues) => {
// TODO Create Recipe Mutation
};
const handleForm = (formikValues: any) => {
// TODO handle update or create
};
const handleIngredientsField = (ingredients: string) => {
let ingredientsArray = ingredients.split(',');
return ingredientsArray;
};
return (
<FormField onSubmit={formik.handleSubmit}>
<Title>{title}</Title>
<InputsWrapper>
<Input
name="name"
id="name"
type="text"
placeholder="Nome da sua receita"
onChange={formik.handleChange}
value={formik.values.name}
/>
<Input
name="description"
id="description"
type="text"
placeholder="Descrição da sua receita"
onChange={formik.handleChange}
value={formik.values.description}
/>
<Input
name="ingredients"
id="ingredients"
type="text"
placeholder="Ingredientes (separados por virgula)"
onChange={formik.handleChange}
value={formik.values.ingredients}
/>
<FinishButton type="submit">{btnName}</FinishButton>
</InputsWrapper>
</FormField>
);
};
export default Form;
Nyní přidáme naši createRecipeMutation:
./components/Form.tsx
import { useMutation } from 'urql';
import { createRecipeMutation } from '../../graphql/mutations/createRecipeMutation';
interface FormProps {
title: string;
btnName: string;
formType: 'update' | 'create';
}
const Form: FunctionComponent<FormProps> = ({ formType, title, btnName }) => {
const [createRecipeResult, createRecipe] = useMutation(createRecipeMutation);
const [appContext, setAppContext] = useContext(AppContext);
const formik = useFormik({
initialValues: {
name: '',
description: '',
ingredients: '',
},
onSubmit: (formikValues) => handleForm(formikValues),
});
const update = async (formikValues: FormikValues) => {
// TODO Update Recipe Mutation
};
const create = async (formikValues: FormikValues) => {
// Create Recipe Mutation
await createRecipe({
...formikValues,
ingredients: handleIngredientsField(formikValues.ingredients),
});
};
const handleForm = (formikValues: any) => {
setAppContext({
...appContext,
isUpdateRecipeFormOpen: false,
isCreateRecipeFormOpen: false,
});
create(formikValues);
};
const handleIngredientsField = (ingredients: string) => {
let ingredientsArray = ingredients.split(',');
return ingredientsArray;
};
return (
//...
)
};
export default Form;
Pomocí háčku useMutation , budeme mít objekt s výsledkem a funkcí pro provedení mutace .
Pojďme to otestovat!
Ukázat! 🔥
Nyní pro naši aktualizaci Mutation uděláme něco velmi podobného.
Tentokrát však budeme muset odeslat ID receptu, který chceme aktualizovat.
./updateRecipeMutation.ts
export const updateRecipeMutation = `
mutation(
$id: String!,
$name: String!,
$description: String!,
$ingredients: [String!]!
) {
updateRecipe(
id: $id,
data: {
name: $name,
description: $description,
ingredients: $ingredients
}) {
recipe {
id
}
error {
message
}
success
}
}
`;
Takže na naší RecipeCard použijeme tlačítko aktualizace ke spuštění procesu aktualizace.
V aplikaci jsem také použil kontextové API ke sdílení ID receptu, který bude aktualizován. A v tomto případě, jak víme, otevřeme formulář Aktualizace.
AppContext.ts
import { createContext } from 'react';
import Recipe from '../interfaces/Recipe';
interface AppContextType {
recipes: Array<Recipe>;
isCreateRecipeFormOpen: boolean;
isUpdateRecipeFormOpen: boolean;
recipeIdToUpdate: string;
}
export const initialAppContext: AppContextType = {
recipes: [],
isCreateRecipeFormOpen: false,
isUpdateRecipeFormOpen: false,
recipeIdToUpdate: '',
};
export const AppContext = createContext<
[AppContextType, React.Dispatch<React.SetStateAction<AppContextType>>]
>([initialAppContext, () => {}]);
./RecipeCard.component.tsx
const openUpdateForm = () => {
setAppContext({
...appContext,
isCreateRecipeFormOpen: false,
isUpdateRecipeFormOpen: true,
recipeIdToUpdate: id ? id : '',
});
};
<ActionsWrapper>
<UpdateButton onClick={openUpdateForm}>Atualizar</UpdateButton>
<DeleteButton>Deletar</DeleteButton>
</ActionsWrapper
A naše v našem formuláři:
./components/Form.component.tsx
import { useMutation } from 'urql';
import { updateRecipeMutation } from '../../graphql/mutations/updateRecipeMutation';
interface FormProps {
title: string;
btnName: string;
formType: 'update' | 'create';
}
const Form: FunctionComponent<FormProps> = ({ formType, title, btnName }) => {
const [createRecipeResult, createRecipe] = useMutation(createRecipeMutation);
const [updateRecipeResult, updateRecipe] = useMutation(updateRecipeMutation);
const [appContext, setAppContext] = useContext(AppContext);
const formik = useFormik({
initialValues: {
name: '',
description: '',
ingredients: '',
},
onSubmit: (formikValues) => handleForm(formikValues),
});
const update = async (formikValues: FormikValues) => {
// Update Recipe Mutation
await updateRecipe({
id: appContext.recipeIdToUpdate,
...formikValues,
ingredients: handleIngredientsField(formikValues.ingredients),
});
};
const create = async (formikValues: FormikValues) => {
// Create Recipe Mutation
await createRecipe({
...formikValues,
ingredients: handleIngredientsField(formikValues.ingredients),
});
};
const handleForm = (formikValues: any) => {
setAppContext({
...appContext,
isUpdateRecipeFormOpen: false,
isCreateRecipeFormOpen: false,
});
formType === 'update' ? update(formikValues) : create(formikValues);
};
const handleIngredientsField = (ingredients: string) => {
let ingredientsArray = ingredients.split(',');
return ingredientsArray;
};
return (
//...
);
};
export default Form;
Rozzlobený! Nyní zbývá implementovat Delete .
Pojďme tedy vytvořit naši deleteRecipeMutation
export const deleteRecipeMutation = `
mutation(
$id: String!
) {
deleteRecipe(id: $id) {
recipe {
id
}
error {
message
}
success
}
}
`;
A abychom mohli odeslat tuto mutaci, přidejte funkci do našeho tlačítka Smazat.
./components/RecipeCard.component.tsx
import { useMutation } from 'urql';
import { deleteRecipeMutation } from '../../graphql/mutations/deleteRecipeMutation';
interface RecipeCardProps {
id?: string;
name: string;
description: string;
ingredients: Array<string>;
}
const RecipeCard: FunctionComponent<RecipeCardProps> = ({
id,
name,
description,
ingredients,
}) => {
const [appContext, setAppContext] = useContext(AppContext);
const [deleteRecipeResult, deleteRecipe] = useMutation(deleteRecipeMutation);
const handleDeleteRecipe = async () => {
//Delete Recipe Mutation
await deleteRecipe({ id });
};
return (
<Card>
//...
<ActionsWrapper>
<UpdateButton onClick={openUpdateForm}>Atualizar</UpdateButton>
<DeleteButton onClick={handleDeleteRecipe}>Deletar</DeleteButton>
</ActionsWrapper>
</TextWrapper>
</Card>
);
};
export default RecipeCard;
Nyní ano, máme náš CRUD s URQL 🎉 🎉
Doufám, že tento malý úvod byl užitečný 😊
Stálo to! ♥️
Odkaz na projekt na Github:
vinisaveg/urql-basics
Odkaz na můj příspěvek o TypeGraphQL
TypeGraphQL, základy rozhraní API receptů
Šťastné kódování!