DEV Community

Cover image for Form, Page Navigation using React-Router-Dom with Ionic-React, TypeScript, React-Hook-Form, React-Redux, and @reduxjs/toolkit!
Olorì Àṣàbí
Olorì Àṣàbí

Posted on

Form, Page Navigation using React-Router-Dom with Ionic-React, TypeScript, React-Hook-Form, React-Redux, and @reduxjs/toolkit!

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Step 3: Run the App

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

ionic serve

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
      |- ...

Enter fullscreen mode Exit fullscreen mode

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;


Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

**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;


Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

Image description

Image description

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

Top comments (2)

Collapse
 
leob profile image
leob

This is an absolutely brilliant article (tutorial) - how come this had zero likes? I gave this all of the 5 available likes, lol ...

Sad state of affairs when the umptieth shallow "listicle" gets a 100 likes and this finely crafted masterpiece had zero ...

Seriously, THIS is the kind of content I want to see on dev.to - in-depth tutorials which show how to integrate a number of frameworks/technologies (integrating stuff is always where things get hairy ...)

P.S. still a few points of minor criticism:

  • a github repo woulda been nice ...

  • you explain how to use an API slice with "react query" and all that, but you don't show how to use it

  • the login and signup examples just do a "setUser" on submit, which isn't remotely realistic

  • how do I do an "async" dispatch

But anyway, for someone who wants to get started with the combo of Ionic, React, Redux and react-hook-form, this is pure gold :)

Collapse
 
oloriasabi profile image
Olorì Àṣàbí

I appreciate your positive feedback on the article and the constructive criticism you provided. The GitHub repo is private due to sensitivity reasons. Thank you for your suggestions, and I'll consider improving the tutorial based on your points. Feel free to reach out for potential job or collaboration opportunities.