It's great that we can specify our own custom components, but they seem to be Uncontrolled. I would like to be able to pass state and methods down to custom components - is there a way to do this?
An example use case - I want to add a button in the Toolbar which will modify some of the events on the calendar.
There's nothing stopping you from doing that! You'd just have to pass down a function to the toolbar that updates the state where the events live.
@tobiasandersen how could I do this? I was under the impression it couldn't be done since we're only passing the class to components.
Thanks in advance!
you can pass Componets, and functions are valid components (as well as classes)!
@wasabigeek here's one way to do it: https://codesandbox.io/s/kw645vwm27
@jquense how would you feel about me making an issue template, with a basic codesandbox that people can use for reproducing bugs etc.?
@tobiasandersen how would you do it with a Class?
I've extended the default Toolbar so I can still use the regular methods (e.g. this.navigate), but can't figure out how to pass a method from it's parent.
Example below: how could I pass refreshEvents method from <Calendar/> to <CustomToolbar/>?
(Apologies in advance for my noobness - I can't seem to find anything on the topic online, probably don't know the right search terms)
class CustomToolbar extends Toolbar {
...
render() {
...
return (
<div className='rbc-toolbar'>
<ButtonGroup className="mr-2">
<Button onClick={this.navigate.bind(null, navigate.PREVIOUS)}>
<i className="fa fa-chevron-left"></i>
</Button>
<Button onClick={this.navigate.bind(null, navigate.NEXT)}>
<i className="fa fa-chevron-right"></i>
</Button>
</ButtonGroup>
...
<Button onClick={() => this.props.refreshEvents()}>Refresh Events</Button>
</div>
);
}
}
class Calendar extends React.Component {
constructor(props) {
super(props);
this.state = {
events: [],
}
this.refreshEvents = this.refreshEvents.bind(this);
}
refreshEvents() {
...
this.setState({events: events});
}
handleNavigate(date, view, action) {
this.refreshEvents();
}
render() {
return (
<BigCalendar
events={this.state.events}
views={['month', 'week']}
components={{toolbar: CustomToolbar}}
onNavigate={this.handleNavigate}
/>
);
}
}
Ok this seems to work for me, encapsulate the class in another function:
const CustomToolbar = ({refreshEvents}) => {
return class BaseToolbar extends Toolbar { ... }
}
class Calendar extends React.Component {
...
render() {
return (
<BigCalendar
...
components={{toolbar: CustomToolbar({this.refreshEvents})}}
/>
);
}
}
If there's a better way to do it I'd love to know - otherwise, thanks for the example!
Hello, i tried to use custom toolba as you showed before, but I get this warning:
Warning: Failed prop type: You have provided a
viewprop toCalendarwithout anonViewhandler. This will render a read-only field. If the field should be mutable usedefaultView. Otherwise, setonView
Tried to search everywhere for this problem, but i couldn't find anything to help me. I've tried to add 'onView' prop to BigCalendar component, but it doesn't work.
`const CustomToolbar = (courses) => {
return class BaseToolbar extends Toolbar {
render() {
console.log(courses);
let {messages, label} = this.props;
return (
<div className="rbc-toolbar">
<span className="rbc-btn-group">
<button
type="button"
onClick={this.navigate.bind(null, 'TODAY')}
>
{messages.today}
</button>
<button
type="button"
onClick={this.navigate.bind(null, 'PREV')}
>
{messages.previous}
</button>
<button
type="button"
onClick={this.navigate.bind(null, 'NEXT')}
>
{messages.next}
</button>
</span>
<span className="rbc-toolbar-label">{label}</span>
<span className="rbc-btn-group">
<Input type="select" name="select" id="exampleSelect">
<option>Kaunas | Pavasario semestras 2018</option>
<option>Vilnius | Pavasario semestras 2018</option>
<option>艩iauliai | Pavasario semestras 2018</option>
</Input>
{this.viewNamesGroup(messages)}
</span>
</div>
);
}
navigate = (action) => {
this.props.onNavigate(action)
};
view = (view) => {
this.props.onViewChange(view)
};
viewNamesGroup(messages) {
let viewNames = this.props.views;
const view = this.props.view;
if (viewNames.length > 1) {
return viewNames.map(name => (
<button
type="button"
key={name}
className={view === name ? 'rbc-active' : ''}
onClick={this.view.bind(null, name)}
>
{messages[name]}
</button>
))
}
};
}
};`
@DresDasLT could you edit to include <Calendar/>'s render() code (where <BigCalendar /> is)? It seems like that's where the issue is occuring.
`
culture='lt'
popup events={events}
step={60}
defaultDate={new Date()}
views={["month", "week", "day", "agenda"]}
view={this.state.currentView}
onSelecting={() => false}
onView={(view) => {
this.setState({currentView: view});
}}
eventPropGetter={
(event, start, end, isSelected) => {
let newStyle = {
color: 'white',
backgroundColor: event.category.color
};
return {
className: "",
style: newStyle
};
}
}
messages={
{
'today': '艩iandien',
'previous': 'Atgal',
'next': 'Kitas',
'month': 'M臈nuo',
'week': 'Savait臈',
'day': 'Diena',
'showMore': total => `+${total} 沤i奴r臈ti daugiau`
}
}
components={{
toolbar: CustomToolbar(courses),
month: {
event: CustomMonthEvent
},
week: {
event: CustomWeekEvent
},
}}
onSelectSlot={s => this.toggle(s)}
/>`
There is one problem, i dont' use <Calendar /> component anywhere.
@wasabigeek Can you post a link to your full imports for your example? I am only trying to create a custom toolbar and can't access the original methods, eg this.navigate? I'm also using typescript so I really could use a working example in jsx?
@GavinThomas1192 sorry for the late response, this is my import statement import Toolbar from 'react-big-calendar/lib/Toolbar';. I manually inspect node_modules/react-big-calendar to find out where the needed file is.
If you don't want to do class inheritance (which admittedly, is not recommended in React), I suggest using the source as a base (i.e. copy it and edit): https://github.com/intljusticemission/react-big-calendar/blob/master/src/Toolbar.js. this.navigate is simply a prop that is passed to the component as this.props.onNavigate.
Hey @wasabigeek , thanks for getting back to me! I am using typescript and the issue was that their types were incorrect and I was calling this.props.navigate(date, PREV) and it should have been reversed. Heres my custom header for reference, since this single post was super important for myself when trying to create a custom header.
import React from 'react'
import { Navigate } from 'react-big-calendar'
import Icon from 'app/common/components/Icon'
const styles = require('./CustomToolbar.scss')
`
interface Props {
onNavigate: (action: Navigate) => void
date: string | Date
label: string
}
export default class CustomToolbar extends React.Component
prevMonthClick = () => {
this.props.onNavigate('PREV')
}
nextMonthClick = () => {
this.props.onNavigate('NEXT')
}
render() {
const { label } = this.props
return (
<div className={styles.viewOptionsContainer}>
<span className="rbc-btn-group">
<button>Month</button>
<button>Day</button>
<button>Week</button>
</span>
<button className="btn btn-back">
<Icon icon="R" />
</button>
<button className="btn btn-back">
<Icon icon="meet_now" />
</button>
</div>
</div>
)
}
}
Hi @wasabigeek, this answer is long time closed but i still cannot figure the syntax to pass the prop to my imported custom class component.
Here's a short version of what I'm trying to do, but it fails:
import React, {Component} from 'react'
import Calendar from 'react-big-calendar'
// my External custom components
import CustomToolbar from './components/CustomToolbar'
class App extends Component {
// encapsulating the class component, not sure if ok
CustomToolbarCapsule = (something) => {
const instance = new CustomToolbar(something)
return instance.render()
}
render() {
const something = this.state.something
const components = {
toolbar: this.CustomToolbarCapsule({something})
}
return (<Calendar components={components} />)
}
For future reference, I ended up doing this, but I really don't think it's the right way.
Never the less, it works:
import React, {Component} from 'react'
import Calendar from 'react-big-calendar'
// my external custom components
import CustomToolbar from './components/CustomToolbar'
class App extends Component {
// encapsulating the class component, not sure if ok
CustomToolbarCapsule = (props) => {
return class CustomToolbarInstance extends CustomToolbar {
static defaultProps = { ...props }
}
}
render() {
const something = this.state.something
const components = {
toolbar: this.CustomToolbarCapsule({something})
}
return (<Calendar components={components} />)
}
If anybody could advise for a better solution, please let me know.
@elgandoz I don't really know the terminology or the best practices, but if you look at https://github.com/intljusticemission/react-big-calendar/blob/master/src/Calendar.js#L888 you'll see that the custom toolbar is declared in render() via the <... /> syntax. In React, this can either be a function or a class which is somehow resolved or instantiated through React magic.
In your first example, when you declare this.CustomToolbarCapsule(), you are resolving that function and returning the value (admittedly, I don't know what the value is) only. In the second the function is also resolved, but the resolution of that is to return a class, which will work.
There are other ways you can declare it, e.g. in your first example you should be able to keep everything the same and wrap the custom component in an anonymous function:
const components = {
toolbar: () => this.CustomToolbarCapsule({something})
}
Hope that helps!
Thanks for the reply @wasabigeek. I tried your solution but I get the error:
Cannot read property 'messages' of undefined
That's because all the default props of my custom toolbar gets replaced by something. All I wanted was to add a prop on top of the existing ones, basically what I would do calling
<CustomToolbar someprop={something} />
The problem is that I cannot use that syntax when declaring the calendar components, and if my class is in a different file (import CustomToolbar from './components/CustomToolbar'), I can't do it as you show in your encapsulate example.
Below you can find a snipper of what's going on in the CustomToolbar class:
// CustomToolbar.js, imported in App.js
import React from 'react'
import Toolbar from 'react-big-calendar/lib/Toolbar'
class CustomToolbar extends Toolbar {
render() {
// someprops is custom, the others are default passed by BigCalendar
const { someprop, onNavigate, label , localizer: { messages } } = this.props
[...]
}
}
In the end, all I want is to pass a prop to an imported class component.
My last example with static defaultProps = { ...props } works but I find it quirky.
@elgandoz, since the de-structuring of message from localiser is throwing the error, maybe you can trace that? What is the localizer that is being passed in etc.
I realise we missed passing the props down to the custom toolbar, that could be the reason (I think this would be the syntax): toolbar: props => this.CustomToolbarCapsule({ something, ...props })
@wasabigeek That could work, but how do I get the default props passed to the toolbar (or to any custom component)?
In this case onNavigate, label , localizer come from big calendar itself.
This issue shouldn't be closed. The problem is as @elgandoz says, the props passed to Custom components this way are overwriting existing components props. Is there a way to keep them both?
I want to get answer to @mdjuric-itk question too.
I had a similar problem and i fixed it by using a function when adding my custom component
components={{event: (props) => <Event {...props} customProp={youCustomProp} />}}
This will allow you to keep the default props and add your own custom props
Guys all your suggestions neglect the fact that it WONT RE-RENDER if the props change. This is bad practice and I am puzzled without they haven't fixed it yet.
I had a similar problem and i fixed it by using a function when adding my custom component
components={{event: (props) => <Event {...props} customProp={youCustomProp} />}}This will allow you to keep the default props and add your own custom props
After hours of scouring all these git issues and PRs, this was the only thing that worked for me. This also allowed me to pass functions as props as well, which is the main thing I was trying to do.
Most helpful comment
Ok this seems to work for me, encapsulate the class in another function:
If there's a better way to do it I'd love to know - otherwise, thanks for the example!