question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Event-data conventions!?

See original GitHub issue

FullCalendar has not established a clear convention for React users (nor Vue/Angular users) for providing event data to their calendars, which is a little bit ridiculous. This has led to many improvised approaches which often have shortcomings like double-rendering events or being at odds with Reactā€™s declarative nature.

Iā€™m proposing two patterns that I will eventually add to the docs. They require some additional API tweaks (see proposals below), but hopefully they are immediately readable as pseudocode. The sample code is based off the v5 api, which is currently in beta. The sample code displays a simple month-view calendar with events loaded from a server and also displays a plain text list of events below, along with an ā€œadd eventā€ button.

Please comment on this issue or at least give a šŸ‘ if you approve.

Technique 1: FullCalendar owns the event data

import * as React from 'react'
import FullCalendar from '@fullcalendar/react'

//
// FullCalendar OWNS THE EVENT DATA, meaning:
// FullCalendar requests the data from a feed,
// FullCalendar manipulates the data for drag-n-drop/resize.
//
class DemoApp extends React.Component {

  calendarRef = React.createRef()
  state = {
    currentRefinedEvents: [] // events parsed by FullCalendar
  }
 
  render() {
    return (
      <div>
        <FullCalendar
          ref={this.calendarRef}
          initialView='dayGridMonth'
          editable={true}
          events='/events/query' // a remote JSON feed that will receive start/end params
          eventsDidUpdate={this.handleEventsDidUpdate} // eventsDidUpdate not-yet-implemented (see proposal)
        />
        <h2>Current Event List:</h2>
        <ul>
          {this.state.currentRefinedEvents.map((refinedEvent) => (
            <li>{refinedEvent.title} - {refinedEvent.start.toISOString()}</li>
          ))}
        </ul>
        <button onClick={this.handleAddEventToday}>
          add an event today!
        </button>
      </div>
    )
  }

  handleEventsDidUpdate = (arg) => {
    if (arg.addedEvent) { // an EventApi object
      request('/events/add', JSON.stringify(arg.addedEvent)) // requires EventApi::toJSON (see proposal)

    } else if (arg.updatedEvent) { // an EventApi object
      request('/events/update', JSON.stringify(arg.updatedEvent)) // requires EventApi::toJSON (see proposal)

    } else if (arg.removedEvent) { // an EventApi object
      request('/events/remove', { id: arg.removedEvent.id })
    }
    // There's a final condition for when events are received from the server
    // (the arg.loadedEvents array will be populated)
    //
    // For any of these scenarios (add/update/delete/loaded),
    // give DemoApp the parsed event data...
    this.setState({ currentRefinedEvents: arg.allEvents })
  }

  handleAddEventToday = () => {
    let calendarApi = this.calendarRef.current.getApi()
    calendarApi.addEvent({
      title: 'event that happens today',
      start: new Date()
    })
  }

}

Technique 2: DemoApp owns the event data

import * as React from 'react'
import FullCalendar from '@fullcalendar/react'
import { requestJson, request } from './utils' // please imagine these

//
// DemoApp OWNS THE EVENT DATA, meaning:
// DemoApp requests and parses the data data from a feed,
// DemoApp manipulates the data for drag-n-drop/resize.
//
class DemoApp extends React.Component {

  state = {
    currentRefinedEventMap: {} // an {id:event} hash of parsed events
  }

  render() {
    return (
      <div>
        <FullCalendar
          initialView='dayGridMonth'
          editable={true}
          datesDidUpdate={this.handleDatesDidUpdate}
          events={this.state.currentRefinedEventMap} // accepting a hash not-yet-implemented (see proposal)
          eventsDidUpdate={this.handleEventsDidUpdate} // eventsDidUpdate not-yet-implemented (see proposal)
        />
        <h2>Current Event List:</h2>
        <ul>
          {Object.values(this.state.currentRefinedEvents).map((refinedEvent) => (
            <li>{refinedEvent.title} - {refinedEvent.start.toISOString()}</li>
          ))}
        </ul>
        <button onClick={this.handleAddEventToday}>
          add an event today!
        </button>
      </div>
    )
  }

  // called when the calendar's data are initially set, or when they change
  handleDatesDidUpdate = (arg) => {
    requestJson('/events/query', {
      start: arg.view.activeRange.start.toISOString(),
      end: arg.view.activeRange.end.toISOString()
    }).then((rawEvents) => {
      this.setState({
        currentRefinedEventMap: buildRefinedEventMap(rawEvents)
      })
    })
  }

  handleEventsDidUpdate = (arg) => {
    if (arg.addedEvent) { // an EventApi object
      this.addEvent(arg.addedEvent)

    } else if (arg.updatedEvent) { // an EventApi object
      this.updateEvent(arg.updatedEvent)

    } else if (arg.removedEvent) { // an EventApi object
      this.removeEvent(arg.removedEvent)
    }

    // returning false will prevent FullCalendar from recording these changes internally.
    // this is desirable because our setState call will modify currentRefinedEventMap,
    // which will be passed to FullCalendar anyway.
    return false
  }

  handleAddEventToday = () => {
    this.addEvent(
      parseRawEvent({
        title: 'event that happens today',
        start: new Date()
      })
    )
  }

  addEvent(event) { // event might have been refined by DemoApp OR FullCalendar
    request('/events/add', JSON.stringify(event)) // requires EventApi::toJSON (see proposal)
    this.setState({
      currentRefinedEventMap: addOrUpdateEventMap(
        this.state.currentRefinedEventMap,
        event
      )
    })
  }

  updateEvent(event) { // event might have been refined by DemoApp OR FullCalendar
    request('/events/update', JSON.stringify(event)) // requires EventApi::toJSON (see proposal)
    this.setState({
      currentRefinedEventMap: addOrUpdateEventMap(
        this.state.currentRefinedEventMap,
        event
      )
    })
  }

  removeEvent(event) { // event might have been refined by DemoApp OR FullCalendar
    request('/events/remove', { id: event.id })
    this.setState({
      currentRefinedEventMap: deleteFromEventMap(
        this.state.currentRefinedEventMap,
        event
      )
    })
  }

}

// event data-manipulation methods
// (this logic would probably be in your reducer)
// ----------------------------------------------

function buildRefinedEventMap(rawEvents) {
  let map = {}

  for (let rawEvent of rawEvents) {
    let refinedEvent = parseRawEvent(rawEvent)
    
    if (refinedEvent) {
      map[refinedEvent.id] = refinedEvent
    } else {
      console.warn('Invalid event data', rawEvent)
    }
  }

  return map
}

function parseRawEvent(rawEvent) {
  if (rawEvent.id && rawEvent.start) { // TODO: could be much more stringent
    return {
      id: rawEvent.id,
      title: rawEvent.title,
      start: new Date(rawEvent.start), // parses an ISO8601 date in most browsers
      end: rawEvent.end ? new Date(rawEvent.end) : null // (same)
    }
  }
}

function addOrUpdateEventMap(map, event) {
  return {
    ...map,
    [event.id]: event.toJSON() // returns a plain object. not-yet-implemented (see proposal)
  }
}

function deleteFromEventMap(map, event) {
  let mapCopy = { ...map }
  delete mapCopy[event.id]
  return mapCopy
}

Apendix

Proposed API changes to make the above code possible:

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:11
  • Comments:18 (3 by maintainers)

github_iconTop GitHub Comments

6reactions
Phibedycommented, May 24, 2020

Technique 2 would be really great. In react you donā€™t want some children-component to own data, because you always have to sync it with its parent. It would be a great improvement šŸ‘

5reactions
hanselkecommented, May 26, 2020

pretty sure its better design to handle it via the parent as in use case 2. weā€™d need the data in the parent , and to pass in a function declared in the parent to act on button clicks.

Read more comments on GitHub >

github_iconTop Results From Across the Web

EVENTDATA (Transact-SQL) - SQL Server - Microsoft Learn
This function returns information about server or database events. When an event notification fires, and the specified service broker receivesĀ ...
Read more >
What Are the Components of Event Data? - Amplitude
In this guide, you will learn about the components of event data, the preferred naming convention to define events, the categories of entityĀ ......
Read more >
EVENTdata
Totally parametric and adaptable to each event's unique needs. OnSite Registration. Set the standards high right from the start! Make the registration processĀ ......
Read more >
Safeguarding Your Event's Data - Meetings & Conventions
Who in your organization has access to my event data, and how is access ... Review your organization's information-security standards with aĀ ...
Read more >
Create an event data store for AWS Config configuration items
With an event data store, you can relate non-compliant rules to the users and resources associated with the changes. A configuration item represents...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found