<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Theresa Okoro</title>
    <description>The latest articles on DEV Community by Theresa Okoro (@theresa_okoro).</description>
    <link>https://dev.to/theresa_okoro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1999045%2F835c8f40-4e00-42fb-a301-7a4c7f59869f.png</url>
      <title>DEV Community: Theresa Okoro</title>
      <link>https://dev.to/theresa_okoro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/theresa_okoro"/>
    <language>en</language>
    <item>
      <title>Implementing Search Functionality in Django Rest Framework (DRF)</title>
      <dc:creator>Theresa Okoro</dc:creator>
      <pubDate>Sun, 15 Sep 2024 15:37:19 +0000</pubDate>
      <link>https://dev.to/theresa_okoro/implementing-search-functionality-in-django-rest-framework-drf-2fp0</link>
      <guid>https://dev.to/theresa_okoro/implementing-search-functionality-in-django-rest-framework-drf-2fp0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When building APIs with Django Rest Framework, one of the essential features you might want to include is a search functionality. This allows clients to query the API and filter results based on specific criteria, making it much more user-friendly and flexible.&lt;/p&gt;

&lt;p&gt;Django provides us with a powerful tool for our APIs called django-filters, this gives us access to;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The DjangoFilterBackend class which supports highly customizable field filtering for REST framework.&lt;/li&gt;
&lt;li&gt;The SearchFilter class supports simple single query parameter based searching which are case-insensitive partial matches, and is based on the Django admin's search functionality. &lt;/li&gt;
&lt;li&gt;The OrderingFilter class supports simple query parameter controlled ordering of results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are going to focus on the SearchFilter for this tutorial as we are trying to implement search functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Setting Up a Sample API
&lt;/h2&gt;

&lt;p&gt;To demonstrate search functionality, we'll first set up a simple model, serializer, and view.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a Model
&lt;/h3&gt;

&lt;p&gt;Let's say we have a model for Category, JobSkills and PostAJob. Category is a ForeignKey to PostAJob and JobSkills is a ManyToManyField relationship to PostAJob.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Category(models.Model):
   name = models.CharField(max_length=50, unique=True, null=True, blank=True)
   def __str__(self):
       return self.name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class JobSkills(models.Model):
   title = models.CharField(max_length=20, unique=True)
   category = models.ManyToManyField(Category)


   def __str__(self):
       return self.title
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PostAJob(models.Model):
   job_title = models.CharField(max_length=200)
   job_category = models.ForeignKey(Category, on_delete=models.CASCADE)
   job_skills = models.ManyToManyField(JobSkills, blank=True)
   job_salary_range = models.IntegerField(blank=True)
   job_description = models.TextField()


   def __str__(self):
       return self.job_title
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Create a Serializer
&lt;/h3&gt;

&lt;p&gt;Create a serializer for the models.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class CategorySerializer(serializers.ModelSerializer):
   class Meta:
       model = Category
       fields = ['id', 'name']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class JobSkillsSerializer(serializers.ModelSerializer):
   category = CategorySerializer(many=True, read_only=True)


   class Meta:
       model = JobSkills
       fields = ['id', 'title', 'category']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class JobSerializer(serializers.ModelSerializer):
   job_category = CategorySerializer(read_only=True)
   job_skills = JobSkillsSerializer(many=True, read_only=True)

   class Meta:
       model = PostAJob
       fields = (
           'id',
           'job_title',
           'job_category',
           'job_skills',
           'job_salary_range',
           'job_description',
       )


   def create(self, validated_data):
       request = self.context['request']


       job_category_pk = request.data.get('job_category')
       validated_data['job_category_id'] = job_category_pk


       job_skills_data = request.data.get('job_skills')
       validated_data['job_skills'] = job_skills_data


       instance = super().create(validated_data)


       return instance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Setup Django-Filter
&lt;/h3&gt;

&lt;p&gt;To use SearchFilter, first install django-filter.&lt;br&gt;
&lt;code&gt;pip install django-filter&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then add 'django_filters' to Django's INSTALLED_APPS in your settings.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSTALLED_APPS = [
    ...
    'django_filters',
    ...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Create a View and URL
&lt;/h3&gt;

&lt;p&gt;You should now add the search filter to an individual View or ViewSet.&lt;/p&gt;

&lt;p&gt;The SearchFilter class will only be applied if the view has a search_fields attribute set. The search_fields attribute should be a list of names of text type fields on the model, such as CharField or TextField.&lt;/p&gt;

&lt;p&gt;Here is what’s happening, we are adding a SearchFilter to the filter_backends and a search_fields which tells the filter class what fields it should query i.e if the user searches for a text and it is in the job_title or category or skills db section, if that string is available, the system should return what the user searched for.&lt;/p&gt;

&lt;p&gt;If you have a ForeignKey or a ManyToMany relational fields like the below category and skills field example, you need to specify what the related lookup is by using  the lookup API double-underscore notation &lt;code&gt;__&lt;/code&gt;, this is what you can see in the model of the Category and JobSKills seen above. That is why in searchFields we have an underscore for job_category as the lookup we are using is the name - 'job_category_&lt;em&gt;name’ and for job_skills, we are looking for title - 'job_skills&lt;/em&gt;_title',.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from rest_framework import filters
from rest_framework.pagination import PageNumberPagination

class PostAJobListView(GenericAPIView):
   serializer_class = JobSerializer
   queryset = (
       PostAJob.objects.select_related('job_category')
       .prefetch_related('job_skills')
       .all()
   )
   filter_backends = [filters.SearchFilter]
   search_fields = [
       'job_title',
       'job_category__name',
       'job_skills__title',
   ]

   def get(self, request, *args, **kwargs):
       # Get the filtered queryset
       queryset = self.filter_queryset(self.get_queryset())


       # Paginate the queryset
       page = self.paginate_queryset(queryset)
       if page is not None:
           # If pagination is applied, get the paginated response
           serializer = self.get_serializer(page, many=True)
           return self.get_paginated_response(serializer.data)


       # If no pagination, return the full response
       serializer = self.get_serializer(queryset, many=True)
       return Response(serializer.data, status=status.HTTP_200_OK)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;urlpatterns = [
   path('post-a-job/', PostAJobListView.as_view(), name='post_a_job_list'),
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Testing the Search
&lt;/h3&gt;

&lt;p&gt;Now, you can use the ?search= query parameter in the URL to search for books by title or author. For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://127.0.0.1:8000/post-a-job/?search=Machine&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
This will return all jobs with "Machine" in the title or category or skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Advanced Search Customization
&lt;/h2&gt;

&lt;p&gt;You can further customize the search functionality in various ways.&lt;br&gt;
The search behavior may be specified by prefixing field names in search_fields with one of the following characters (which is equivalent to adding __ to the field):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfu09wnqa1x6meotyxmn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfu09wnqa1x6meotyxmn.png" alt="Advanced Search Customization" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Implementing Custom Search Functionality
In some cases, you may need more control over the search behavior. You can override the get_queryset method in the view.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def get_queryset(self):
       """
       Optionally restricts the returned jobs to query parameter in the URL.
       """

       filter_query = Q()
       job_title = self.request.query_params.get(title)
       if job_title is not None:
           filter_query &amp;amp;= Q(job_title__contains=job_title)

       user_id = self.request.query_params.get('user_id')
       if user_id is not None:
           filter_query &amp;amp;= Q(created_by=user_id)

       salary = self.request.query_params.get('salary')
       if salary is not None:
           filter_query &amp;amp;= Q(job_salary_range__gte=salary)

       queryset = self.queryset.filter(filter_query)

       return queryset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we manually filter the queryset based on the search query parameter, allowing for more complex search logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.django-rest-framework.org/api-guide/filtering/#searchfilter" rel="noopener noreferrer"&gt;https://www.django-rest-framework.org/api-guide/filtering/#searchfilter&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Adding search functionality to your Django Rest Framework APIs can significantly enhance the user experience by allowing for flexible and efficient data retrieval. Using DRF's built-in SearchFilter, you can easily add basic search capabilities, and with a bit of customization, create more advanced and tailored search options. For even more performance, consider integrating specialized search tools.&lt;br&gt;
Feel free to extend this example by implementing different filtering strategies or integrating third-party search services as per your application needs!&lt;/p&gt;

</description>
      <category>django</category>
      <category>djangorestframework</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Handling Complex Multi Step Forms with Formik and Yup</title>
      <dc:creator>Theresa Okoro</dc:creator>
      <pubDate>Sat, 14 Sep 2024 23:40:25 +0000</pubDate>
      <link>https://dev.to/theresa_okoro/handling-complex-multi-step-forms-with-formik-and-yup-1995</link>
      <guid>https://dev.to/theresa_okoro/handling-complex-multi-step-forms-with-formik-and-yup-1995</guid>
      <description>&lt;h3&gt;
  
  
  Introduction:
&lt;/h3&gt;

&lt;p&gt;When creating forms on our own, we are tasked with managing our forms values, writing our custom event handlers for every input, handling error states manually and the worst part is creating verbose validation logic. This might be manageable for simple forms, but for more complex forms, it becomes imperative to use a form handler.&lt;/p&gt;

&lt;p&gt;I love Formik and Yup because they're simple to use while still being lightweight so that they don't affect the performance of our site.&lt;/p&gt;

&lt;p&gt;Formik provides a minimal API to manage form state, validation, error handling, and form submission. It gives you full control over how your form behaves without being too restrictive as it gives you the room and autonomy to adjust and manipulate your form to your desired state. Meanwhile, Yup is a concise but powerful object schema validation that compliments Formik to create powerful validation schemas, ensuring that your forms have strong validation rules and it is small enough for the browser and fast enough for runtime usage so not taking up too much performance space (which is very important for external apps).&lt;/p&gt;

&lt;p&gt;Formik has a special config option / prop for Yup called validationSchema which will automatically transform Yup's validation errors into a pretty object whose keys match values and touched i.e whatever name you have for your field values and initialValues should match the name provided to the validationSchema.&lt;/p&gt;

&lt;p&gt;What I love about Formik is that it provides components that abstracts and hides unneeded info under the hood by offering components like &lt;code&gt;&amp;lt;Form /&amp;gt;, &amp;lt;Field /&amp;gt;, and &amp;lt;ErrorMessage /&amp;gt;&lt;/code&gt; that handle value changes, blur events, and validation messages, reducing the need for repetitive code. These components use React context, allowing you to access form state globally and make managing complex forms more efficient.&lt;/p&gt;

&lt;p&gt;Common scenarios where complex forms are necessary include checkout processes, or applications with dynamic user data, such as adding multiple addresses or phone numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Overview:
&lt;/h3&gt;

&lt;p&gt;In this article, we'll build a multi-step form for a job board, where each step gathers specific information, such as job details and company information. We'll also implement dynamic fields, error handling, and validations using Yup and this is going to be done in a simple and succinct way.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup for the Tutorial
&lt;/h4&gt;

&lt;p&gt;We'll start by setting up a React project with Formik and Yup. The installation is straightforward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Formik Installation: &lt;/strong&gt;&lt;br&gt;
&lt;code&gt;npm install formik - save or yarn add formik&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Yup Installation:&lt;/strong&gt; &lt;br&gt;
&lt;code&gt;npm install yup - save or yarn add yup&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Once installed, we'll configure Formik and Yup, ensuring the form values, validation rules, and submission process are integrated.&lt;/p&gt;
&lt;h4&gt;
  
  
  Basic Setup of Formik
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Formik, Form, Field, ErrorMessage } from "formik";

const Basic = () =&amp;gt; (
 &amp;lt;div&amp;gt;
   &amp;lt;Formik
     initialValues={{ email: "", password: "" }} // Initial form values
     validate={(values) =&amp;gt; {
       // Custom validation logic
       const errors = {};
       if (!values.email) {
         errors.email = "Required";
       } else if (!/\S+@\S+\.\S+/.test(values.email)) {
         errors.email = "Invalid email address";
       }
       if (!values.password) {
         errors.password = "Required";
       }
       return errors;
     }}
     onSubmit={(values, { setSubmitting }) =&amp;gt; {
       // Handle form submission
       setTimeout(() =&amp;gt; {
         alert(JSON.stringify(values, null, 2));
         setSubmitting(false);
       }, 400);
     }}
   &amp;gt;
     {({ isSubmitting }) =&amp;gt; (
       &amp;lt;Form&amp;gt;
         &amp;lt;Field type="email" name="email" /&amp;gt; {/* Email input field */}
         &amp;lt;ErrorMessage name="email" component="div" /&amp;gt; {/* Error for email */}
         &amp;lt;Field type="password" name="password" /&amp;gt; {/* Password input field */}
         &amp;lt;ErrorMessage name="password" component="div" /&amp;gt;{" "}
         {/* Error for password */}
         &amp;lt;button type="submit" disabled={isSubmitting}&amp;gt;
           Submit
         &amp;lt;/button&amp;gt;
       &amp;lt;/Form&amp;gt;
     )}
   &amp;lt;/Formik&amp;gt;
 &amp;lt;/div&amp;gt;
);

export default Basic;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Initial Values:&lt;/strong&gt; You define the initial state of your form by passing an initialValues object to Formik. In this case, the form has two fields: email and password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation:&lt;/strong&gt; You can create custom validation logic in the validate function. Here, the form checks if the email and password fields are empty or if the email is in an invalid format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Submission Handling:&lt;/strong&gt; When the form is submitted, the onSubmit function handles the form data. This function simulates a form submission by displaying the values in an alert after a 400ms delay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Field:&lt;/strong&gt; This component connects your input field to Formik, automatically managing value changes and error handling for you. You simply should add the type if it's not a normal input field and the name (this should tally with the name on the initialValues and validation schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ErrorMessage:&lt;/strong&gt; Displays error messages for each field, simplifying the error display logic, this should contain the name of the field.&lt;/p&gt;
&lt;h2&gt;
  
  
  STEP 1: MultiStepForm Component
&lt;/h2&gt;

&lt;p&gt;Create a Multi-Step-Form component file within your components folder.&lt;/p&gt;

&lt;p&gt;project/src/components/multi-step-form.tsx&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Fragment } from "react";

type stepProps = {
 steps: any[];
 currentStep: number;
};

const MultiStepForm = ({
 steps,
 currentStep,
}: 
stepProps) =&amp;gt; {
 return (
   &amp;lt;div className="md:max-w-5xl mx-auto p-4 text-black font-base"&amp;gt;
     &amp;lt;div className="md:flex items-center justify-between mb-8 hidden"&amp;gt;
       {steps.map((step, index) =&amp;gt; (
         &amp;lt;Fragment key={step.id}&amp;gt;
           &amp;lt;div className="flex items-center mx-4"&amp;gt;
             &amp;lt;div
               className={`rounded-full h-12 w-12 flex items-center justify-center text-white ${
                 currentStep &amp;gt;= step.id ? "bg-purple-200" : "bg-gray-400"
               }`}
             &amp;gt;
               {step.id}
             &amp;lt;/div&amp;gt;
             &amp;lt;div className="ml-2 text-xl font-medium "&amp;gt;{step.title}&amp;lt;/div&amp;gt;
           &amp;lt;/div&amp;gt;
           {index &amp;lt; steps.length - 1 &amp;amp;&amp;amp; (
             &amp;lt;div
               className={`flex-1 h-1 ${
                 currentStep &amp;gt; step.id ? "bg-purple-200" : "bg-gray-400"
               }`}
             &amp;gt;&amp;lt;/div&amp;gt;
           )}
         &amp;lt;/Fragment&amp;gt;
       ))}
     &amp;lt;/div&amp;gt;

     &amp;lt;div className="mb-4"&amp;gt;
       &amp;lt;h2 className="text-2xl font-bold mb-4 flex justify-center"&amp;gt;
         {steps[currentStep - 1].title}
       &amp;lt;/h2&amp;gt;
     &amp;lt;/div&amp;gt;
   &amp;lt;/div&amp;gt;
 );
};

export default MultiStepForm;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MultiStepForm component displays a progress bar showing the current step in a multi-step form process. Each step is represented by a number and title, with lines connecting them for completed steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps Array:&lt;/strong&gt; The steps array contains an ID and title for each step.&lt;br&gt;
&lt;strong&gt;Current Step:&lt;/strong&gt; The currentStep prop controls the active step visually and in the title.&lt;br&gt;
&lt;strong&gt;Step Indicator:&lt;/strong&gt; The round indicators change color when completed, and a horizontal line connects the steps.&lt;/p&gt;
&lt;h2&gt;
  
  
  STEP 2: Setting Up AddJob Component
&lt;/h2&gt;

&lt;p&gt;Next, we create the AddJob component, which will hold the multi-step form's logic.&lt;/p&gt;

&lt;p&gt;Within your SRC routes structure create a page for add-job.tsx, let's start with the boilerplate.&lt;/p&gt;

&lt;p&gt;Here, currentStep tracks the user's position in the form, and goToNextStep and goToPreviousStep manages navigation by checking what step the user is on, add one step to the step to take the user to the next page or minus one step to take the user back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
import { useState } from "react";

const AddJob = () =&amp;gt; {
 const [currentStep, setCurrentStep] = useState(1);
 const steps = [
   { id: 1, title: "Job Information" },
   { id: 2, title: "Company Information" },
   { id: 3, title: "Submit" },
 ];

 const goToNextStep = () =&amp;gt; {
   if (currentStep &amp;lt; steps.length) {
     setCurrentStep(currentStep + 1);
   }
 };

 const goToPreviousStep = () =&amp;gt; {
   if (currentStep &amp;gt; 1) {
     setCurrentStep(currentStep - 1);
   }
 };
};

return (TODO)

export default AddJob;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  STEP 3: Form Validation with Yup
&lt;/h2&gt;

&lt;p&gt;We are going to define our validation schema using Yup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as Yup from "yup"

const validationSchema = Yup.object({
 jobTitle: Yup.string().required("Job title is required"),
 jobDescription: Yup.string().required("Please describe this role"),
 jobCategory: Yup.string().required("Kindly select a job category"),
 jobSkills: Yup.string().required("Please select skills needed"),
 jobSalary: Yup.string().required("Select a job salary"),
 jobUrl: Yup.string().required("Provide the link for the job"),
 companyName: Yup.string().required("Please provide the name"),
 companyHq: Yup.string(),
 companysWebsite: Yup.string().required("Enter the company's website"),
 companysEmail: Yup.string()
   .email("Invalid email address")
   .required("Enter the company's email"),
 companysDescription: Yup.string().required("What does the company do?"),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Currently, we are doing basic schema checks for almost all fields seen above.&lt;/p&gt;

&lt;p&gt;Yup.string()/required("Error message to be shown to the user") states that the field is a required field which expects string values.&lt;/p&gt;

&lt;p&gt;While companysEmail: Yup.string().email("Invalid email address")required("Enter the company's email"), validates that the user has inputted a string with an email format and it is a required field.&lt;/p&gt;

&lt;p&gt;While there are more complex schema validation and I might create a separate article delving into it, let's keep the validation schema simple for our use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Integrating Formik for Form Handling
&lt;/h2&gt;

&lt;p&gt;In this step, we'll integrate Formik to handle form data, validation, and submission logic. We'll also connect it to our multi-step form navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Importing Formik and Setting Up Form Structure
&lt;/h3&gt;

&lt;p&gt;First, let's import Formik, Field, Form, and ErrorMessage components.&lt;br&gt;
We'll then set up our form structure with initial values and link it to our validation schema.&lt;/p&gt;

&lt;p&gt;Now, let's add the Formik component and bind it with our initial form values, validation schema, and the submission handler. We also include the MultiStepForm component to visually display the current step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useState } from "react";
import MultiStepForm from "./components/multi-step-form";
import { Formik, Field, Form, ErrorMessage } from "formik";
import * as Yup from "yup";

const validationSchema = Yup.object({
 jobTitle: Yup.string().required("Job title is required"),
    // other fields...
});

const AddJob = () =&amp;gt; {
 const [currentStep, setCurrentStep] = useState(1);
 const steps = [
…
 ];

 const goToNextStep = () =&amp;gt; {
…
 };

 const goToPreviousStep = () =&amp;gt; {
…
 };

 const createJobFn = (values, { resetForm, setSubmitting }) =&amp;gt; {
   console.log(values);
   resetForm();
   setSubmitting(false);
 };

 return (
   &amp;lt;section className="w-full nx-auto flex-row min-h-screen flex justify-center items-center bg-gray-100 px-4"&amp;gt;
     &amp;lt;div className="bg-white shadow-lg rounded-lg p-8"&amp;gt;
       &amp;lt;MultiStepForm
         steps={steps}
         currentStep={currentStep}
       /&amp;gt;
       &amp;lt;Formik
         initialValues={{
           jobTitle: "",
           jobDescription: "",
           jobCategory: "",
           jobSkills: "",
           jobSalary: "",
           jobUrl: "",
           companyName: "",
           companyHq: "",
           companysWebsite: "",
           companysEmail: "",
           companysDescription: "",
         }}
         validationSchema={validationSchema}
         onSubmit={(values, { resetForm, setSubmitting }) =&amp;gt; {
           createJobFn(values, {
             setSubmitting,
             resetForm,
           });
         }}
       &amp;gt;
         {({
           isSubmitting,
           setFieldValue,
           isValid,
         }) =&amp;gt; &amp;lt;div className="md:container md:mx-auto mb-12"&amp;gt;TODO&amp;lt;/div&amp;gt;}
       &amp;lt;/Formik&amp;gt;
     &amp;lt;/div&amp;gt;
   &amp;lt;/section&amp;gt;
 );
};

export default AddJob;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Initial Values:&lt;/strong&gt; We define the initial form values using initialValues in the Formik component. Each field corresponds to the keys in our validation schema. These values will change as the user inputs data into the form.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation:&lt;/strong&gt; The validationSchema prop connects Formik with the Yup validation we set up earlier. This ensures that when the form is submitted, the data is validated according to the rules specified (e.g., required fields).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Form Submission:&lt;/strong&gt; The onSubmit prop defines the createJobFn function to handle the form submission. It logs the form values to the console, resets the form, and sets isSubmitting to false to prevent multiple submissions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Form State Handling:&lt;/strong&gt; Formik provides useful props (values, errors, touched, isSubmitting, isValid, etc.) to manage and track form states. These can be used to conditionally render content, control button states, and handle form validation and submission more dynamically. Here, we are passing isSubmitting and isValid that we would use in the following steps to check if the user has filled the required fields before they can submit the form - this shows how using Formik gives us the power to control and manage our forms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Constant File For Our Select Fields
&lt;/h2&gt;

&lt;p&gt;Create a constant file that we would map through to get our select options as some of our fields would be a select field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const CATEGORIES = [
 { id: "1", name: "Engineering" },
 { id: "2", name: "Marketing" },
 { id: "3", name: "Design" },
];

export const SKILLS = [
 { id: "1", name: "JavaScript" },
 { id: "2", name: "React" },
 { id: "3", name: "CSS" },
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3lh4zlrq9ubrzfl76z5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3lh4zlrq9ubrzfl76z5.png" alt="Form Step 2" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Return Statement
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;..
 return (
   &amp;lt;section className="w-full nx-auto flex-row min-h-screen flex justify-center items-center bg-gray-100 px-4"&amp;gt;
     &amp;lt;div className="bg-white shadow-lg rounded-lg p-8"&amp;gt;
       &amp;lt;MultiStepForm steps={steps} currentStep={currentStep} /&amp;gt;
       &amp;lt;Formik
         initialValues={{
           jobTitle: "",
   …other fields
         }}
         validationSchema={validationSchema}
         onSubmit={(values, { resetForm, setSubmitting }) =&amp;gt; {
  …other fields
           });
         }}
       &amp;gt;
         {({ isSubmitting, isValid }) =&amp;gt; (
           &amp;lt;div&amp;gt;
             &amp;lt;Form className="bg-white shadow-lg rounded-lg w-full px-8 max-w-4xl mx-auto"&amp;gt;
               {currentStep === 1 &amp;amp;&amp;amp; (
                 &amp;lt;div className="flex flex-col mb-2"&amp;gt;
                   &amp;lt;div className="mb-4"&amp;gt;
                     &amp;lt;label
                       htmlFor="jobTitle"
                       className="block text-gray-700 text-sm font-bold mb-2"
                     &amp;gt;
                       Job Title{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="jobTitle"
                       type="text"
                       className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="jobTitle"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="flex flex-wrap"&amp;gt;
                     &amp;lt;div className="w-full md:w-1/2 md:pr-5 mb-2"&amp;gt;
                       &amp;lt;label
                         htmlFor="jobCategory"
                         className="block text-gray-700 text-sm font-bold"
                       &amp;gt;
                         Job Category{" "}
                         &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                           *
                         &amp;lt;/span&amp;gt;
                       &amp;lt;/label&amp;gt;
                       &amp;lt;div className="relative"&amp;gt;
                         &amp;lt;Field
                           as="select"
                           name="jobCategory"
                           className="shadow appearance-none capitalize border rounded w-full py-2 px-3 bg-white text-black text-sm leading-tight focus:outline-none focus:shadow-outline"
                         &amp;gt;
                           &amp;lt;option value="" disabled&amp;gt;
                             Select
                           &amp;lt;/option&amp;gt;
                           {CATEGORIES?.map((category) =&amp;gt; (
                             &amp;lt;option key={category.id} value={category.id}&amp;gt;
                               {category.name}
                             &amp;lt;/option&amp;gt;
                           ))}
                         &amp;lt;/Field&amp;gt;
                       &amp;lt;/div&amp;gt;
                       &amp;lt;ErrorMessage
                         name="jobCategory"
                         component="div"
                         className="!text-red-500 text-xs italic mt-2"
                       /&amp;gt;
                     &amp;lt;/div&amp;gt;

                     &amp;lt;div className="w-full md:w-1/2 md:pl-5 mb-2"&amp;gt;
                       &amp;lt;label
                         htmlFor="jobSkills"
                         className="block text-gray-700 text-sm font-bold"
                       &amp;gt;
                         Job Skills{" "}
                         &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                           *
                         &amp;lt;/span&amp;gt;
                       &amp;lt;/label&amp;gt;
                       &amp;lt;div className="relative"&amp;gt;
                         &amp;lt;Field
                           as="select"
                           name="jobSkills"
                           className="shadow appearance-none capitalize border rounded w-full py-2 px-3 bg-white text-black text-sm leading-tight focus:outline-none focus:shadow-outline"
                         &amp;gt;
                           &amp;lt;option value="" disabled&amp;gt;
                             Select
                           &amp;lt;/option&amp;gt;
                           {SKILLS?.map((skill) =&amp;gt; (
                             &amp;lt;option key={skill.id} value={skill.id}&amp;gt;
                               {skill.name}
                             &amp;lt;/option&amp;gt;
                           ))}
                         &amp;lt;/Field&amp;gt;
                       &amp;lt;/div&amp;gt;
                       &amp;lt;ErrorMessage
                         name="jobSkills"
                         component="div"
                         className="!text-red-500 text-xs italic mt-2"
                       /&amp;gt;
                     &amp;lt;/div&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="flex flex-wrap"&amp;gt;
                     &amp;lt;div className="w-full md:w-1/2 md:pr-5 mb-2"&amp;gt;
                       &amp;lt;label
                         htmlFor="jobSalary"
                         className="block text-gray-700 text-sm font-bold"
                       &amp;gt;
                         Job Salary{" "}
                         &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                           *
                         &amp;lt;/span&amp;gt;
                       &amp;lt;/label&amp;gt;
                       &amp;lt;Field
                         name="jobSalary"
                         type="text"
                         className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                       /&amp;gt;
                       &amp;lt;ErrorMessage
                         name="jobSalary"
                         component="div"
                         className="!text-red-500 text-xs italic mt-2"
                       /&amp;gt;{" "}
                     &amp;lt;/div&amp;gt;

                     &amp;lt;div className="w-full md:w-1/2 md:pl-5 mb-2"&amp;gt;
                       &amp;lt;label
                         htmlFor="jobUrl"
                         className="block text-gray-700 text-sm font-bold"
                       &amp;gt;
                         Job Link{" "}
                         &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                           *
                         &amp;lt;/span&amp;gt;
                       &amp;lt;/label&amp;gt;
                       &amp;lt;Field
                         name="jobUrl"
                         type="text"
                         className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                       /&amp;gt;
                       &amp;lt;ErrorMessage
                         name="jobUrl"
                         component="div"
                         className="!text-red-500 text-xs italic mt-2"
                       /&amp;gt;
                     &amp;lt;/div&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="jobDescription"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Job Description{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="jobDescription"
                       type="textarea"
                       as="textarea"
                       className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="jobDescription"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;
                 &amp;lt;/div&amp;gt;
               )}

               {currentStep === 2 &amp;amp;&amp;amp; (
                 &amp;lt;div&amp;gt;
                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="companyName"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Company Name{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="companyName"
                       type="text"
                       className="shadow appearance-none border rounded w-full py-2 px-3 capitalize bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="companyName"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="companyHq"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Company HQ
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="companyHq"
                       type="text"
                       className="shadow appearance-none border rounded w-full py-2 px-3 capitalize bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="companyHq"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="companysWebsite"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Company's Website{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="companysWebsite"
                       type="text"
                       className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="companysWebsite"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="companysEmail"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Company's Email{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="companysEmail"
                       type="email"
                       className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="companysEmail"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="companysDescription"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Company's Description{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="companysDescription"
                       type="textarea"
                       as="textarea"
                       className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="companysDescription"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;
                 &amp;lt;/div&amp;gt;
               )}

               {currentStep === 3 &amp;amp;&amp;amp; (
                 &amp;lt;div className="flex flex-col justify-center text-black"&amp;gt;
                   &amp;lt;h1&amp;gt;Thank You.&amp;lt;/h1&amp;gt;
                   &amp;lt;h2&amp;gt;The team would review and provide feedback&amp;lt;/h2&amp;gt;
                 &amp;lt;/div&amp;gt;
               )}

               &amp;lt;div className="flex justify-between p-4"&amp;gt;
                 {currentStep === 1 ? (
                   &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
                 ) : (
                   &amp;lt;button
                     onClick={goToPreviousStep}
                     type="button"
                     className="px-6 py-2 min-w-[120px] text-center text-white bg-purple-900 border border-purple-900 rounded active:text-purple-200  hover:bg-transparent hover:text-purple-900 focus:outline-none"
                   &amp;gt;
                     Previous
                   &amp;lt;/button&amp;gt;
                 )}

                 {currentStep === 1 || currentStep === 2 ? (
                   &amp;lt;button
                     onClick={(e) =&amp;gt; {
                       e.preventDefault();
                       goToNextStep();
                     }}
                     type="button"
                     className="px-6 py-2 min-w-[120px] text-center text-white bg-purple-900 border border-purple-900 rounded active:text-purple-200 hover:bg-transparent hover:text-purple-900 focus:outline-none"
                   &amp;gt;
                     Next
                   &amp;lt;/button&amp;gt;
                 ) : (
                   &amp;lt;button
                     type="submit"
                     disabled={isSubmitting || !isValid}
                     className="px-6 py-2 min-w-[120px] text-center text-white bg-purple-900 border border-purple-900 rounded active:text-purple-200 hover:bg-transparent hover:text-purple-900 focus:outline-none"
                   &amp;gt;
                     {isValid ? "Incomplete form" : "Submit"}
                   &amp;lt;/button&amp;gt;
                 )}
               &amp;lt;/div&amp;gt;
             &amp;lt;/Form&amp;gt;
           &amp;lt;/div&amp;gt;
         )}
       &amp;lt;/Formik&amp;gt;
     &amp;lt;/div&amp;gt;
   &amp;lt;/section&amp;gt;
 );
};

export default AddJob;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the key parts of this return statement, focusing on currentStep === 1, labels, fields, error messages, and the button section.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;currentStep === 1:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This block renders the form fields for step 1. The currentStep prop controls which part of the form is shown. When currentStep === 1, it displays input fields like jobTitle, jobCategory, jobSkills, jobSalary, jobUrl, and jobDescription.&lt;br&gt;
This structure allows for multi-step forms, showing different fields depending on the current step of the form.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Labels, Fields, and Error Messages:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Labels:&lt;/strong&gt;&lt;br&gt;
Each form field includes a  element to describe the input. This label is connected to the corresponding field using the htmlFor attribute.&lt;br&gt;
For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;label htmlFor="jobTitle" className="block text-gray-700 text-sm font-bold mb-2"&amp;gt;
  Job Title &amp;lt;span className="text-red-500"&amp;gt;*&amp;lt;/span&amp;gt;
&amp;lt;/label&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fields:&lt;/strong&gt;&lt;br&gt;
Uses the Field component from Formik to create input fields. The name prop links the field to a property in Formik's initialValues. This connection enables Formik to manage the form's state and validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Field
name="jobTitle"
type="text"
className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm"
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Error Messages:&lt;/strong&gt;&lt;br&gt;
The ErrorMessage component is used to display validation errors. name specifies which field's error to display, and component defines the HTML element to render the error message in and then a red text styling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ErrorMessage
name="jobTitle"
component="div"
className="!text-red-500 text-xs italic mt-2"
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Button Explanation:&lt;/strong&gt;&lt;br&gt;
There are three buttons in the form, shown conditionally based on the currentStep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Previous Button&lt;/strong&gt;&lt;br&gt;
Displayed only when currentStep is not 1. This button allows the user to go back to the previous step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;button
onClick={goToPreviousStep}
type="button"
className="…"
&amp;gt;
Previous
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Next Button:&lt;/strong&gt;&lt;br&gt;
Displayed for steps 1 and 2. This button advances the form to the next step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;button
    onClick={(e) =&amp;gt; {
       e.preventDefault();
        goToNextStep();
     }}
    type="button"
    className="..."
  &amp;gt;
   Next
 &amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff8vgqaf3gh24db0wkmp3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff8vgqaf3gh24db0wkmp3.png" alt="Form Step 3" width="800" height="470"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Submit Button:&lt;/strong&gt;&lt;br&gt;
Displayed when the final step (not 1 or 2) is reached. The button's disabled state is controlled by Formik's isSubmitting and isValid properties so this help us disable the button if the form is already in the process of submission, this prevents duplicate submission or if its invalid i.e if the user does not enter the required fields in the valid format as defined in the validation schema.&lt;/p&gt;

&lt;p&gt;Then our button text displays "Incomplete form" if the form is invalid, otherwise, it shows "Submit".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;button
type="submit"
disabled={isSubmitting || !isValid}
className="…"
&amp;gt;
{isValid ? "Incomplete form" : "Submit"}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, here's the full working code. I hope this gives you some solid information as to how powerful Formik and Yup are in terms of giving you the control to build lightweight forms.&lt;/p&gt;

&lt;p&gt;The documentation is very easy to browse through and understand so feel free to check out the documentation to learn more.&lt;/p&gt;

&lt;p&gt;Here's the full AddJob Form code below;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useState } from "react";
import MultiStepForm from "./components/multi-step-form";
import { Formik, Field, Form, ErrorMessage } from "formik";
import * as Yup from "yup";
import { CATEGORIES, SKILLS } from "./constants";

const validationSchema = Yup.object({
 jobTitle: Yup.string().required("Job title is required"),
 jobDescription: Yup.string().required("Please describe this role"),
 jobCategory: Yup.string().required("Kindly select a job category"),
 jobSkills: Yup.string().required("Please select skills needed"),
 jobType: Yup.string().required("This is required"),
 jobLocation: Yup.string().required("Please select relevant locations"),
 jobLevel: Yup.string().required("What level is this job?"),
 jobSalary: Yup.string().required("Select a job salary"),
 jobUrl: Yup.string().required("Provide the link for the job"),
 // createdBy: Yup.string(),
 companyName: Yup.string().required("Please provide the name"),
 companyHq: Yup.string(),
 companysWebsite: Yup.string().required("Enter the company's website"),
 companysEmail: Yup.string()
   .email("Invalid email address")
   .required("Enter the company's email"),
 companysDescription: Yup.string().required("What does the company do?"),
});

const AddJob = () =&amp;gt; {
 const [currentStep, setCurrentStep] = useState(1);

 const steps = [
   { id: 1, title: "Job Information" },
   { id: 2, title: "Company Information" },
   { id: 3, title: "Submit" },
 ];

 const goToNextStep = () =&amp;gt; {
   if (currentStep &amp;lt; steps.length) {
     setCurrentStep(currentStep + 1);
   }
 };

 const goToPreviousStep = () =&amp;gt; {
   if (currentStep &amp;gt; 1) {
     setCurrentStep(currentStep - 1);
   }
 };

 const createJobFn = (values: any, { resetForm, setSubmitting }: any) =&amp;gt; {
   // Define your createJobFn logic here
   console.log(values);
   resetForm();
   setSubmitting(false);
 };

 return (
   &amp;lt;section className="w-full nx-auto flex-row min-h-screen flex justify-center items-center bg-gray-100 px-4"&amp;gt;
     &amp;lt;div className="bg-white shadow-lg rounded-lg p-8"&amp;gt;
       &amp;lt;MultiStepForm steps={steps} currentStep={currentStep} /&amp;gt;
       &amp;lt;Formik
         initialValues={{
           jobTitle: "",
           jobDescription: "",
           jobCategory: "",
           jobSkills: "",
           jobType: "",
           jobLocation: "",
           jobLevel: "",
           jobSalary: "",
           jobUrl: "",
           // createdBy: "",
           companyName: "",
           companyHq: "",
           companysWebsite: "",
           companysEmail: "",
           companysDescription: "",
         }}
         validationSchema={validationSchema}
         onSubmit={(values, { resetForm, setSubmitting }) =&amp;gt; {
           createJobFn(values, {
             setSubmitting,
             resetForm,
           });
         }}
       &amp;gt;
         {({ isSubmitting, isValid }) =&amp;gt; (
           &amp;lt;div&amp;gt;
             &amp;lt;Form className="bg-white shadow-lg rounded-lg w-full px-8 max-w-4xl mx-auto"&amp;gt;
               {currentStep === 1 &amp;amp;&amp;amp; (
                 &amp;lt;div className="flex flex-col mb-2"&amp;gt;
                   &amp;lt;div className="mb-4"&amp;gt;
                     &amp;lt;label
                       htmlFor="jobTitle"
                       className="block text-gray-700 text-sm font-bold mb-2"
                     &amp;gt;
                       Job Title{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="jobTitle"
                       type="text"
                       className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="jobTitle"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="flex flex-wrap"&amp;gt;
                     &amp;lt;div className="w-full md:w-1/2 md:pr-5 mb-2"&amp;gt;
                       &amp;lt;label
                         htmlFor="jobCategory"
                         className="block text-gray-700 text-sm font-bold"
                       &amp;gt;
                         Job Category{" "}
                         &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                           *
                         &amp;lt;/span&amp;gt;
                       &amp;lt;/label&amp;gt;
                       &amp;lt;div className="relative"&amp;gt;
                         &amp;lt;Field
                           as="select"
                           name="jobCategory"
                           className="shadow appearance-none capitalize border rounded w-full py-2 px-3 bg-white text-black text-sm leading-tight focus:outline-none focus:shadow-outline"
                         &amp;gt;
                           &amp;lt;option value="" disabled&amp;gt;
                             Select
                           &amp;lt;/option&amp;gt;
                           {CATEGORIES?.map((category) =&amp;gt; (
                             &amp;lt;option key={category.id} value={category.id}&amp;gt;
                               {category.name}
                             &amp;lt;/option&amp;gt;
                           ))}
                         &amp;lt;/Field&amp;gt;
                       &amp;lt;/div&amp;gt;
                       &amp;lt;ErrorMessage
                         name="jobCategory"
                         component="div"
                         className="!text-red-500 text-xs italic mt-2"
                       /&amp;gt;
                     &amp;lt;/div&amp;gt;

                     &amp;lt;div className="w-full md:w-1/2 md:pl-5 mb-2"&amp;gt;
                       &amp;lt;label
                         htmlFor="jobSkills"
                         className="block text-gray-700 text-sm font-bold"
                       &amp;gt;
                         Job Skills{" "}
                         &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                           *
                         &amp;lt;/span&amp;gt;
                       &amp;lt;/label&amp;gt;
                       &amp;lt;div className="relative"&amp;gt;
                         &amp;lt;Field
                           as="select"
                           name="jobSkills"
                           className="shadow appearance-none capitalize border rounded w-full py-2 px-3 bg-white text-black text-sm leading-tight focus:outline-none focus:shadow-outline"
                         &amp;gt;
                           &amp;lt;option value="" disabled&amp;gt;
                             Select
                           &amp;lt;/option&amp;gt;
                           {SKILLS?.map((skill) =&amp;gt; (
                             &amp;lt;option key={skill.id} value={skill.id}&amp;gt;
                               {skill.name}
                             &amp;lt;/option&amp;gt;
                           ))}
                         &amp;lt;/Field&amp;gt;
                       &amp;lt;/div&amp;gt;
                       &amp;lt;ErrorMessage
                         name="jobSkills"
                         component="div"
                         className="!text-red-500 text-xs italic mt-2"
                       /&amp;gt;
                     &amp;lt;/div&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="flex flex-wrap"&amp;gt;
                     &amp;lt;div className="w-full md:w-1/2 md:pr-5 mb-2"&amp;gt;
                       &amp;lt;label
                         htmlFor="jobSalary"
                         className="block text-gray-700 text-sm font-bold"
                       &amp;gt;
                         Job Salary{" "}
                         &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                           *
                         &amp;lt;/span&amp;gt;
                       &amp;lt;/label&amp;gt;
                       &amp;lt;Field
                         name="jobSalary"
                         type="text"
                         className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                       /&amp;gt;
                       &amp;lt;ErrorMessage
                         name="jobSalary"
                         component="div"
                         className="!text-red-500 text-xs italic mt-2"
                       /&amp;gt;{" "}
                     &amp;lt;/div&amp;gt;

                     &amp;lt;div className="w-full md:w-1/2 md:pl-5 mb-2"&amp;gt;
                       &amp;lt;label
                         htmlFor="jobUrl"
                         className="block text-gray-700 text-sm font-bold"
                       &amp;gt;
                         Job Link{" "}
                         &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                           *
                         &amp;lt;/span&amp;gt;
                       &amp;lt;/label&amp;gt;
                       &amp;lt;Field
                         name="jobUrl"
                         type="text"
                         className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                       /&amp;gt;
                       &amp;lt;ErrorMessage
                         name="jobUrl"
                         component="div"
                         className="!text-red-500 text-xs italic mt-2"
                       /&amp;gt;
                     &amp;lt;/div&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="jobDescription"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Job Description{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="jobDescription"
                       type="textarea"
                       as="textarea"
                       className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="jobDescription"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;
                 &amp;lt;/div&amp;gt;
               )}

               {currentStep === 2 &amp;amp;&amp;amp; (
                 &amp;lt;div&amp;gt;
                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="companyName"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Company Name{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="companyName"
                       type="text"
                       className="shadow appearance-none border rounded w-full py-2 px-3 capitalize bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="companyName"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="companyHq"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Company HQ
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="companyHq"
                       type="text"
                       className="shadow appearance-none border rounded w-full py-2 px-3 capitalize bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="companyHq"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="companysWebsite"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Company's Website{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="companysWebsite"
                       type="text"
                       className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="companysWebsite"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="companysEmail"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Company's Email{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="companysEmail"
                       type="email"
                       className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="companysEmail"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;

                   &amp;lt;div className="mb-2"&amp;gt;
                     &amp;lt;label
                       htmlFor="companysDescription"
                       className="block text-gray-700 text-sm font-bold"
                     &amp;gt;
                       Company's Description{" "}
                       &amp;lt;span className="text-red-500 ltr:mr-1 rtl:ml-1"&amp;gt;
                         *
                       &amp;lt;/span&amp;gt;
                     &amp;lt;/label&amp;gt;
                     &amp;lt;Field
                       name="companysDescription"
                       type="textarea"
                       as="textarea"
                       className="shadow appearance-none border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline"
                     /&amp;gt;
                     &amp;lt;ErrorMessage
                       name="companysDescription"
                       component="div"
                       className="!text-red-500 text-xs italic mt-2"
                     /&amp;gt;
                   &amp;lt;/div&amp;gt;
                 &amp;lt;/div&amp;gt;
               )}

               {currentStep === 3 &amp;amp;&amp;amp; (
                 &amp;lt;div className="flex flex-col justify-center text-black"&amp;gt;
                   &amp;lt;h1&amp;gt;Thank You.&amp;lt;/h1&amp;gt;
                   &amp;lt;h2&amp;gt;The team would review and provide feedback&amp;lt;/h2&amp;gt;
                 &amp;lt;/div&amp;gt;
               )}

               &amp;lt;div className="flex justify-between p-4"&amp;gt;
                 {currentStep === 1 ? (
                   &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
                 ) : (
                   &amp;lt;button
                     onClick={goToPreviousStep}
                     type="button"
                     className="px-6 py-2 min-w-[120px] text-center text-white bg-purple-900 border border-purple-900 rounded active:text-purple-200  hover:bg-transparent hover:text-purple-900 focus:outline-none"
                   &amp;gt;
                     Previous
                   &amp;lt;/button&amp;gt;
                 )}

                 {currentStep === 1 || currentStep === 2 ? (
                   &amp;lt;button
                     onClick={(e) =&amp;gt; {
                       e.preventDefault();
                       goToNextStep();
                     }}
                     type="button"
                     className="px-6 py-2 min-w-[120px] text-center text-white bg-purple-900 border border-purple-900 rounded active:text-purple-200 hover:bg-transparent hover:text-purple-900 focus:outline-none"
                   &amp;gt;
                     Next
                   &amp;lt;/button&amp;gt;
                 ) : (
                   &amp;lt;button
                     type="submit"
                     disabled={isSubmitting || !isValid}
                     className="px-6 py-2 min-w-[120px] text-center text-white bg-purple-900 border border-purple-900 rounded active:text-purple-200 hover:bg-transparent hover:text-purple-900 focus:outline-none"
                   &amp;gt;
                     {isValid ? "Incomplete form" : "Submit"}
                   &amp;lt;/button&amp;gt;
                 )}
               &amp;lt;/div&amp;gt;
             &amp;lt;/Form&amp;gt;
           &amp;lt;/div&amp;gt;
         )}
       &amp;lt;/Formik&amp;gt;
     &amp;lt;/div&amp;gt;
   &amp;lt;/section&amp;gt;
 );
};

export default AddJob;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;br&gt;
By using Formik and Yup, handling complex multi-step forms becomes much more manageable. Their ability to handle dynamic fields, conditional logic, and schema-based validation reduces the need for verbose custom code, letting you focus on building an excellent user experience.&lt;/p&gt;

</description>
      <category>multistepform</category>
      <category>formik</category>
      <category>yup</category>
    </item>
    <item>
      <title>How to Use Tiptap Rich Text Editor with Next.js and Tailwind CSS: A Simple Guide</title>
      <dc:creator>Theresa Okoro</dc:creator>
      <pubDate>Fri, 30 Aug 2024 11:37:13 +0000</pubDate>
      <link>https://dev.to/theresa_okoro/how-to-use-tiptap-rich-text-editor-with-nextjs-and-tailwind-css-a-simple-guide-18c2</link>
      <guid>https://dev.to/theresa_okoro/how-to-use-tiptap-rich-text-editor-with-nextjs-and-tailwind-css-a-simple-guide-18c2</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59xwallon7a1t5flg2z9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59xwallon7a1t5flg2z9.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want a functional Rich Text Editor (RTE) that is simple to set up and use, you’ve come to the right place.&lt;/p&gt;

&lt;p&gt;Tiptap is a headless editor framework with an open source core that allows you to build and customise your rich text editor effortlessly. The great  thing about it is that it already gives you the essentials without requiring you to understand the low-level details making it an excellent choice for modern web applications. &lt;/p&gt;

&lt;p&gt;I went on a rich text editor gold search and everywhere I searched, someone recommended TipTap as the top rich text editor. I used it, loved it, ran into some errrmm(not issues but my fuzzy brain did not necessary know certain things and how it should be adjusted especially if you are using TailwindCSS) so I'm here to give you the guide I wish I had.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHY TIPTAP:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ease of Use: Intuitive and easy to integrate into your project.&lt;/li&gt;
&lt;li&gt;Customizable: Add or remove features as needed without delving into complex configurations.&lt;/li&gt;
&lt;li&gt;Flexible Styling: Customize the styling of your Tiptap editor to create a unique design with minimal effort.&lt;/li&gt;
&lt;li&gt;Easy-to-Use Documentation: Tiptap's documentation is straightforward and easy to follow. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without further ado, let’s get into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  SET UP A BASIC TIPTAP EDITOR
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create a project (optional)
&lt;/h3&gt;

&lt;p&gt;If you don’t have an existing project, create one using the following command: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#create a project
npx create-next-app our-tiptap-project

# change directory
cd our-tiptap-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Install dependencies
&lt;/h3&gt;

&lt;p&gt;We need to install three packages: @tiptap/react, @tiptap/pm and @tiptap/starter-kit which includes all the extensions you need to get started quickly.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @tiptap/react @tiptap/pm @tiptap/starter-kit

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Create a Tiptap Component
&lt;/h3&gt;

&lt;p&gt;In your project, create a new component file called Tiptap.jsx or Tiptap.tsx (if you're using TypeScript).&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'use client'
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
const Tiptap = () =&amp;gt; {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '&amp;lt;p&amp;gt;Welcome to Tiptap&amp;lt;/p&amp;gt;',
  })
  return &amp;lt;EditorContent editor={editor} /&amp;gt;
}
export default Tiptap

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Add it to your app
&lt;/h3&gt;

&lt;p&gt;Add your Tiptap component in your index file or your desired file.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Tiptap from '../components/Tiptap'
export default function Home() {
  return &amp;lt;Tiptap /&amp;gt;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run “npm run dev” and go to your localhost to see the editor in action - open &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Welcome to the TipTap world, but hey, we can do more than that basic setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  EXTENDING THE EDITOR WITH MORE FEATURES
&lt;/h2&gt;

&lt;p&gt;Now, let’s make our TipTap editor more robust with fun things like making your text bold, italic, adding header, bullet list and ordered list. You can also easily add more functionality by looking at the doc.&lt;/p&gt;

&lt;h4&gt;
  
  
  INDEX.TSX
&lt;/h4&gt;

&lt;p&gt;On our Tiptap component, we’re going to use two props and use useState to manage those props.&lt;/p&gt;

&lt;p&gt;The initial content the user types in the input field will be stored in the jobDescription and the changes would be saved via  setJobDescription]&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const [jobDescription, setJobDescription] = useState("");
   &amp;lt;Tiptap
    editorContent={jobDescription}
    onChange={setJobDescription}
  /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  TIPTAP.TSX
&lt;/h4&gt;
&lt;h4&gt;
  
  
  Step 1: Define Your Props (TypeScript Only)
&lt;/h4&gt;

&lt;p&gt;Add the types for your props if you're using TypeScript:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface TipTapProps {
  editorContent: string;
  onChange: (content: string) =&amp;gt; void;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Step 2: Initialise the Editor
&lt;/h4&gt;

&lt;p&gt;This is where we can customise our rich text editor by adding the nodes (bulletlist, code block, emoji, heading, ordered list e.t.c), marks (bold, italics, strike, underline e.t.c) that we want to add to our app.&lt;/p&gt;

&lt;p&gt;The StarterKit gives you a lot out of the box like bold, italics e.t.c, and in Tiptaps favourites words “Don’t bend it, extend it”, you can extend and add other items that you want like heading, bullet list, ordered list and so much more like we did below.&lt;/p&gt;

&lt;p&gt;Content is the description we got from the input field a.k.a what the user types into the input field. You can also style the editor using editor props like we did below (so get your styling wizardry up or you can use my style below - easy and simple). The next is onUpdate, this simply helps us update what the user sees when they click on the button, this calls onChange and takes whatever is in the editor and convert it to HTML.&lt;/p&gt;

&lt;p&gt;Are you still with me? I hope so, I told you that Tiptap gives you the ability to customise your Rich Text Editor easily so if you want to add Heading, you can configure it to the extensions like we did below, HTML attributes helps you to add class and to specify the levels you want (H1, H2 - I just used H2 here to simplify this).&lt;/p&gt;

&lt;p&gt;We also specify in the below example, if the editor does not exist, we should not render the toolbar therefore returning “null”&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const Tiptap = ({ editorContent, onChange }: TipTapProps) =&amp;gt; {
  const editor = useEditor({
    extensions: [
      StarterKit,
      Heading.configure({
        HTMLAttributes: {
          class: "text-xl font-bold capitalize",
          levels: [2],
        },
      }),
      ListItem,
      BulletList.configure({
        HTMLAttributes: {
          class: "list-disc ml-2",
        },
      }),
      OrderedList.configure({
        HTMLAttributes: {
          class: "list-decimal ml-2",
        },
      }),
    ],
    immediatelyRender: false,
    editorProps: {
      attributes: {
        class:
          "shadow appearance-none min-h-[150px] border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline",
      },
    },
    content: editorContent,
    onUpdate: ({ editor }) =&amp;gt; {
      onChange(editor.getHTML());
    },
  });
  if (!editor) {
    return null;
  }
return (TODO)
export default Tiptap;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  STEP 3:
&lt;/h4&gt;

&lt;p&gt;Now, let’s focus on what we would return.&lt;/p&gt;

&lt;p&gt;Using the bold button as an example, if the button is clicked, we are telling the editor to use the chain method to confirm the focus to be on the bold button and to make the selected text bold and run it. &lt;/p&gt;

&lt;p&gt;Now, lets add an active class to bold to give it a grey background when Active and remove the grey background when Inactive and don’t forget to specify that this is a button type.&lt;/p&gt;

&lt;p&gt;Then add the editor to the EditorContent.&lt;/p&gt;

&lt;p&gt;That’s the logic that can be applied to others.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; return (
    &amp;lt;div className="flex flex-col justify-stretch min-h-[200px] border rounded border-b-0"&amp;gt;
      &amp;lt;div className="flex items-center gap-2 mb-2"&amp;gt;
        {/* Bold Button */}
        &amp;lt;button
          type="button"
          onClick={() =&amp;gt; editor.chain().focus().toggleBold().run()}
          className={`p-2 rounded ${
            editor.isActive("bold") ? "bg-gray-200" : ""
          }`}
          title="Bold (Ctrl+B)"
        &amp;gt;
          &amp;lt;b&amp;gt;B&amp;lt;/b&amp;gt;
        &amp;lt;/button&amp;gt;
        {/* Italic Button */}
        &amp;lt;button
          type="button"
          onClick={() =&amp;gt; editor.chain().focus().toggleItalic().run()}
          className={`p-2 rounded ${
            editor.isActive("italic") ? "bg-gray-200" : ""
          }`}
          title="Italic (Ctrl+I)"
        &amp;gt;
          &amp;lt;i&amp;gt;I&amp;lt;/i&amp;gt;
        &amp;lt;/button&amp;gt;
        {/* Heading */}
        &amp;lt;button
          type="button"
          onClick={() =&amp;gt;
            editor.chain().focus().toggleHeading({ level: 2 }).run()
          }
          className={
            editor.isActive("heading", { level: 2 }) ? "is-active" : ""
          }
        &amp;gt;
          &amp;lt;Image
            src="/heading2.svg"
            alt="Heading 2"
            width={10}
            height={10}
            className="h-4 w-4"
          /&amp;gt;
        &amp;lt;/button&amp;gt;
        {/* Heading */}
        {/* Bullet List Button */}
        &amp;lt;button
          type="button"
          onClick={() =&amp;gt; editor.chain().focus().toggleBulletList().run()}
          className={`p-2 rounded ${
            editor.isActive("bulletList") ? "bg-gray-200" : ""
          }`}
          title="Bullet List"
        &amp;gt;
          &amp;lt;Image
            src="/bullet-list.svg"
            alt="Bullet list"
            width={10}
            height={10}
            className="h-4 w-4"
          /&amp;gt;
        &amp;lt;/button&amp;gt;
        {/* Ordered List Button */}
        &amp;lt;button
          type="button"
          onClick={() =&amp;gt; editor.chain().focus().toggleOrderedList().run()}
          className={`p-2 rounded ${
            editor.isActive("orderedList") ? "bg-gray-200" : ""
          }`}
          title="Ordered List"
        &amp;gt;
          &amp;lt;Image
            src="/numbered-list.svg"
            alt="Numberedlist"
            width={10}
            height={10}
            className="h-4 w-4"
          /&amp;gt;
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
      {/* Editor Content */}
      &amp;lt;EditorContent editor={editor} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Step 4: Extend Your Editor with More Extensions
&lt;/h4&gt;

&lt;p&gt;Some extensions, such as Heading, BulletList, and others, require installation to be used. Be sure to check the documentation for detailed instructions on how to install and integrate them. Below are some useful links:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tiptap.dev/docs/editor/extensions/nodes/heading" rel="noopener noreferrer"&gt; Heading Extension Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tiptap.dev/docs/editor/extensions/nodes/bullet-list" rel="noopener noreferrer"&gt; BulletList Extension Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tiptap.dev/docs/editor/extensions/nodes/list-item" rel="noopener noreferrer"&gt;ListItem Extension Documentation&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Additional Notes:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Heading Configuration: Simply installing and using the Heading extension won’t work out of the box. You’ll need to configure it and apply the necessary styles, as demonstrated in the example.&lt;/li&gt;
&lt;li&gt;Tailwind CSS Integration: If you're using Heading, BulletList, and OrderedList with Tailwind CSS, you need to install the Tailwind Typography plugin. To make the listings and headings work, add the below configuration to your Extensions array (look at the full code example if you are confused). This helps to ensure that these elements render correctly. Here's a useful &lt;a href="https://stackoverflow.com/questions/78057571/why-isnt-the-headings-and-lists-working-in-tiptap/78103317#78103317" rel="noopener noreferrer"&gt;StackOverflow link&lt;/a&gt; that can guide you through the process.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Heading.configure({
        HTMLAttributes: {
          class: "text-xl font-bold capitalize",
          levels: [2],
        },
      }),
      BulletList.configure({
        HTMLAttributes: {
          class: "list-disc ml-2",
        },
      }),
      OrderedList.configure({
        HTMLAttributes: {
          class: "list-decimal ml-2",
        },
      }),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here’s the full working code, feel free to customise and let me know what you think via the comment section.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useEditor, EditorContent } from "@tiptap/react";&lt;br&gt;
import StarterKit from "@tiptap/starter-kit";&lt;br&gt;
import BulletList from "@tiptap/extension-bullet-list";&lt;br&gt;
import ListItem from "@tiptap/extension-list-item";&lt;br&gt;
import OrderedList from "@tiptap/extension-ordered-list";&lt;br&gt;
import Heading from "@tiptap/extension-heading";&lt;br&gt;
import Image from "next/image";&lt;br&gt;
interface TipTapProps {&lt;br&gt;
  editorContent: string;&lt;br&gt;
  onChange: (content: string) =&amp;gt; void;&lt;br&gt;
}&lt;br&gt;
const Tiptap = ({ editorContent, onChange }: TipTapProps) =&amp;gt; {&lt;br&gt;
  const editor = useEditor({&lt;br&gt;
    extensions: [&lt;br&gt;
      StarterKit,&lt;br&gt;
      ListItem,&lt;br&gt;
      Heading.configure({&lt;br&gt;
        HTMLAttributes: {&lt;br&gt;
          class: "text-xl font-bold capitalize",&lt;br&gt;
          levels: [2],&lt;br&gt;
        },&lt;br&gt;
      }),&lt;br&gt;
      BulletList.configure({&lt;br&gt;
        HTMLAttributes: {&lt;br&gt;
          class: "list-disc ml-2",&lt;br&gt;
        },&lt;br&gt;
      }),&lt;br&gt;
      OrderedList.configure({&lt;br&gt;
        HTMLAttributes: {&lt;br&gt;
          class: "list-decimal ml-2",&lt;br&gt;
        },&lt;br&gt;
      }),&lt;br&gt;
    ],&lt;br&gt;
    immediatelyRender: false,&lt;br&gt;
    editorProps: {&lt;br&gt;
      attributes: {&lt;br&gt;
        class:&lt;br&gt;
          "shadow appearance-none min-h-[150px] border rounded w-full py-2 px-3 bg-white text-black text-sm mt-0 md:mt-3 leading-tight focus:outline-none focus:shadow-outline",&lt;br&gt;
      },&lt;br&gt;
    },&lt;br&gt;
    content: editorContent,&lt;br&gt;
    onUpdate: ({ editor }) =&amp;gt; {&lt;br&gt;
      onChange(editor.getHTML());&lt;br&gt;
    },&lt;br&gt;
  });&lt;br&gt;
  if (!editor) {&lt;br&gt;
    return null;&lt;br&gt;
  }&lt;br&gt;
  return (&lt;br&gt;
    &amp;lt;div className="flex flex-col justify-stretch min-h-[200px] border rounded border-b-0"&amp;gt;&lt;br&gt;
      &amp;lt;div className="flex items-center gap-2 mb-2"&amp;gt;&lt;br&gt;
        {/* Bold Button &lt;em&gt;/}&lt;br&gt;
        &amp;lt;button&lt;br&gt;
          type="button"&lt;br&gt;
          onClick={() =&amp;gt; editor.chain().focus().toggleBold().run()}&lt;br&gt;
          className={&lt;code&gt;p-2 rounded ${&lt;br&gt;
            editor.isActive("bold") ? "bg-gray-200" : ""&lt;br&gt;
          }&lt;/code&gt;}&lt;br&gt;
          title="Bold (Ctrl+B)"&lt;br&gt;
        &amp;gt;&lt;br&gt;
          &amp;lt;b&amp;gt;B&amp;lt;/b&amp;gt;&lt;br&gt;
        &amp;lt;/button&amp;gt;&lt;br&gt;
        {/&lt;/em&gt; Italic Button &lt;em&gt;/}&lt;br&gt;
        &amp;lt;button&lt;br&gt;
          type="button"&lt;br&gt;
          onClick={() =&amp;gt; editor.chain().focus().toggleItalic().run()}&lt;br&gt;
          className={&lt;code&gt;p-2 rounded ${&lt;br&gt;
            editor.isActive("italic") ? "bg-gray-200" : ""&lt;br&gt;
          }&lt;/code&gt;}&lt;br&gt;
          title="Italic (Ctrl+I)"&lt;br&gt;
        &amp;gt;&lt;br&gt;
          &amp;lt;i&amp;gt;I&amp;lt;/i&amp;gt;&lt;br&gt;
        &amp;lt;/button&amp;gt;&lt;br&gt;
        {/&lt;/em&gt; Heading &lt;em&gt;/}&lt;br&gt;
        &amp;lt;button&lt;br&gt;
          type="button"&lt;br&gt;
          onClick={() =&amp;gt;&lt;br&gt;
            editor.chain().focus().toggleHeading({ level: 2 }).run()&lt;br&gt;
          }&lt;br&gt;
          className={&lt;br&gt;
            editor.isActive("heading", { level: 2 }) ? "is-active" : ""&lt;br&gt;
          }&lt;br&gt;
        &amp;gt;&lt;br&gt;
          &amp;lt;Image&lt;br&gt;
            src="/heading2.svg"&lt;br&gt;
            alt="Heading 2"&lt;br&gt;
            width={10}&lt;br&gt;
            height={10}&lt;br&gt;
            className="h-4 w-4"&lt;br&gt;
          /&amp;gt;&lt;br&gt;
        &amp;lt;/button&amp;gt;&lt;br&gt;
        {/&lt;/em&gt; Heading &lt;em&gt;/}&lt;br&gt;
        {/&lt;/em&gt; Bullet List Button &lt;em&gt;/}&lt;br&gt;
        &amp;lt;button&lt;br&gt;
          type="button"&lt;br&gt;
          onClick={() =&amp;gt; editor.chain().focus().toggleBulletList().run()}&lt;br&gt;
          className={&lt;code&gt;p-2 rounded ${&lt;br&gt;
            editor.isActive("bulletList") ? "bg-gray-200" : ""&lt;br&gt;
          }&lt;/code&gt;}&lt;br&gt;
          title="Bullet List"&lt;br&gt;
        &amp;gt;&lt;br&gt;
          &amp;lt;Image&lt;br&gt;
            src="/bullet-list.svg"&lt;br&gt;
            alt="Bullet list"&lt;br&gt;
            width={10}&lt;br&gt;
            height={10}&lt;br&gt;
            className="h-4 w-4"&lt;br&gt;
          /&amp;gt;&lt;br&gt;
        &amp;lt;/button&amp;gt;&lt;br&gt;
        {/&lt;/em&gt; Ordered List Button &lt;em&gt;/}&lt;br&gt;
        &amp;lt;button&lt;br&gt;
          type="button"&lt;br&gt;
          onClick={() =&amp;gt; editor.chain().focus().toggleOrderedList().run()}&lt;br&gt;
          className={&lt;code&gt;p-2 rounded ${&lt;br&gt;
            editor.isActive("orderedList") ? "bg-gray-200" : ""&lt;br&gt;
          }&lt;/code&gt;}&lt;br&gt;
          title="Ordered List"&lt;br&gt;
        &amp;gt;&lt;br&gt;
          &amp;lt;Image&lt;br&gt;
            src="/numbered-list.svg"&lt;br&gt;
            alt="Numberedlist"&lt;br&gt;
            width={10}&lt;br&gt;
            height={10}&lt;br&gt;
            className="h-4 w-4"&lt;br&gt;
          /&amp;gt;&lt;br&gt;
        &amp;lt;/button&amp;gt;&lt;br&gt;
      &amp;lt;/div&amp;gt;&lt;br&gt;
      {/&lt;/em&gt; Editor Content */}&lt;br&gt;
      &amp;lt;EditorContent editor={editor} /&amp;gt;&lt;br&gt;
    &amp;lt;/div&amp;gt;&lt;br&gt;
  );&lt;br&gt;
};&lt;br&gt;
export default Tiptap;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Wrapping Up:&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Now that you’ve set up and customized your Tiptap Rich Text Editor, feel free to explore more extensions and adjust them to your needs. This guide provides a basic understanding, but the possibilities are endless with Tiptap. Whether you want to add custom nodes, marks, or even your own extensions, the documentation is your best friend.&lt;/p&gt;

&lt;p&gt;If you have any questions, feel free to drop them in the comments. I hope this guide was helpful in getting you started with Tiptap in your Next.js and Tailwind CSS project. Happy coding!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>tailwindcss</category>
      <category>react</category>
    </item>
  </channel>
</rss>
