javascripthtmlreactjsdatepickerpikaday

Trigger React Pikaday calendar from an icon onClick


I have an app that uses React Pikaday, which is a JS calendar plugin that adds a popup calendar and is bound to an HTML <input> tag. Since it's in React, the wrapper component is called <DatePicker>. Source code

I also need to add a little FontAwesome calendar icon to the input box. But because the <DatePicker> is an Date input field, I can't add the calendar icon as a text value. Currently I have the <i> icon positioned after the <DatePicker> component and then padding applied to have it move back slightly.

My render() function looks like

<DatePicker>
    format="MM/DD/YYYY"
    value={this.state.date}
    onOpen={this.handleCalendarOperations}
    onDraw={this.handleCalendarOperations}
    onChange={this.handleCalendarOperations}
</DatePicker>

<i className="fa fa-calendar" id="CalculatorCalendar" title="Toggle Calendar"></i>

However, it is also necessary for a click action on the icon to open the calendar. But the calendar functionality is bound to the input field, so I have no way of accessing the lower level functionality. I found a post on SO, Triggering Pikaday date picker script on input field and icon, and the solution is basically to add an ID value to the <i> tag, which I did, and then to use the following

document.getElementById("CalculatorCalendar").addEventListener("click", function(){
    picker.show();
});

to trigger the Pikaday calendar. But, the problem is this is React. And the linked solution requires the instantiation of a new Pikaday instance in vanilla JS. That's what the picker.show() is from. The solution uses a new Pikaday() instance and calls picker.show() on it.

I need to do what this solution does, but in React. What would be the "React way" to open my DatePicker calendar by clicking the associated icon? Thanks.


Solution

  • Turns out using the third party component wrapper around Pikaday hid too many of the abstractions that I needed access to, like the Pikaday .show() function. So I wound up refactoring my code to use the vanilla JS implementation of Pikaday, and then modified the linked solution from my initial post so it would work with React.createRef().

    constructor(props){
        this.fieldRef = React.createRef(),
    }
    
    componentDidMount(){
        this.pikaday = new Pikaday({
                field: this.fieldRef.current,
                format: 'MM/DD/YYYY',
                onOpen: this.handleCalendar,
                onDraw: this.handleCalendar,
                onChange: this.changeColors
        });
    }
    
    render(){
        return(
            <input 
                type="text" 
                id="datepicker" 
                ref={this.fieldRef} 
                value={this.state.date}
            ></input>
            <i class="fa fa-calendar" onClick={() => this.pikaday.show()} />
        );
    }
    

    The fieldRef is what Pikaday uses to reference the element that it binds to, so you initialize the reference in the app constructor, and add this.fieldRef as a ref attribute to the <input> tag. I then instantiated the Pikaday calendar in componentDidMount() so the render operation will have already completed, allowing the Pikaday() constructor to have a ref field ready to bind to. And in the Pikaday constructor's parameters, link the field parameter with this.fieldRef.current so Pikaday binds to the current instance of the ref.