tiptap

Tiptap gives you full control about every single aspect of your text editor experience. It’s customizable, comes with a ton of extensions, is open source and has an extensive documentation. Join our welcoming community and start building cool things!

examples

Troubleshooting

hot reloading and initial content

I wanted a clever way to load in a default Tiptap template if no data was found. Tiptap was only taking the inital state of the react useState until I dropped this piece in

  useEffect(() => {
    if (editor && !editor.isDestroyed) {
      editor.chain().focus().setContent(defTemplate).run();
    }
  }, [defTemplate, editor]);

Here is the whole script for context

import React, {useState, useEffect} from 'react'

import Document from '@tiptap/extension-document'
import Gapcursor from '@tiptap/extension-gapcursor'
import Paragraph from '@tiptap/extension-paragraph'
import Table from '@tiptap/extension-table'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
import TableRow from '@tiptap/extension-table-row'
import Text from '@tiptap/extension-text'
import History from '@tiptap/extension-history'
import { EditorContent, useEditor } from '@tiptap/react'
import { StyledTableEditor } from '../styles/TableEditor.styled';


export const TiptapTable = ({id, section, handleSave}) => {

  const [currTimeStart, setTimeStart] = useState()
  const [defTemplate, setdefTemplate] = useState('<p>loading<p>')
  const [isLoaded, setIsLoaded] = useState(false)


  const editor = useEditor({
    extensions: [
      History,
      Document,
      Paragraph,
      Text,
      Gapcursor,
      Table.configure({
        resizable: true,
      }),
      TableRow.extend({
        content: '(tableCell | tableHeader)*',
      }),
      TableHeader,
      TableCell,
    ],
    // I wish it was this easy
    content: (section.data) ? section.data : defTemplate,

  }, [section])

  const  pickTemplate = async (name) => {
    try{
	// grab from 'public' folder
      const res = await fetch(`/templates/${name}.json`,{
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      });
			const data = await res.json()
      
      console.log('data, ', data);
      setIsLoaded(true)
      setdefTemplate(data)

    } catch (err){
      console.warn('template error: ', err);
    }
  }


  useEffect(() => {
    setTimeStart(section.timeStart)

    if(!section.data) pickTemplate(section.header).catch(console.warn)
 
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [id, section, isLoaded])

  useEffect(() => {
    if (editor && !editor.isDestroyed) {

      if(section.data) editor.chain().focus().setContent(section.data).run()

      if(!section.data) editor.chain().focus().setContent(defTemplate).run()

      setIsLoaded(true)
      
    }
  }, [section, defTemplate, editor]);


  
  if (!editor) {
    return null
  }

  return isLoaded ? (<>

    <StyledTableEditor>
        ...
    </StyledTableEditor>
  </>) 
  : null
}

reveal editor on focus

Needed a slick way to show and hide the editor once the user interacts with a specific section. It can all be done in CSS

.parent-wrapper{
	.toolbar{
	    opacity: 0;
	    z-index: -5;
	    transition: .2s;
	}
	
	&:focus-within{
	    .toolbar{
	      background-color: #00000082;
	      opacity: 1;
	      z-index: 5;
	    }
	}
}

Credits