XF 2.2 How to implement reactions in a custom content

FoxSecrets

Active member
Can someone tell how can I implement reactions in a custom content?

I know there are tags xf:react and xf:reactions but I don't know how to use them. Any documentation about it?
 
Last edited:
XF has concepts of content types and content type handlers for implementing generic functionality across content types. I'd recommend looking at a core content type to see how they implement it (profile posts may be a simpler one to get your head around).

The gist of it is you create a handler class (subclassing \XF\Reaction\AbstractHandler and implementing the abstract methods at a minimum, and any others as needed), and register the handler (using the content types page in the control panel, in this case the type will be reaction_handler_class). Then you implement the pertinent entity columns and behaviors, and controller actions. There are behaviors and traits for the entity and controller plugins for controller actions.

For a react button:
HTML:
<xf:react content="{$contentEntity}" link="some/route/to/react/action" list=".some-selector-for-reaction-list" />

For a reaction list:
HTML:
 <xf:reactions content="{$contentEntity}" link="some/route/to/reaction/list" />

It can be a bit intimidating to implement for the first time but mostly straight-forward afterwards. This same pattern is used for a lot of functionality (alerts, the approval queue, attachments, bookmarks, content voting, edit history, the what's new system, inline moderation, moderator logs, the news feed, reports, sitemap generation, statistics, tagging, warnings, etc.). If you have any specific questions or problems feel free to ask.
 
For a react button:
HTML:
<xf:react content="{$contentEntity}" link="some/route/to/react/action" list=".some-selector-for-reaction-list" />
Hey @Jeremy P . It's been very challenging for me, even following the core structure.
What does attribute list stand for? How do I setup this?

Also, I have the object ($contentEntity) but I can't even see the Like button.
 
Last edited:
What does attribute list stand for? How do I setup this?
The list attribute is a CSS selector pointing to the corresponding <xf:reactions> list container. For example:

HTML:
<div class="block>
    <xf:react content="{$contentEntity}" link="some/route/react" list="< .block | .js-reactionsList" />

    <div class="reactionsBar js-reactionsList {{ $album.reactions ? 'is-active' : '' }}">
        <xf:reactions content="{$contentEntity}" link="some/route/reactions" />
    </div>
</div>

Also, I have the object ($contentEntity) but I can't even see the Like button.
It won't show up until you've implemented the handler and set up the entity appropriately (add \XF\Entity\ReactionTrait to your entity, and update your structure to call addReactableStructureElements and include the XF:Reactable behavior).

You also need to add actionReact and actionReactions methods to your controller using the reactions controller plugin.
 
@Jeremy P, still not showing the button.
Below is my code related to this implementation. My content is a kind of 'user profile'.
Am I missing something?

Template:
Code:
<xf:react content="{$profile}" link="xxxxxxxxxx/react" list="< .userProfile | .reactionsListClass" />

Content type handler:
Code:
<?php

namespace xxxxxxxxxxxxxx\Reaction;

use XF\Reaction\AbstractHandler;
use XF\Mvc\Entity\Entity;

class UserProfile extends AbstractHandler
{
  public function reactionsCounted(Entity $entity) { // }

  public function getEntityWith()
  {
    return ['User', 'User.Privacy'];
  }

  public function canReactToContent(Entity $content, &$error = null) { // }

  public function canViewContent(Entity $content, &$error = null) { // }
}

UserProfile Entity:
Code:
<?php
namespace xxxxxxxxxxxxxxxxx;
use xxxxxxxxxxxx

class UserProfile extends Entity
{
  use ReactionTrait;

  public static function getStructure(Structure $structure): Structure
  {
    $structure->table = xxxxxxxxxx
    //
    $structure->columns = [
       //
      'profile_state' => [
        'type' => self::STR, 'default' => 'visible',
        'allowedValues' => ['visible', 'moderated', 'deleted'], 'api' => true
      ],
      'reaction_score' => ['type' => self::UINT, 'default' => 0],
      'reactions' => ['type' => self::JSON_ARRAY, 'default' => []],
      'reaction_users' => ['type' => self::JSON_ARRAY, 'default' => []],
       //
    ];
    $structure->relations = [
      'User' => [
        'entity' => 'XF:User',
        'type' => self::TO_ONE,
        'conditions' => 'user_id',
        'primary' => true
      ],
      'Reactions' => [
        'entity' => 'XF:ReactionContent',
        'type' => self::TO_MANY,
        'conditions' => [
          ['content_type', '=', $structure->contentType],
          ['content_id', '=', '$' . $structure->primaryKey]
        ],
        'key' => 'reaction_user_id',
        'order' => 'reaction_date'
      ]
    ];
    $structure->defaultWith = [];
    $structure->behaviors = [
      'XF:Reactable' => ['stateField' => 'profile_state']
    ];
    $structure->getters['reactions'] = true;
    $structure->getters['reaction_users'] = true;

    static::addReactableStructureElements($structure);

    return $structure;
  }

  //

  public function canReact(&$error = null) { // }

  public function getReactUserId() { // }
}

UserProfile Controller:
Code:
<?php
namespace xxxxxxxxxxxxx;
use xxxxxxxxxxxx

class UserProfile extends AbstractController
{
  public function actionIndex(ParameterBag $params)
  {
    // $profile
  }

  public function actionReact(ParameterBag $params)  { // }

  public function actionReactions(ParameterBag $params) { // }
}
 
I'm getting an error: "The requested page could not be found."
the url is like this: http://localhost:8000/index.php?user-profile/2/react

Code:
  public function actionReact(ParameterBag $params)
  {
    $profile = $this->assertUserProfileExists($params->profile_id);

    /** @var \XF\ControllerPlugin\Reaction $reactionPlugin */
    $reactionPlugin = $this->plugin('XF:Reaction');
    return $reactionPlugin->actionReactSimple($profile, 'user-profile');
  }
 
Does the error show the right controller and action (Code: invalid_action, controller: Some\AddOn:UserProfile, action: React)? If not, check your route definition to ensure the action can be routed appropriately.\

Edit: Actually, be sure to append the ?reaction_id=1 as noted in my message.
 
Here is:
Code:
  public function actionReact(ParameterBag $params)
  {
    $profile = $this->assertUserProfileExists($params->profile_id);

    /** @var \XF\ControllerPlugin\Reaction $reactionPlugin */
    $reactionPlugin = $this->plugin('XF:Reaction');
    return $reactionPlugin->actionReactSimple($profile, 'user-profile');
  }

When I came back to the profiles page, the button suddenly appeared!!
Why this happen if I haven't done anything ? I mean, I just run the url you said, nothing else.

Now when I click the Like button I got the js error below (nothing in server error log)

1711343328055.webp
 
When I came back to the profiles page, the button suddenly appeared!!
Why this happen if I haven't done anything ? I mean, I just run the url you said, nothing else.
No idea, unless the template was stale for some reason.


Now when I click the Like button I got the js error below (nothing in server error log)
That error is because you have an \XF::dump on that action which causes the response to fail to parse.
 
Another issue related to this topic @Jeremy P , can you help me?
After clicking on Like button, I got the Alert on the other user profile, however there is no message.
How can I configure the alert message?

1712890864259.webp
 
Back
Top Bottom