Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for passing children in from Rails #1650

Closed
wants to merge 4 commits into from

Conversation

rangerscience
Copy link

@rangerscience rangerscience commented Oct 1, 2024

Summary

Allows you to do:
(rails template)

<%= react_component "MyComponent" do %>
  <%= render "events/form", event: Event.new %>
<% end %>

(react component)

const MyComponent= () => {
    return (
    <div>
      { props.children }
    </div>
  )
}

Note that this uses React's dangerouslySetInnerHtml, which... maybe isn't a good idea, but might be fine, since the HTML being set is coming in from the Rails template engine.

You could just include the changes to the Ruby code so that the block gets rendered, and that gets passed in a a nice prop (like children_html) but then the React components would have to care about whether their children are coming in from more React code, or from the Rails side of things.

Pull Request checklist

Remove this line after checking all the items here. If the item is not applicable to the PR, both check it out and wrap it by ~.

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file
    Add the CHANGELOG entry at the top of the file.

Other Information

As per the contribution guidelines, I'm supposed to run rake examples:gen_all, but that task doesn't exist


This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Enhanced react_component and react_component_hash methods to accept child HTML elements via block parameters.
    • Updated documentation for ReactOnRails to clarify new functionality and usage of render-functions.
  • Bug Fixes

    • Improved handling of child elements in React components, ensuring proper rendering and functionality.

Copy link
Contributor

coderabbitai bot commented Oct 1, 2024

Walkthrough

The changes enhance the react_component and react_component_hash methods in the ReactOnRails::Helper module to accept block parameters, allowing for the inclusion of child HTML elements. The internal logic for rendering remains largely unchanged, but the handling of props has been improved. Documentation updates clarify the new functionality and provide guidance on using render-functions and security considerations with dangerouslySetInnerHtml.

Changes

Files Change Summary
lib/react_on_rails/helper.rb The react_component and react_component_hash methods now accept a block for child elements, initializing options[:props] with an empty hash if not provided.
Documentation Updated to include new functionality for passing blocks to react_component, clarifying render-functions, and addressing security concerns with dangerouslySetInnerHtml.

Poem

In the garden of code where rabbits play,
New features bloom, brightening the day.
With blocks of joy, we nestle and weave,
React components dance, oh, how they believe!
Hopping through changes, we cheer and we sing,
For every new line, a fresh spring! 🐰✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 0caeb6e and eceb9f4.

📒 Files selected for processing (1)
  • lib/react_on_rails/helper.rb (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/react_on_rails/helper.rb

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (4)
node_package/src/createReactOutput.ts (2)

76-76: LGTM: Consistent handling of children in default case

The addition of the children argument to React.createElement in the default case is correct and aligns with the PR objective. This change ensures that children components passed from Rails templates are properly rendered within the React component, regardless of whether a render function is used or not.

For consistency and to reduce code duplication, consider refactoring the common React.createElement call into a separate function:

const createReactElement = (component: ReactComponent, props: any) => 
  React.createElement(component, props, children);

// Then use it in both places:
return createReactElement(reactComponent, props);
// and
return createReactElement(component as ReactComponent, props);

This refactoring would make the code more DRY and easier to maintain.


Line range hint 1-76: Summary of changes and recommendations

The implementation successfully achieves the PR objective of passing children from Rails templates to React components. The changes are consistent and well-integrated into the existing code structure. However, there are a few points to consider:

  1. Security: The use of dangerouslySetInnerHTML poses a potential security risk. Consider implementing proper HTML sanitization as suggested in the earlier comment.

  2. Code consistency: The React.createElement calls could be refactored into a separate function to reduce code duplication and improve maintainability.

  3. Testing: Ensure that comprehensive tests are added to cover the new functionality, including edge cases and security aspects.

  4. Documentation: Update the relevant documentation to reflect these changes and provide usage examples for passing children from Rails templates.

Overall, the implementation is on the right track but requires addressing the security concern before it can be considered ready for production.

🧰 Tools
🪛 Biome

[error] 32-32: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 29-29: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

lib/react_on_rails/helper.rb (2)

56-57: LGTM! Consider adding a comment for clarity.

The changes successfully implement the ability to pass children components from Rails templates to React components. This aligns well with the PR objectives.

Consider adding a brief comment explaining the purpose of this new functionality:

 def react_component(component_name, options = {}, &block)
+  # Capture any nested content passed to the component
   (options[:props] ||= {})[:children_html] = capture(&block) if block

56-57: Ensure proper testing and documentation

While the implementation looks good, please ensure the following:

  1. Add unit tests to cover this new functionality, including edge cases.
  2. Update the documentation (README, CHANGELOG, etc.) to reflect this new feature.
  3. Consider adding an example in the documentation showing how to use this new functionality in a Rails template.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between bb9a8a2 and 1e7ddf2.

📒 Files selected for processing (2)
  • lib/react_on_rails/helper.rb (1 hunks)
  • node_package/src/createReactOutput.ts (2 hunks)
🧰 Additional context used
🪛 Biome
node_package/src/createReactOutput.ts

[error] 29-29: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🔇 Additional comments (1)
node_package/src/createReactOutput.ts (1)

73-73: LGTM: Proper handling of children in render function

The addition of the children argument to React.createElement when using a render function is correct and aligns with the PR objective. This change ensures that children components passed from Rails templates are properly rendered within the React component.

Comment on lines 29 to 30
const children = props?.children_html ? React.createElement('div', {dangerouslySetInnerHTML: {__html: props.children_html }}) : null;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security concern: Use of dangerouslySetInnerHTML

While the implementation aligns with the PR objective of passing children from Rails templates, the use of dangerouslySetInnerHTML poses a security risk. This approach can expose users to cross-site scripting (XSS) attacks if the content of props.children_html is not properly sanitized.

Consider the following alternatives:

  1. Sanitize the HTML content on the server-side before passing it to the React component.
  2. Use a trusted HTML sanitization library on the client-side before rendering.
  3. If possible, pass structured data instead of HTML and render it safely using React components.

Example of using a sanitization library:

import DOMPurify from 'dompurify';

const children = props?.children_html
  ? React.createElement('div', { dangerouslySetInnerHTML: { __html: DOMPurify.sanitize(props.children_html) } })
  : null;

Would you like assistance in implementing one of these alternatives or exploring other secure options for handling HTML content?

🧰 Tools
🪛 Biome

[error] 29-29: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

@justin808
Copy link
Member

@rangerscience Why not just pass the HTML as a prop and let the react component handle the HTML?

@rangerscience
Copy link
Author

rangerscience commented Oct 1, 2024

@rangerscience Why not just pass the HTML as a prop and let the react component handle the HTML?

Sure! So, then, if you want to have a React component that can take children when used from both Rails and other React components -

# Rails
<%= react_component "MyComponent" do %>
  <%= render "events/form", event: Event.new %>
<% end %>
// React
const ParentComponent = () => {
  return <div>
    <MyComponent>
      <div>Children!</div>
    </MyComponent>
  </div>
}

You'd need to do:

const MyComponent= () => {
    return (
    <div>
      { props.children }
      { props.children_html && <div dangerouslySetInnerHtml={{__html: props.children_html}} /> }
    </div>
  )
}

By having React on Rails turn the passed HTML into a React element, the React components don't have to "know" whether they're being invoked by the Rails layer or by the React layer - they get to act just like regular React components.

@justin808
Copy link
Member

Given an easy work around, why expand the API?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
spec/dummy/client/app/startup/ChildrenExample.jsx (2)

3-8: LGTM: Component implementation is correct and aligns with PR objectives.

The ComponentWithChildren functional component is well-implemented and correctly uses the children prop, which aligns with the PR objective of passing children from Rails. The static message in the h1 element provides useful context.

Consider making the header text configurable via a prop for increased flexibility:

-const ComponentWithChildren = ({ children }) => (
+const ComponentWithChildren = ({ children, headerText = "This is component for testing passing children in from Rails" }) => (
   <div>
-    <h1>This is component for testing passing children in from Rails</h1>
+    <h1>{headerText}</h1>
     { children }
   </div>
 );

This change would allow users to customize the header text if needed, while maintaining the default message.


1-10: Component aligns with PR objectives, but consider adding more context.

The ComponentWithChildren successfully demonstrates the ability to accept and render children, which aligns with the PR's objective of passing children from Rails to React components. It's also good that the implementation avoids using dangerouslySetInnerHTML, addressing the security concern mentioned in the PR objectives.

To further improve the implementation and documentation:

  1. Consider adding a comment or documentation explaining how this component is intended to be used with Rails, perhaps including an example of the Rails helper usage.
  2. It might be helpful to add a prop-types declaration to clearly define the expected props, including the children prop.
  3. You could create a companion Rails view example that demonstrates how to use this component with the react_component helper, as mentioned in the PR description.

These additions would provide more context and make it easier for other developers to understand and use this new functionality.

lib/react_on_rails/helper.rb (2)

56-58: LGTM with a minor suggestion for clarity

The changes to the react_component method successfully implement the ability to pass children components from Rails to React, which aligns with the PR objectives. The use of capture(&block) is a good approach to handle the block content.

Consider adding a comment explaining the purpose of this new functionality for better code readability:

 def react_component(component_name, options = {}, &block)
+  # Capture the block content to pass as children to the React component
   (options[:props] ||= {})[:children_html] = capture(&block) if block

113-116: LGTM with a suggestion for consistency

The changes to the react_component_hash method are consistent with those made to react_component, allowing for passing children components in this context as well. The implementation is correct and follows the same pattern.

For consistency with the react_component method, consider adding a similar comment explaining the purpose of this new functionality:

 def react_component_hash(component_name, options = {}, &block)
+  # Capture the block content to pass as children to the React component
   (options[:props] ||= {})[:children_html] = capture(&block) if block
   options[:prerender] = true
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 1e7ddf2 and 678ebfd.

📒 Files selected for processing (2)
  • lib/react_on_rails/helper.rb (2 hunks)
  • spec/dummy/client/app/startup/ChildrenExample.jsx (1 hunks)
🔇 Additional comments (4)
spec/dummy/client/app/startup/ChildrenExample.jsx (2)

1-1: LGTM: React import is correct.

The import statement for React is properly implemented, which is necessary for creating React components.


10-10: LGTM: Default export is correct.

The component is properly exported as the default export, which is appropriate for a single component in a file and follows common React practices.

lib/react_on_rails/helper.rb (2)

Line range hint 1-1000: Summary of changes and impact

The modifications to lib/react_on_rails/helper.rb successfully implement the ability to pass children components from Rails to React in both react_component and react_component_hash methods. These changes align with the PR objectives and provide a more flexible way to compose React components within Rails templates.

Key points:

  1. The implementation is consistent across both methods.
  2. The changes are backwards-compatible.
  3. There are no apparent performance issues.

Considerations for follow-up:

  1. Add documentation for this new feature, including usage examples and security best practices.
  2. Implement unit tests to cover the new functionality.
  3. Review and update any affected components to ensure they handle the passed HTML content safely.

Overall, these changes enhance the flexibility of the react_on_rails gem while maintaining its existing functionality.


56-58: Security consideration and testing suggestion

While the implementation looks good, there are two points to consider:

  1. Security: The captured HTML content will be passed to React components, which may use dangerouslySetInnerHTML. Ensure that the content is properly sanitized or that the React components handle the HTML safely to prevent XSS vulnerabilities.

  2. Testing: Consider adding unit tests for this new functionality to ensure it works as expected and to prevent regressions in the future.

To check for potential XSS vulnerabilities, you can run the following script to search for uses of dangerouslySetInnerHTML in the React components:

Consider adding a section in the documentation explaining the security implications of passing HTML content from Rails to React components, and provide best practices for safely handling this content.

Also applies to: 113-116

✅ Verification successful

No instances of dangerouslySetInnerHTML found

The search did not find any uses of dangerouslySetInnerHTML in the React components, which suggests there are no XSS vulnerabilities related to this change.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
echo "Searching for uses of dangerouslySetInnerHTML:"
rg --type js --type jsx 'dangerouslySetInnerHTML'

Length of output: 180


Script:

#!/bin/bash
echo "Searching for uses of dangerouslySetInnerHTML in .js and .jsx files:"
rg -g '*.js' -g '*.jsx' 'dangerouslySetInnerHTML'

Length of output: 196

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (8)
spec/dummy/app/views/pages/children_example.erb (1)

1-4: Good implementation of children passing from Rails to React

This example effectively demonstrates the new feature of passing children from Rails templates to React components, which aligns well with the PR objectives. The use of prerender: true is beneficial for performance and SEO.

However, consider the following suggestions to enhance this example:

  1. The trace: true option might be useful for debugging, but ensure it's not enabled in production environments.
  2. To better showcase the feature's capabilities, consider including a more dynamic example, such as rendering a partial or including Ruby logic within the passed children.

Here's an enhanced version that demonstrates more dynamic content:

<%= react_component("ChildrenExample", prerender: true, trace: Rails.env.development?) do %>
  <p>This page demonstrates using Rails to produce content for child nodes of React components</p>
  <p>Current time: <%= Time.now %></p>
  <%= render partial: "shared/example_partial", locals: { message: "I'm from a partial!" } %>
<% end %>

This version:

  1. Only enables tracing in development environment
  2. Includes dynamic Ruby-generated content (current time)
  3. Demonstrates rendering a partial within the children

Would you like me to create a GitHub issue to track the implementation of this enhanced example?

spec/dummy/config/routes.rb (1)

42-42: LGTM! Consider adding a comment for clarity.

The new route for children_example is correctly implemented and aligns with the PR objective of adding support for passing children from Rails to React components. It follows the existing pattern in the file and doesn't conflict with other routes.

For consistency with some other routes in this file, consider adding a brief comment explaining the purpose of this route. For example:

# Example of passing children from Rails to React components
get "children_example" => "pages#children_example"

This would enhance readability and maintain consistency with the documentation style of other routes in this file.

node_package/src/createReactOutput.ts (1)

Line range hint 1-81: Summary of changes in createReactOutput.ts

The changes in this file successfully implement the PR objective of passing children from Rails templates to React components. However, there's a significant security concern with the use of dangerouslySetInnerHTML that needs to be addressed.

Positive changes include:

  1. Improved logging for renderFunction.
  2. Introduction of createElementFromComponent for better code structure.
  3. Consistent use of the new function throughout the file.

Action items:

  1. Address the security concern with dangerouslySetInnerHTML by implementing one of the suggested alternatives.
  2. Once the security issue is resolved, the other changes can be approved.

Consider discussing with the team about establishing a standardized approach for handling HTML content passed from Rails to React components. This could involve creating a utility function or adopting a consistent sanitization strategy across the project.

🧰 Tools
🪛 Biome

[error] 46-46: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

spec/dummy/app/views/shared/_header.erb (1)

107-109: LGTM! Consider adding a comment for clarity.

The new link for the "Children Example" is correctly implemented and consistent with the existing structure. It aligns well with the PR objective of adding support for passing children from Rails to React components.

Consider adding a brief comment above this new list item to highlight that it's a new feature, which could help other developers quickly identify recent changes. For example:

<%# New feature: Passing children from Rails to React components %>
<li>
  <%= link_to "Children Example", children_example_path %>
</li>
spec/dummy/spec/system/integration_spec.rb (1)

287-301: LGTM! Consider adding a more specific selector.

The new test case for the "children_example" page is well-structured and consistent with the existing test suite. It effectively verifies the presence of Rails-generated content within React components.

To enhance the test's robustness, consider using more specific selectors to target the text content. This can help prevent false positives if similar text appears elsewhere on the page.

Here's a suggestion to improve the test:

 it "children_example should not have any errors" do
-  expect(page).to have_text(
-    "This page demonstrates using Rails to produce content for child nodes of React components"
-  )
-  expect(page).to have_text(
-    "And, just to check that multiple DOM nodes are fine"
-  )
+  within('#react-component-with-children') do
+    expect(page).to have_text(
+      "This page demonstrates using Rails to produce content for child nodes of React components"
+    )
+    expect(page).to have_text(
+      "And, just to check that multiple DOM nodes are fine"
+    )
+  end
 end

This assumes there's a container with the id 'react-component-with-children'. Adjust the selector as needed based on your actual DOM structure.

docs/getting-started.md (3)

124-129: LGTM! Consider adding a brief explanation.

The new section clearly demonstrates how to pass a block to react_component. This is a valuable addition to the documentation.

Consider adding a brief explanation before the code example to introduce the concept. For example:
"You can now pass a block to react_component, which will be provided as the children prop to the React component. This allows you to include both static HTML and dynamic Rails content within your React components."


131-139: Consider simplifying the component example.

While the example correctly demonstrates how to use the children prop, the nested function structure might be confusing for some readers.

Consider simplifying the example to a more straightforward functional component:

import React from 'react';

const YourComponent = ({ children }) => (
  <div>{children}</div>
);

export default YourComponent;

Also, consider adding a brief explanation of why the component is structured this way if you decide to keep the current nested function approach. This would help readers understand the rationale behind this pattern.


140-141: Enhance the security warning.

The note about dangerouslySetInnerHtml is crucial. However, we can strengthen it to ensure users fully understand the implications.

Consider expanding the warning as follows:

"Security Note: This feature is implemented using React's dangerouslySetInnerHtml. While generally safe when the HTML comes from your own Rails rendering engine, it can expose your application to cross-site scripting (XSS) attacks if not used carefully. Always ensure that the content passed to the block is properly sanitized and comes from trusted sources. Avoid passing user-generated content without proper sanitization."

This provides more context about the specific risks and offers guidance on safe usage.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 678ebfd and 99c6c72.

📒 Files selected for processing (8)
  • docs/getting-started.md (1 hunks)
  • lib/react_on_rails/helper.rb (2 hunks)
  • node_package/src/createReactOutput.ts (2 hunks)
  • spec/dummy/app/views/pages/children_example.erb (1 hunks)
  • spec/dummy/app/views/shared/_header.erb (1 hunks)
  • spec/dummy/client/app/startup/ChildrenExample.jsx (1 hunks)
  • spec/dummy/config/routes.rb (1 hunks)
  • spec/dummy/spec/system/integration_spec.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/react_on_rails/helper.rb
  • spec/dummy/client/app/startup/ChildrenExample.jsx
🧰 Additional context used
🪛 Biome
node_package/src/createReactOutput.ts

[error] 46-46: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🔇 Additional comments (6)
node_package/src/createReactOutput.ts (4)

39-42: LGTM: Improved logging for renderFunction

The additional logging for renderFunction is a good improvement. It enhances debugging capabilities without introducing any issues.


48-48: LGTM: Improved React element creation

The introduction of createElementFromComponent is a good refactoring. It streamlines the creation of React elements and improves code readability.


78-81: LGTM: Consistent use of createElementFromComponent

The use of createElementFromComponent in both the renderFunction and non-renderFunction cases improves consistency and readability. This is a good refactoring.


45-46: ⚠️ Potential issue

Security concern: Use of dangerouslySetInnerHTML

While this implementation aligns with the PR objective of passing children from Rails templates, the use of dangerouslySetInnerHTML poses a significant security risk. This approach can expose users to cross-site scripting (XSS) attacks if the content of props.children_html is not properly sanitized.

Consider the following alternatives:

  1. Sanitize the HTML content on the server-side before passing it to the React component.
  2. Use a trusted HTML sanitization library on the client-side before rendering.
  3. Pass structured data instead of HTML and render it safely using React components.

Example of using a sanitization library:

import DOMPurify from 'dompurify';

const children = props?.children_html
  ? React.createElement('div', { dangerouslySetInnerHTML: { __html: DOMPurify.sanitize(props.children_html) } })
  : null;

Would you like assistance in implementing one of these alternatives or exploring other secure options for handling HTML content?

🧰 Tools
🪛 Biome

[error] 46-46: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

spec/dummy/spec/system/integration_spec.rb (1)

287-301: Good addition to the test suite.

The new test case for React components with children is a valuable addition to the test suite. It enhances the coverage by testing an important feature of the React on Rails integration. The test case is well-integrated into the existing structure and doesn't disrupt other tests.

Consider adding more specific test cases to cover different scenarios of passing children from Rails to React components, such as:

  1. Passing multiple children
  2. Passing children with complex HTML structure
  3. Passing children with embedded Ruby code

This would further strengthen the test coverage for this feature.

docs/getting-started.md (1)

124-141: Overall, these changes significantly enhance the React on Rails documentation.

The new feature for passing blocks to react_component adds flexibility to the library, allowing developers to include both static HTML and dynamic Rails content within React components. This is a valuable addition that will likely improve the developer experience.

The documentation provides clear examples in both Ruby and JavaScript, which is helpful for understanding the implementation. However, there are opportunities to further improve clarity and provide more context, especially for developers who might be less familiar with certain React patterns.

The inclusion of a security warning regarding dangerouslySetInnerHtml is crucial. Strengthening this warning, as suggested, will help ensure that developers use this feature safely.

These changes align well with the PR objectives of enhancing the functionality of the react_on_rails gem by allowing Rails templates to pass children components directly to React components.

@rangerscience
Copy link
Author

rangerscience commented Oct 1, 2024

Given an easy work around, why expand the API?

Sure! IMHO, developer happiness, least-surprise, and laziness. You can workaround both changes:

<%= react_component "MyComponent", props: { children_html: render_to_string("events/form") } %>
const MyComponent = () => <div>{ props.children_html && <div dangerouslySetInnerHtml={{__html: props.children_html}} /> } />

but this makes the developer's intention less clear (all we're doing is passing child elements, nothing more), requires boilerplate, and breaks expectations (causing surprise) - Rails templates can take blocks (presuming the template has a yield); React components can take children (presuming they put { children } somewhere). Without these changes, neither is true for React on Rails components.

This API expansion allows both the Rails and React side of things to work as intuitively expected and in-line with the rest of their ecosystems. It's also a pretty minimal set of changes - four lines of code or so, depending on how you count? - so the maintenance cost should be minimal as well.

Those are some arguments for why this API expansion is worthwhile. If the debate is actually about the generalized debate over whether or not APIs should be expanded when workarounds are available, that's a different discussion that I'd be happy to have :)

@rangerscience rangerscience changed the title [WIP] Add support for passing children in from Rails Add support for passing children in from Rails Oct 1, 2024
@justin808
Copy link
Member

Closing for now as this is easily done via the current API. @rangerscience could you provide a doc PR instead?

@justin808 justin808 closed this Oct 7, 2024
@rangerscience
Copy link
Author

No worries, I'll just keep my fork up and running :) I've got some other changes I want to look at, so if I write up docs it'll be there (and after that), and at that point I'll welcome and help out if y'all want to open a PR from me to you.

I would recommend adding a bit to the docs pointing to this PR, if you want a quick and easy way to get someone this info - or, otherwise make sure the GH PR search pulls this up with search terms you'd expect someone to use.

@justin808
Copy link
Member

I'm not sure if pointing the docs to the implementation of an added API that was not added will help.

Thanks in advance for docs on how to cleanly accomplish the goal with the existing API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants