Form, Page Navigation using React-Router-Dom with Ionic-React, TypeScript, React-Hook-Form, React-Redux, and @reduxjs/toolkit!
Tags: react, reactnative, redux, typescript
Published: 2023-08-07 12:56:43

Welcome to this comprehensive Ionic-React-Native tutorial, where we'll explore key aspects such as file structuring, page navigation using React-Router-Dom, state management with React-Redux and @reduxjs/toolkit, seamless API connections with @reduxjs/toolkit/query/react, and elegant styling using Ionic. Let's dive in!

To get started, follow these steps to create your application using Create React App and install the necessary dependencies for managing user state with React-Redux and React Router. Before proceeding, ensure you have Node.js installed, a code editor (preferably Visual Studio Code), and a basic understanding of React.

## Step 1: Create the React Application 
Open your terminal or command prompt and run the following command to create a new React application using Create React App:


```
npm install -g @ionic/cli

```
Now, create the Ionic React Native app with TypeScript template using the following command:

```
ionic start my_ionic_react_native_app blank --type=react --capacitor --language=typescript

```
This will generate a new folder named my-app containing the basic structure of your React application.

This command creates a new Ionic app with a blank template, using React as the framework, TypeScript as the language, and Capacitor for native integration.
 

## Step 2: Navigate to the Application Directory
Change your working directory to the newly created application folder:

```
cd my_ionic_react_native_app

```

## Step 3: Run the App

To run the app in a development server, use the following command:

```
ionic serve

```
This will open the app in your web browser, and you can see the web version of your Ionic React Native app.

Step 4: Set Up Native Platforms (Optional)

If you want to build the app for native platforms (iOS and Android), you'll need to set up the native platforms using Capacitor. First, make sure you have Xcode installed for iOS development and Android Studio for Android development.

To add the native platforms, run the following commands:

```
npx cap add ios
npx cap add android

```

## Step 5: Build the Native App

After adding the native platforms, you can build the app for each platform:

```
npx cap copy
npx cap open ios   // For iOS
npx cap open android   // For Android

```
These commands will open the native projects in Xcode and Android Studio, where you can build, run, and test your app on actual devices or emulators.


## Step 6: Install React-Redux, React Router, @reduxjs/toolkit, and React Hook Form Dependencies

Now that we have our Ionic React Native app set up, let's proceed with installing the necessary dependencies to manage user state, handle API connections, and handle form input with React Hook Form.

In your terminal or command prompt, run the following command to install the required dependencies:



```
npm install react-redux react-router-dom @reduxjs/toolkit @reduxjs/toolkit/query/react react-hook-form

```
With these dependencies installed, our app is equipped with powerful tools to efficiently manage state, handle API connections with @reduxjs/toolkit/query/react, and seamlessly handle form input using React Hook Form.

## Step 7: Organizing File Structure

A well-organized file structure is crucial for a scalable and maintainable project. Let's reorganize our project to keep it clean and organized. Here's a suggested file structure for our Ionic React Native app:


```
my_ionic_react_native_app
  |- src
        |- pages
          |- SignUp.tsx
          |- Login.tsx
          |- Home.tsx
          |- ForgotPassword.tsx
          |- ResetPassword.tsx
      |- store
          |- apiSlice.ts
          |- rootReducer.ts
      |- interfaces.ts
      |- Routes.tsx
      |- App.tsx
      |- index.tsx
  |- public
      |- index.html
      |- ...

```
In this file structure, we've grouped related components into the components folder, pages into the pages folder, and Redux-related code into the store folder. The interfaces.ts file is used to define types and interfaces for better type safety and code consistency.


## Step 8: Setting Up React Router with Ionic

In the Routes.tsx file, we'll set up React Router with Ionic to handle navigation between different pages. Replace the content of Routes.tsx with the following code:

```
import React from 'react';
import { IonRouterOutlet } from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import { Route, Redirect } from 'react-router-dom';
import SignUp from './pages/SignUp';
import Login from './pages/Login';
import Home from './pages/Home';
import ForgotPassword from './pages/ForgotPassword';
import ResetPassword from './pages/ResetPassword';

const Routes: React.FC = () => {
  return (
    <IonReactRouter>
      <IonRouterOutlet>
        <Route path="/" component={Home} exact />
        <Route path="/sign-up" component={SignUp} exact />
        <Route path="/login" component={Login} exact />
        <Route path="/forgot-password" component={ForgotPassword} exact />
        <Route path="/reset-password" component={ResetPassword} exact />
        {/* Add other routes as needed */}
        <Route exact path="/">
          <Redirect to="/" />
        </Route>
      </IonRouterOutlet>
    </IonReactRouter>
  );
};

export default Routes;


```

## Step 9: Setting up the Project and Store 

Create a file named interface.ts and add the all necessary interface to it:

```

//interface.ts

export interface User {
  id: number;
  username: string;
  email: string;
  // Add more user properties as needed
}

export interface UserState {
  user: User | null;
  isLoading: boolean;
  error: string | null;
}

export interface RootState {
  user: UserState;
}
```

Inside the store folder, create a file named userSlice.ts with the following content:

```
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { User, RootState, UserState } from './interface';

const initialState: UserState = {
  user: null,
  isLoading: false,
  error: null,
};

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setUser: (state, action: PayloadAction<User>) => {
      state.user = action.payload;
      state.isLoading = false;
      state.error = null;
    },
    setLoading: (state, action: PayloadAction<boolean>) => {
      state.isLoading = action.payload;
    },
    setError: (state, action: PayloadAction<string>) => {
      state.isLoading = false;
      state.error = action.payload;
    },
  },
});

// Export actions
export const { setUser, setLoading, setError } = userSlice.actions;

// Selector to get the user from the state
export const selectUser = (state: RootState) => state.user.user;

// Selector to get the loading state
export const selectLoading = (state: RootState) => state.user.isLoading;

// Selector to get the error
export const selectError = (state: RootState) => state.user.error;

export default userSlice.reducer;

```


In the src folder, create a new folder named store to house our Redux-related code. In your rootReducer.ts file (in the same store folder), import and add the userSlice reducer to the root reducer:

```
import { combineReducers } from '@reduxjs/toolkit';
import userReducer from './userSlice'; // Import your userSlice reducer here

const rootReducer = combineReducers({
  // Other reducers go here
  user: userReducer,
});

export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;

```

Next, create a file named apiSlice.ts in the store folder. This file will handle API connections using @reduxjs/toolkit/query/react. Here's a basic example of how it can be structured:

```
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

// Define your API endpoints and base query
const api = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: 'https://api.example.com/' }), // Replace with your API base URL
  endpoints: (builder) => ({
    // Define your API endpoints here
    // For example:
    // getUserById: builder.query<User, number>({
    //   query: (id) => `user/${id}`,
    // }),
  }),
});

// Export the hooks for using the API endpoints
// For example:
// export const { useGetUserByIdQuery } = api;

// Export the API object to use in the store
export default api;

```
Inside the store folder (same location where userSlice.ts and rootReducer.ts are located), create a file named store.ts with the following content:

```
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer'; // Import your root reducer here

const store = configureStore({
  reducer: rootReducer,
  // Add middleware or other store configurations as needed
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof rootReducer>;

export default store;

```

Now, let's set up the Redux store in App.tsx. Import the necessary dependencies and the rootReducer from the store folder:

```
import React from 'react';
import { Provider } from 'react-redux';
import Routes from './Routes';
import store from './store/store'; 

const App: React.FC = () => {
  return (
    <Provider store={store}>
      <Routes />
    </Provider>
  );
};

export default App;

```

With these changes, you've now properly set up the project structure and the Redux store using @reduxjs/toolkit/query/react for API connections.


## Step 10: Using React Hook Form for Form Handling with Ionic React

Create a file named interface.ts and add the all necessary interface to it:

```
// interface.ts
export interface SignUpForm {
  username: string;
  email: string;
  password: string;
  confirmPassword: string;
}

export interface LoginForm {
  email: string;
  password: string;
}

export interface ForgotPasswordForm {
  email: string;
}

export interface ResetPasswordForm {
  newPassword: string;
  confirmPassword: string;
}

```

Component Organization: Create a PasswordField component to handle password input with show/hide password functionality:
SignUp.tsx

```
// Create PasswordField.tsx
import React, { useState } from 'react';
import { IonInput, IonIcon } from '@ionic/react';
import { eye, eyeOff } from 'ionicons/icons';

interface PasswordFieldProps {
  label: string;
  field: any;
  fieldState: any;
  register: (name: string) => void;
}

const PasswordField: React.FC<PasswordFieldProps> = ({ label, field, fieldState, register }) => {
  const [visibility, setVisibility] = useState<"text" | "password">("password");

  const toggleVisibility = () => {
    setVisibility((prevVisibility) => (prevVisibility === "password" ? "text" : "password"));
  };

  return (
    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%', gap: '20px' }}>
      <IonInput
        {...field}
        type={visibility}
        onPaste={(e) => {
          e.preventDefault();
          return false;
        }}
        required={true}
        onIonChange={(e) => field.onChange(e.detail.value)}
        onKeyUp={() => {
          register(field.name);
        }}
        style={{
          color: '#345430',
          fontSize: '10px',
          borderRadius: '10px',
          border: '2px solid',
          borderColor: fieldState.invalid ? 'red' : '#345430',
          height: '50px',
        }}
      />
      <IonIcon
        icon={visibility === 'text' ? eye : eyeOff}
        onClick={toggleVisibility}
        style={{ color: '#345430' }}
      />
    </div>
  );
};

export default PasswordField;

```
**Use the PasswordField component in your SignUp component and apply the other suggestions to your code as well:

```
import React from 'react';
import { IonContent, IonButton, IonLabel, IonItem, IonPage, IonInput } from '@ionic/react';
import { useForm, Controller } from 'react-hook-form';
import { useDispatch, useSelector } from 'react-redux';
import { setUser, selectLoading } from './store/userSlice';
import { SignUpForm } from './interface';
import PasswordField from '../components/PasswordField'; // Import the PasswordField component

const SignUp: React.FC = () => {
  const dispatch = useDispatch();
  const isLoading = useSelector(selectLoading);

 const { control, handleSubmit, getValues, setError: setErrorFromForm, watch, formState: { errors, isValid }, register, trigger } = useForm<SignUpForm>({
    defaultValues: {
      email: '',
      username: '',
      password: '',
      confirmPassword: '',
    },
  });

  const onSubmit = (data: SignUpForm) => {
    // Handle sign up form submission
    console.log(data);

    // Example: Dispatch setUser action to update user data in the state
    dispatch(setUser(data));
  };

  const handleConfirmPassword = () => {
    const { password, confirmPassword } = getValues();
    if (password !== confirmPassword) {
      setErrorFromForm('confirmPassword', { type: 'manual', message: 'Passwords do not match' });
    }
  };

  const confirmPassword = watch('confirmPassword');

return (
    <IonPage>
      <IonContent>
        <form onSubmit={handleSubmit(onSubmit)}>
          <IonItem>
            <IonLabel position="floating">Username</IonLabel>
            <Controller
              render={({ field, fieldState }) => (
                <IonInput
                  {...field}
                  {...register("username")}
                  placeholder="Enter Username"
                  aria-label="Username"
                  onIonChange={(e) => field.onChange(e.detail.value)}
                  required={true}
                  onKeyUp={() => {
                    trigger('username');
                  }}
                  
                  style={{
                    color: '#345430',
                    fontSize: '10px',
                    borderRadius: '10px',
                    border: '2px solid',
                    borderColor: fieldState.invalid ? 'red' : '#345430',
                    height: '50px',
                  }}
                />
              )}
              name="username"
              control={control}
              rules={{
                required: 'Username is required',
               pattern: {
                value: /^[A-Za-z0-9_!@#$%^&*()-]+$/,
                message: "Username must contain only letters, numbers, underscores, and symbols !@#$%^&*()-",
              },
            minLength: {
              value: 4,
              message: "Username must be at least 4 characters",
            },
            maxLength: {
              value: 20,
              message: "Username must be less than 20 characters",
            }
              }}
            />
          </IonItem>
          <span style={{ color: 'red' }}>{errors.username?.message}</span>
          <IonItem>
            <IonLabel position="floating">Email</IonLabel>
            <Controller
              render={({ field, fieldState }) => 
            <IonInput 
            {...field}
            {...register("email")}
            onIonChange={(e) => field.onChange(e.detail.value)}
                    placeholder="Enter Email" 
                    aria-label="Email"
                required={true}
                    onKeyUp={() => {
                        trigger("email")
                    }}
                            style={{
                                color: "#345430",
                                fontSize: "10px",
                                borderRadius: "10px",
                                border: "2px solid",
                                borderColor: fieldState.invalid ? "red" : "#345430",
                                height: "50px",
                            }}

            />}
              name="email"
              control={control}
              rules={{ 
                required: 'Email is required',
                pattern: {
                        value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
                        message: "Invalid email address",
             } }}
            />
          </IonItem>
          <span style={{ color: 'red' }}>{errors.email?.message}</span>
          <IonItem>
            <IonLabel position="floating">Password</IonLabel>
            <Controller
              render={({ field, fieldState }) => 
            <div 
              style={{
                display: "flex",
                justifyContent: "space-between",
                alignItems: "center",
                width: "100%",
                gap: "20px",
              }}>
    <PasswordField
            label="Password"
            field={field.password}
            fieldState={fieldState.password}
            register={{...register("password")}}
          />
          <span style={{ color: 'red' }}>{errors.password?.message}</span>
              </div>
                }
              name="password"
              control={control}
                  rules={{
                required: "Password is required",
                pattern: {
                    value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[.!@#$%^&*])[A-Za-z\d.!@#$%^&*]{8,20}$/,
                    message: "Password must have a capital letter, a small letter, a number, and a symbol (e.g., .!@#$%^&*)",
                  },
                minLength: {
                  value: 8,
                  message: "Password must be at least 8 characters",
                },
                maxLength: {
                  value: 20,
                  message: "Password must be less than 20 characters",
                },
              }}            />
          </IonItem>
          <span style={{ color: 'red' }}>{errors.password?.message}</span>
   <IonItem>
            <IonLabel position="floating">Confirm Password</IonLabel>
            <Controller
              render={({ field, fieldState }) =>
  <div 
              style={{
                display: "flex",
                justifyContent: "space-between",
                alignItems: "center",
                width: "100%",
                gap: "20px",
              }}>
            <PasswordField
            label="confirmPassword"
            field={field.confirmPassword}
            fieldState={fieldState.confirmPassword}
            register={{...register("confirmPassword")}}
          />
              </div>}
              name="confirmPassword"
              control={control}
              rules={{ required: 'Confirm Password is required' }}
            />
          </IonItem>
          <span style={{ color: 'red' }}>{errors.confirmPassword?.message}</span>

          <IonButton type="submit" disabled={!isValid || isLoading}>
            {isLoading ? 'Signing Up...' : 'Sign Up'}
          </IonButton>
          {errors && <span style={{ color: 'red' }}>{errors}</span>}
        </form>
      </IonContent>
    </IonPage>
  );
};

export default SignUp;


```
Login.tsx:

```
import { IonContent, IonButton, IonLabel, IonItem, IonPage, IonInput } from '@ionic/react';
import { useForm, Controller } from 'react-hook-form';
import { useDispatch, useSelector } from 'react-redux';
import { setUser, selectLoading } from './store/userSlice';
import { LoginForm } from './interface';
import PasswordField from '../components/PasswordField';

const LoginPage = () => {
    const dispatch = useDispatch();
    const isLoading = useSelector(selectLoading);


const { control, handleSubmit,  formState: { errors, isValid }, register, trigger } = useForm<LoginForm>({
    defaultValues: {
      email: "",
      password: "",
    },
  });


const onSubmit = (data: LoginForm) => {
  // Handle login form submission
  dispatch(setUser(data));
};
  return (
 <IonPage>
    <IonContent>
    <form onSubmit={handleSubmit(onSubmit)}>
        <IonItem>
        <IonLabel position="floating">Email</IonLabel>
        <Controller
             name="email"
             control={control}
             rules={{
               required: 'Email is required',
              pattern: {
             value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
             message: "Invalid email address",
              }
              }}
              render={({ field, fieldState }) => (
                <IonInput
                  {...field}
                  placeholder="Enter Email"
                  aria-label="Email"
                  onIonChange={(e) => field.onChange(e.detail.value)}
                  required={true}
                  onKeyUp={() => {
                    trigger('email');
                  }}
                  {...register("email")}
                  style={{
                    color: '#345430',
                    fontSize: '10px',
                    borderRadius: '10px',
                    border: '2px solid',
                    borderColor: fieldState.invalid ? 'red' : '#345430',
                    height: '50px',
                  }}
                />
              )}
              />
             <span style={{ color: 'red' }}>{errors.email?.message}</span>
        </IonItem>
        <IonItem>
        <IonLabel position="floating">Password</IonLabel>
        <Controller
    render={({ field, fieldState }) => (
      <PasswordField
        label="Password"
        field={field.password}
        fieldState={fieldState.password}
        register={{...register("password")}}
      />
    )}
    name="password"
    control={control}
    rules={{
      required: "Password is required",
      pattern: {
        value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[.!@#$%^&*])[A-Za-z\d.!@#$%^&*]{8,20}$/,
        message: "Password must have a capital letter, a small letter, a number, and a symbol (e.g., .!@#$%^&*)",
      },
      minLength: {
        value: 8,
        message: "Password must be at least 8 characters",
      },
      maxLength: {
        value: 20,
        message: "Password must be less than 20 characters",
      },
    }}
  />
            <span style={{ color: 'red' }}>{errors.password?.message}</span>
        </IonItem>
        <IonButton type="submit" disabled={!isValid || isLoading}>
            {isLoading ? 'Logging In...' : 'Login'}
          </IonButton>
          {errors && <span style={{ color: 'red' }}>{errors}</span>}
        </form>
    </IonContent>
 </IonPage>
  )
}

export default LoginPage

```
ResetPassword.tsx

```
import React from 'react';
import { IonContent, IonButton, IonLabel, IonItem, IonPage,  IonInput } from '@ionic/react';
import { useForm, Controller } from 'react-hook-form';
import { useDispatch, useSelector } from 'react-redux';
import { setUser, selectLoading } from './store/userSlice';
import { ResetPasswordForm } from './interface';
import PasswordField from '../components/PasswordField';

const ResetPassword: React.FC = () => {
  const dispatch = useDispatch();
  const isLoading = useSelector(selectLoading);

  const { control, handleSubmit, getValues, setError: setErrorFromForm, formState: { errors, isValid },  register, trigger } = useForm<ResetPasswordForm>({
    defaultValues: {
      newPassword: "",
      confirmPassword: "",
    },
  });


  const onSubmit = (data: ResetPasswordForm) => {
    // Handle reset password form submission
    dispatch(setUser(data));
  };

  const handleConfirmPassword = () => {
    const { newPassword, confirmPassword } = getValues();
    if (newPassword !== confirmPassword) {
      setErrorFromForm('confirmPassword', { type: 'manual', message: 'Passwords do not match' });
    }
  };

  return (
    <IonPage>
      <IonContent>
        <form onSubmit={handleSubmit(onSubmit)}>
          {/* New Password Field */}
          <IonItem>
            <IonLabel position="floating">New Password</IonLabel>
            <Controller
              render={({ field, fieldState }) => (
                <PasswordField
                  label="New Password"
                  field={field.newPassword}
                  fieldState={fieldState.newPassword}
                  register={{...register("password")}}
                />
              )}
              name="newPassword"
              control={control}
              rules={{
                required: "New Password is required",
                pattern: {
                    value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[.!@#$%^&*])[A-Za-z\d.!@#$%^&*]{8,20}$/,
                    message: "Password must have a capital letter, a small letter, a number, and a symbol (e.g., .!@#$%^&*)",
                  },
                  minLength: {
                    value: 8,
                    message: "Password must be at least 8 characters",
                  },
                  maxLength: {
                    value: 20,
                    message: "Password must be less than 20 characters",
                  },
              }}
            />
          </IonItem>
          <span style={{ color: 'red' }}>{errors.newPassword?.message}</span>

          {/* Confirm Password Field */}
          <IonItem>
            <IonLabel position="floating">Confirm Password</IonLabel>
            <Controller
              render={({ field, fieldState }) => (
                <PasswordField
                  label="Confirm Password"
                  field={field.confirmPassword}
                  fieldState={fieldState.confirmPassword}
                  register={{...register("confirmPassword")}}
                  onKeyUp={handleConfirmPassword}
                />
              )}
              name="confirmPassword"
              control={control}
              rules={{ required: 'Confirm Password is required' }}
            />
          </IonItem>
          <span style={{ color: 'red' }}>{errors.confirmPassword?.message}</span>

          <IonButton type="submit" disabled={!isValid || isLoading}>
            {isLoading ? 'Submitting...' : 'Submit'}
          </IonButton>
          {errors && <span style={{ color: 'red' }}>{errors}</span>}
        </form>
      </IonContent>
    </IonPage>
  );
};

export default ResetPassword;

```

ForgotPassword.tsx 

```
import React from 'react';
import { IonContent, IonButton, IonLabel, IonItem, IonPage, IonInput } from '@ionic/react';
import { useForm, Controller } from 'react-hook-form';
import { useDispatch, useSelector } from 'react-redux';
import { setUser,  selectLoading } from './store/userSlice';
import { ForgotPasswordForm } from './interface';
import PasswordField from '../components/PasswordField';

const ForgotPassword: React.FC = () => {
  const dispatch = useDispatch();
  const isLoading = useSelector(selectLoading);

  const { control, handleSubmit,  formState: { errors, isValid }, register, trigger } = useForm<ForgotPasswordForm>({
    defaultValues: {
      email: "",
    },
  });

  const onSubmit = (data: ForgotPasswordForm) => {
    // Handle forgot password form submission
    dispatch(setUser(data));
  };

  return (
    <IonPage>
      <IonContent>
        <form onSubmit={handleSubmit(onSubmit)}>
          {/* Email Field */}
          <IonItem>
            <IonLabel position="floating">Email</IonLabel>
            <Controller
              name="email"
              control={control}
              rules={{
                required: 'Email is required',
                pattern: {
                  value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
                  message: "Invalid email address",
                },
              }}
              render={({ field, fieldState }) => (
                <IonInput
                  {...field}
                  {...register("password")}
                  placeholder="Enter Email"
                  aria-label="Email"
                  onIonChange={(e) => field.onChange(e.detail.value)}
                  required={true}
                  onKeyUp={() => {
                    trigger('email');
                  }}
                  style={{
                    color: '#345430',
                    fontSize: '10px',
                    borderRadius: '10px',
                    border: '2px solid',
                    borderColor: fieldState.invalid ? 'red' : '#345430',
                    height: '50px',
                  }}
                />
              )}
            />
          </IonItem>
          <span style={{ color: 'red' }}>{errors.email?.message}</span>

          <IonButton type="submit"  disabled={!isValid || isLoading}>
            {isLoading ? 'Submitting...' : 'Submit'}
          </IonButton>
          {errors && <span style={{ color: 'red' }}>{errors}</span>}
        </form>
      </IonContent>
    </IonPage>
  );
};

export default ForgotPassword;

```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/38b7uyykpft8k9s2txri.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m7233v1ruh5s0moy7ymm.png)

## **Conclusion**

In this tutorial, we covered the essentials of creating forms, debugging, and page navigation in an Ionic-React-Native application. We harnessed tools like TypeScript, React Hook Form, React-Redux, and @reduxjs/toolkit to build a powerful app.

We started by mastering form creation with React Hook Form, ensuring accurate user input through validation. Then, we explored debugging techniques, fine-tuning our app for a smooth user experience.

Our journey continued with React-Router-Dom and Ionic, where we learned to seamlessly navigate between app sections, enhancing user engagement.

We leveraged Redux and @reduxjs/toolkit to manage state efficiently, establishing a solid architecture for communication between components.

By completing this tutorial, you've gained a strong foundation for crafting advanced Ionic-React-Native apps. Good luck applying these skills to your future projects!

Feel free to contact me for jobs and project opportunities