Components
Note: while the component API is still available, it is now recommended to only use it
Using Components
Assuming a component is registered, you can use it with Components.Foo
. For example:
1 | import { Components } from 'meteor/vulcan:core'; |
Registering Components
Vulcan components are all listed in vulcan:core
‘s Components
object. You can add new ones with the ‘registerComponent` method:
1 | import { registerComponent } from 'meteor/vulcan:core'; |
Components & HoCs
To understand how theming works in Vulcan, it’s important to understand how components and higher-order components (HoCs) interact.
A higher-order component’s role is to wrap a regular component to pass it a specific prop (such as a list of posts, the current user, the Router
object, etc.). You can think of HoCs as specialized assistants that each hand the component a tool it needs to do its job.
For example, this is how you’d pass the currentUser
object to the Logo
component:
1 | registerComponent({ name: 'Logo', component: Logo, hocs: [withCurrentUser] }); |
“Delayed” HoCs
There are a few subtle differences between registering a component with registerComponent
and then calling it with Components.Foo
; and the more standard export default Foo
and import Foo from './Foo.jsx'
ES6 syntax.
First, you can only override a component if it’s been registered using registerComponent
. This means that if you’re building any kind of theme or plugin and would like end users to be able to replace specific components, you shouldn’t use import/export.
Second, both techniques also lead to different results when it comes to higher-order components (more on this below). If you write export withCurrentUser(Foo)
, the withCurrentUser
function will be executed immediately, which will trigger an error because the fragment it depends on isn’t properly initialized yet.
On the other hand, registerComponent('Foo', Foo, withCurrentUser)
doesn’t execute the function (note that we write withCurrentUser
and not withCurrentUser()
), delaying execution until app’s initialization.
But what about HoC functions that take arguments? For example if you were to write:
1 | registerComponent({ name: 'PostsList', component: PostsList, hocs: [withMulti(options)] }); |
The withMulti(options)
would be executed immediately, and you would have no way of overriding the options
object later on (a common use case being overriding a fragment).
For that reason, to delay the execution until the start of the app, you can use the following alternative syntax:
1 | registerComponent({ name: 'PostsList', component: PostsList, hocs: [ [withMulti, options] ] }); |
Accessing Raw Components
Going back to our example:
1 | const WrappedComponent = withCurrentUser(MyComponent); |
This would result a new WrappedComponent
component that has MyComponent
as a child. This has the consequence that properties and objects you set on MyComponent
might not exist on WrappedComponent
.
For that reason, Vulcan provides a getRawComponent
utility that lets you access the unwrapped “raw” component, provided said component has been registered with registerComponent
:
1 | MyComponent.foo = "bar"; |
Shortcut Syntax
Throughout the documentation and the example projects you might also see this equivalent registerComponent
shortcut syntax:
1 | registerComponent('ComponentName', Component, hoc1, hoc2, ...); |
The first argument of registerComponent
will be the component’s name, the second the component itself, and any successive arguments will be interpreted as higher-order components and wrapped around the component.
Replacing Components
If you only need to modify a single component, you can simply override it with a new one without having to touch the original package.code
For example, if you wanted to use your own CustomLogo
component you would do:
1 | import { replaceComponent } from 'meteor/vulcan:core'; |
Note that replaceComponent
will preserve any HoCs originally defined using registerComponent
. In other words, in the following example:
1 | registerComponent('Logo', Logo, withCurrentUser, withRouter); |
The CustomLogo
component will also be wrapped with withCurrentUser
and withRouter
.
Once you’ve replaced the Logo
component with your own CustomLogo
, Components.Logo
will now point to CustomLogo
. If you want an easy way to keep track of which components have been customized, you could add a custom
attribute when calling the component as a reminder for yourself:
1 | import { Components } from 'meteor/vulcan:core'; |
Extending Components
Components are generally defined as functional stateless components, unless they contain extra logic (lifecycle methods, event handlers, etc.) in which case they’ll be defined as ES6 classes.
For components defined as ES6 classes, make sure you extend
the original component. This will let you pick and choose which methods you actually need to replace, while inheriting the ones you didn’t specify in your new component.
In order to extend the original, non-wrapped component we use the getRawComponent
method:
1 | class CustomLogo extends getRawComponent('Logo'){ |
Note that using getRawComponent
is also needed because components get registered at runtime. So in our CustomLogo
example, Components.Logo
would not be defined yet.
Alternative Approach
The main purpose behind the components API is to enable extending and replacing components defined in third-party themes and plug-ins. However, if this is not a concern for you, you can use the standard export default Foo
and import Foo from './foo.jsx'
approach without any trouble.