"Maximum call stack size exceeded" when using Vue.component - node.js

This is a very weird one.
I have some HTML retrieved from a headless CMS, via the asyncData hook, and which I'm displaying via a dynamic component, as follows (truncated for brevity):
<script>
import {apiReq} from '~/assets/js/utils'
import Vue from 'vue'
export default {
data() { return {
}},
async asyncData(ctx) {
let req = await apiReq.call(ctx, 'getMeta', {uri: ctx.route.params.article}, true);
if (req === null) return;
let contentComp = Vue.component('test', {template: '<div>'+req.data.content+'</div>'})
return {article: req.data, contentComp}
},
methods: {}
}
</script>
...and rendered in my template via:
<component :is='contentComp'></component>
I'm using a dynamic component rather than simply v-html because the fetched HTML contains references to child components that should be rendered.
The above results in a "maximum call stack size exceeded" error. If I kill the JS line that creates the dynamic component (Vue.component(...) everything's fine and the page loads. There's no issue with the API request - that fetches the data just fine, with a 200.
The error persists if I keep the JS line but remove the <component... rendering line, so it seems the error is emanating from the creation of the dynamic component, not its rendering.
This happens both with target set to 'server' and 'static'.
[ UPDATE ]
req.data.content is the string of markdown coming from the CMS, which also contains some child component references.

Related

Render PrimeFaces <p:focus> component only if the browser window has a certain size

Is there a way to render the PrimeFaces <p:focus> component (or let it do its job) only when the browser window has a certain size? To do this I understand that maybe I have to have access the browser window size in server code and use the component rendered attribute to access this code.
In client code, I get the window width with code like this: $(window).width() > 480.
The reason for this is that I don't want to focus the first component in mobile devices, which most of the time have small screens.
Today I'm doing this with the client code below, but I'd like to use the <p:focus> component for the task, as it also has the benefit to focus the first invalid component when validation fails.
$(
function()
{
if (bigWindow())
{
focusFirstInput();
}
}
)
function bigWindow()
{
return $(window).width() > 480;
}
function focusFirstInput()
{
$("#form input:text, #form input[type=number], #form input[type=password], #form textarea").
first().focus();
}
I've found a way using the <p:focus> component:
$(
function()
{
let primeFacesOriginalFocusFunction = PrimeFaces.focus;
PrimeFaces.focus =
function(id, context)
{
if (!isMobile())
{
primeFacesOriginalFocusFunction(id, context);
}
}
}
)
function isMobile()
{
return ...
}

LitElement <slot> not wokring

I'm creating my custom accordion element. In which I'll have 2 components 1 for ul and other for li.
Content in file accordion-ul.ts, in which I've a slot where I want my li.
import { html, customElement, property, LitElement } from 'lit-element';
import { Accordion } from 'carbon-components';
#customElement('accordion-panel')
export default class AccordionPanel extends LitElement {
firstUpdated() {
const accordionElement = document.getElementById('accordion');
Accordion.create(accordionElement);
}
connectedCallback() {
super.connectedCallback();
}
render() {
return html`
<ul data-accordion class="accordion" id="accordion">
<slot></slot>
</ul>
`;
}
createRenderRoot() {
return this;
}
}
NOTE: I'm getting an error in the console in the firstUpdated() : Uncaught (in promise) TypeError: Cannot read property 'nodeName' of null.
The way I'm using it for testing:
<accordion-panel><li>test</li></accordion-panel>
IDK, it's not working and nothing is printing on the screen. On inspecting the element, I can see there's empty in DOM.
Your problem is that you're trying to use slots, which are a shadow DOM feature but you're not using shadow DOM (since you're overwriting the createRenderRoot method to prevent the creation of the shadowRoot)
So, if you want to use slots, just remove the createRenderRoot function from your class and use shadow DOM
Edit:
You should also update your firstUpdated method so that this part:
const accordionElement = document.getElementById('accordion');
Uses the element from your shadow DOM
const accordionElement = this.shadowRoot.querySelector('.accordion');
Then again, CarbonComponents styling will probably not work so you'll need to add those in some other way

Using Fragment to insert HTML rendered on the back end via dangerouslySetInnerHTML

I used to compile and insert JSX components via
<div key={ ID } dangerouslySetInnerHTML={ { __html: HTML } } />
which wrapped my HTML into a <div>:
<div>my html from the HTML object</div>
Now react > 16.2.0 has support for Fragments and I wonder if I can use that somehow to avoid wrapping my HTML in a <div> each time I get data from the back end.
Running
<Fragment key={ ID } dangerouslySetInnerHTML={ { __html: HTML } } />
will throw a warning
Warning: Invalid prop `dangerouslySetInnerHTML` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.
in React.Fragment
Is this supported yet at all? Is there another way to solve this?
Update
Created an issue in the react repo for it if you want to upvote it.
Short Answer
Not possible:
key is the only attribute that can be passed to Fragment. In the
future, we may add support for additional attributes, such as event
handlers.
https://reactjs.org/docs/fragments.html
You may want to chime in and suggest this as a future addition.
https://github.com/facebook/react/issues
In the Meantime
You may want to consider using an HTML parsing library like:
https://github.com/remarkablemark/html-react-parser
Check out this example to see how it will accomplish your goal:
http://remarkablemark.org/blog/2016/10/07/dangerously-set-innerhtml-alternative/
In Short
You'll be able to do this:
<>
{require('html-react-parser')(
'<em>foo</em>'
)}
</>
Update December 2020
This issue (also mentioned by OP) was closed on Oct 2, 2019. - However, stemming from the original issue, it seems a RawHTML component has entered the RFC process but has not reached production, and has no set timeline for when a working solution may be available.
That being said, I would now like to allude to a solution I currently use to get around this issue.
In my case, dangerouslySetInnerHTML was utilized to render plain HTML for a user to download; it was not ideal to have additional wrapper tags included in the output.
After reading around the web and StackOverflow, it seemed most solutions mentioned using an external library like html-react-parser.
For this use-case, html-react-parser would not suffice because it converts HTML strings to React element(s). Meaning, it would strip all HTML that wasn't standard JSX.
Solution:
The code below is the no library solution I opted to use:
//HTML that will be set using dangerouslySetInnerHTML
const html = `<div>This is a div</div>`
The wrapper div within the RawHtml component is purposely named "unwanteddiv".
//Component that will return our dangerouslySetInnerHTML
//Note that we are using "unwanteddiv" as a wrapper
const RawHtml = () => {
return (
<unwanteddiv key={[]}
dangerouslySetInnerHTML={{
__html: html,
}}
/>
);
};
For the purpose of this example, we will use renderToStaticMarkup.
const staticHtml = ReactDomServer.renderToStaticMarkup(
<RawHtml/>
);
The ParseStaticHtml function is where the magic happens, here you will see why we named the wrapper div "unwanteddiv".
//The ParseStaticHtml function will check the staticHtml
//If the staticHtml type is 'string'
//We will remove "<unwanteddiv/>" leaving us with only the desired output
const ParseStaticHtml = (html) => {
if (typeof html === 'string') {
return html.replace(/<unwanteddiv>/g, '').replace(/<\/unwanteddiv>/g, '');
} else {
return html;
}
};
Now, if we pass the staticHtml through the ParseStaticHtml function you will see the desired output without the additional wrapper div:
console.log(ParseStaticHtml(staticHtml));
Additionally, I have created a codesandbox example that shows this in action.
Notice, the console log will throw a warning: "The tag <unwanteddiv> is unrecognized in this browser..." - However, this is fine because we intentionally gave it a unique name so we can easily differentiate and target the wrapper with our replace method and essentially remove it before output.
Besides, receiving a mild scolding from a code linter is not as bad as adding more dependencies for something that should be more simply implemented.
i found a workaround
by using react's ref
import React, { FC, useEffect, useRef } from 'react'
interface RawHtmlProps {
html: string
}
const RawHtml: FC<RawHtmlProps> = ({ html }) => {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
if (!ref.current) return
// make a js fragment element
const fragment = document.createDocumentFragment()
// move every child from our div to new fragment
while (ref.current.childNodes[0]) {
fragment.appendChild(ref.current.childNodes[0])
}
// and after all replace the div with fragment
ref.current.replaceWith(fragment)
}, [ref])
return <div ref={ref} dangerouslySetInnerHTML={{ __html: html }}></div>
}
export { RawHtml }
Here's a solution that works for <td> elements only:
type DangerousHtml = {__html:string}
function isHtml(x: any): x is DangerousHtml {
if(!x) return false;
if(typeof x !== 'object') return false;
const keys = Object.keys(x)
if(keys.length !== 1) return false;
return keys[0] === '__html'
}
const DangerousTD = forwardRef<HTMLTableCellElement,Override<React.ComponentPropsWithoutRef<'td'>,{children: ReactNode|DangerousHtml}>>(({children,...props}, ref) => {
if(isHtml(children)) {
return <td dangerouslySetInnerHTML={children} {...props} ref={ref}/>
}
return <td {...props} ref={ref}>{children}</td>
})
With a bit of work you can make this more generic, but that should give the general idea.
Usage:
<DangerousTD>{{__html: "<span>foo</span>"}}</DangerousTD>

Uncaught Invariant Violation: Objects are not valid as a React child for HTML Form

I wrote a function to build an HTML form based on the keys and values of an object and I am trying to return the form in my render method. However, I keep getting the error:
ReactJs 0.14 - Invariant Violation: Objects are not valid as a React child
Here is my createForm() method:
createForm() {
const obj = {
}
const object_fields = resourceFields.fields;
let form = document.createElement('form');
_.forIn(object_fields, function(field_value, field_name) {
let div = document.createElement('div');
div.setAttribute('className', 'form-control');
let label = document.createElement('label');
label.setAttribute('htmlFor', 'name');
label.innerHTML = field_name;
let input = document.createElement('input');
input.setAttribute('className', 'form-control');
input.setAttribute('type', 'text');
input.setAttribute('ref', field_name);
input.setAttribute('id', field_name);
input.setAttribute('value', field_value);
input.setAttribute('onChange', '{this.handleChange}');
div.appendChild(label);
div.appendChild(input);
form.appendChild(div);
})
console.log(form) //this prints out fine
return form
}
Here is my render() method:
render() {
return (
<div>
{this.createForm()}
</div>
)
}
Does anyone know what might be happening? My form prints out in the console just fine... Thanks in advance!
You never manipulate actual DOM nodes when you're working with React. When you build your UI in the render function, the JSX markup is translated into plain JavaScript (React.createElement function calls), which builds a representation of the DOM.
So, in your case, you should return JSX in createForm, not a DOM element.

(Post/Redirect/Get pattern) Laravel layout variables don't work after redirect

I'm using the Post/Redirect/Get (PRG) pattern in my Laravel controllers to prevent duplicate form submission.
It works well when I don't use layouts or when my layouts don't use any variable. The problem is my layout uses a variable named $title. When I load the view and the layout without redirect it works well, the title set in the controller is passed to the layout, but after processing a form and redirecting to the same route which uses the same layout and the same controller method I get a "Undefined variable: title" error coming from my layout file.
Here is my code:
File: app/routes.php
Route::get('contact', array('as' => 'show.contact.form', 'uses' => 'HomeController#showContactForm'));
Route::post('contact', array('as' => 'send.contact.email', 'uses' => 'HomeController#sendContactEmail'));
File: app/controllers/HomeController.php
class HomeController extends BaseController {
protected $layout = 'layouts.master';
public function showContactForm()
{
$this->layout->title = 'Contact form';
$this->layout->content = View::make('contact-form');
}
public function sendContactEmail()
{
$rules = ['email' => 'required|email', 'message' => 'required'];
$input = Input::only(array_keys($rules));
$validator = Validator::make($input, $rules);
if($validator->fails())
return Redirect::back()->withInput($input)->withErrors($validator);
// Code to send email omitted as is not relevant
Redirect::back()->withSuccess('Message sent!');
}
}
File: app/views/layouts/master.blade.php
<!DOCTYPE html>
<html>
<head>
<title>{{{ $title }}}</title>
</head>
<body>
#yield('body')
</body>
</html>
File: app/views/contact-form.blade.php
#section('body')
#if (Session::has('success'))
<div class="success">{{ Session::get('success') }}</div>
#endif
{{
Form::open(['route' => 'send.contact.email']),
Form::email('email', null, ['placeholder' => 'E-mail']),
Form::textarea('message', null, ['placeholder' => 'Message']),
Form::submit(_('Send')),
Form::close()
}}
#stop
I don't understand why after redirecting the next line of code is ignored
$this->layout->title = 'Contact form';
I've tried with Redirect::action('HomeController#sendContactEmail'); or Redirect::route('show.contact.form'); but the result is the same.
The controller in charge of rendering that view is exactly the same before the redirect than after the redirect, and it has no business logic at all, so why it only works on the first case but not in the second?
This
Redirect::back()->withSuccess('Message sent!');
should be
return Redirect::back()->withSuccess('Message sent!');
When layout attribute is set in a controller and method is not returning any response, controller try to render the layout. In your sendContactEmail() method both conditions fulfilled and controller tried to render layout before $title is set.
see callAction() in Illuminate\Routing\Controllers\controller.
http://laravel.com/api/source-class-Illuminate.Routing.Controllers.Controller.html#93-127
Have you tried using
return View::make('contact-form', array('title' => 'Contact Form'));
Instead of interacting with the layout directly?
Redirect::back() creates a 302 using the referer value of the current HTTP request. I would start by comparing the initial form request to the redirect request to see if that yields any clues. You could also try...
Redirect::route('HomeController#showContactForm')->withInput()...
I know it's less dynamic but it will generate the URL rather then rely on the referer value in the HTTP header.

Resources