Syntax Highlighter

Develop a client component for a syntax-highlighting code editor.


Environment

Use the environment you are most comfortable with. Because the React docs site suggests using a full-stack React framework, these challenges use the most popular of these frameworks, Next.js. You can run

npx create-next-app@latest

from your terminal to set up a new Next.js project, then use npm run dev to develop locally and inspect the DOM from your browser when debugging. I opt to use App Router, made stable in Next.js 13.4 over the Pages Router. I also use tailwindcss for simple inline styling.

Specification

Develop a client component for a syntax-highlighting code editor. The primary goal is to create an intuitive interface allowing users to input code, select a programming language, and view the syntax-highlighted code in real-time.

The component should consist of two main sections: a textarea for code input and a preformatted block for displaying the highlighted code. Utilize the Prism library to achieve syntax highlighting, ensuring that the code is re-highlighted whenever the user modifies the input or changes the selected programming language. Implement a dropdown menu for language selection, offering options for HTML, JavaScript, and CSS.

Focus on a clean and simple design. The default language upon initialization should be JavaScript.

Here are a few images of the finished application:

The component after entering some code
The component after entering some code
The component after opening the language-selection dropdown
The component after opening the language-selection dropdown

Starter Code

Below is a small skeleton of the application to get you started.

app/page.tsx

import CodeReader from './CodeReader'

export default function Home() {
  return (
    <div className='flex justify-center h-screen w-screen pt-10'>
      <CodeReader/>
    </div>
  )
}

app/CodeReader.tsx

'use client'

import Prism from 'prismjs';
import 'prismjs/themes/prism.css'

const LANGUAGES = ['html', 'javascript', 'css'];
export type Language = typeof LANGUAGES[number];

export default function CodeReader(){
  // YOUR CODE HERE
}

Solution

Below is the entire solution code for the component followed by a short explanation of how it works.

app/CodeReader.tsx

'use client'

import { useEffect, useState } from "react"
import Prism from 'prismjs';
import 'prismjs/themes/prism.css'

const LANGUAGES = ['html', 'javascript', 'css'];
export type Language = typeof LANGUAGES[number];

export default function CodeReader(){
  const [text, setText] = useState('')
  const [language, setLang] = useState<Language>('javascript')

  useEffect(() => {
    Prism.highlightAll()
  }, [text, language])

  return (
    <div className="w-2/4 h-3/4">
      <div className="relative h-1/2">
        <textarea className="w-full h-full" value={text} onChange={(e) => setText(e.target.value)}></textarea>
      </div>
      <div className="relative h-1/2">
        <pre className={`h-full language-${language}`}>
          <code className={`language-${language}`}>
            {text}
          </code>
        </pre>
        <select value={language} onChange={(e) => setLang(e.target.value)} className="absolute top-2 right-2">
          {LANGUAGES.map((lang) => <option value={lang}>{lang}</option>)}
        </select>
      </div>
    </div>
  )
}

The component begins with import statements, bringing in necessary dependencies. useEffect and useState are imported from React for managing side effects and state, respectively. Prism is imported for syntax highlighting, and the associated styles from 'prismjs/themes/prism.css' are included.

A constant array LANGUAGES is defined, containing three string values: 'html', 'javascript', and 'css'. A TypeScript type Language is created using typeof LANGUAGES[number], ensuring that the type can only be one of the values in the LANGUAGES array.

The main CodeReader component is defined as a functional component. Two state variables, text and language, are initialized using the useState hook. The text state holds the content of the code input, while the language state represents the selected programming language for syntax highlighting (defaulted to 'javascript').

The useEffect hook is used to trigger Prism's highlightAll function whenever there are changes in the text or language state variables. This ensures that the code input is syntax-highlighted in real-time based on the selected programming language.

The code editor is divided into two sections using nested <div> elements. The first section contains a textarea for code input. The value of the textarea is controlled by the text state variable, and the onChange event updates the text state.

The second section contains a pre element for displaying the syntax-highlighted code. The language state is used to dynamically set the class for Prism highlighting. Additionally, a select element is included to allow users to choose the programming language. The value and onChange attributes are used to manage the language state.


input
useState
useEffect
Next.js