Recreating HTML/CSS Behavior with Styled-Components - javascript

I have a fairly annoying problem using Styled-Components in a React app. The class names of the HTML elements are very important, as is the dynamism of styled-components (via props passing). So it's important that both are used.
tl;dr I want to use a classname in CSS (and not HTML) without having to create an empty component or something.
I have a class with a dynamic classname, let's stay "style-123", where "123" is dynamically changed via state and is important for parity with the current setup. (IMO, the dynamic nature of the classname is preventing a solution.) The "style-123" classname is used in other HTML elements, but it is not a component itself so should not show up as an HTML element.
I have a component ("ComponentA") that has a classname of "component-a". This component's HTML should looking like the following:
<div class="component-a">
HELLO
</div>
and its Styles (in Dev Tools) should look like this:
.style-123 .component-a {
margin-left: -2%;
}
where we now see the class ("style-123") added. This should be the final result.
The general structure of the HTML is along the lines of:
<div class="name-whatever style-123 another-classname">
<div>
<div class="component-a">
</div>
</div>
</div>
Problem
I do not seem to be able to isolate "component-a" as an HTML classname for "ComponentA" while applying CSS as .style-123 .component-a via styled-components. In order for the HTML to have an isolated classname, the component (JSX) would need to be:
<ComponentA className={'component-a'}/>
and the styled component:
const ComponentA = styled.div`
&.component-a {
color: red;
}
`
This is the only way I know how to link-up a component in JSX with its styled-component counterpart.
I cannot do:
<ComponentA className={'component-a'}/>
and the styled component:
const ComponentA = styled.div`
&.style-123 .component-a {
color: red;
}
`
as this won't apply the styling since there's a classname-mismatch.
I've tried many combinations of SASS, all the ampersands and arrows and pluses and etc., over the last few hours. I hope this is feasible via styled-components as I need to declare classnames and have classnames dynamic as well as the CSS attributes and values, all passed via props. If there is another solution that doesn't use styled-components but maintains this dynamism, I am open to that.
Thanks for the time.

Related

LitElement appending custom elements using button

I have a custom LitElement and inside it, I need to have a button that will dynamically append new custom elements (preferably using TemplateResult objects generated by html function) inside a container:
import { LitElement, html, render } from "lit";
import { customElement } from "lit/decorators.js";
#customElement('example-custom-component')
class CustomComponent extends LitElement {
render() {
return html`
<div id="custom-el-container">
</div>
<button #click=${this.appendNewCustomEl}>click me!</button>
`;
}
appendNewCustomEl() {
const templateToAppend = html`
<another-custom-component>
some other things added here
</another-custom-component>
`;
render(templateToAppend, this.shadowRoot?.querySelector('#custom-el-container'));
}
}
As you can see above I tried to achieve it by using the render function, but instead of appending it at the end of the container, I'm simply overwriting the content of the div. What am I doing wrong? What's the best approach to achieve those results? Please help.
EDIT:
New example for my question from comments about click events:
appendNewCustomEl() {
this.shadowRoot!.querySelector('#custom-el-container').insertAdjacentHTML("beforeend", `
<another-custom-component>
<button #click=${this.functionFromCustomComponent}>click me!</button>
</another-custom-component>
`)
}
If you really want to do it with lit-html and your container's content is purely what you are dynamically rendering on each button click, (i.e. not server side rendered content) or you are using lit-html v2 then you could have a list and track what you have rendered. something like:
items=[];
appendNewCustomEl() {
this.items.push(null);
const templatesToAppend = this.items.map(() => html`
<another-custom-component>
some other things added here
</another-custom-component>
`);
render(templatesToAppend, this.shadowRoot?.querySelector('#custom-el-container'));
}
in general what lit-html is good at and tries to achieve is an optimal re-render of markup when only parts are changed. not necessary a template engine alternative to handlebars, mustache and co.
In your example, you don't need it and could do it simply without lit-html:
appendNewCustomEl() {
this.shadowRoot!.querySelector('#custom-el-container').insertAdjacentHTML("beforeend", `
<another-custom-component>
some other things added here
</another-custom-component>
`)
}
I've slightly modified your example code to be more idiomatic Lit. Instead of using a querySelector and insertAdjacentHTML, it is preferred to render the array of templates declaratively. This has the added benefit that your functionFromCustomComponent event bindings "just work".
I've written the full example in a Lit playground.
The list of templates are stored in a reactive property, this.templates that schedules an efficient update whenever the list is updated.
Then the this.appendNewCustomEl method can push new templates to the list, and an automatic efficient render happens automatically.
this.templates is rendered into the #custom-el-container div using an expression:
<div id="custom-el-container">
${this.templates}
</div>
See Lit expressions documentation for other examples.
In my linked example, the list of templates turn blue when clicked to demonstrate the event working.
Not necessary but related, Lit.dev has a tutorial all about Working with lists that goes much more in depth if you're curious.

can I "import" React CSS modules from a string?

I have a React project that uses CSS Modules by importing .css files. e.g. import styles from "./styles/MyComponent.css";
I find myself now in a situation where a component is receiving a customized snippet of CSS as a string in response to a dynamic call to the server.
Is it possible to take this string (which is unknown until runtime) and essentially do the same thing to it that import does to the .css file when it is compiled by webpack?
For example:
import styles from "./styles/MyComponent.css";
//later on in component...
moreStyle = "a string containing valid CSS";
//do *something* here to moreStyle string to do whatever importing does to a file.
myJSX = (
<div className={styles.someClass}>
This div content is styled by someClass
</div>
<div className={moreStyle.someOtherClass}>
This div content needs to be styled by someOtherClass, but obviously this isn't working
</div>
);
You can try this:
import styles from "./styles/MyComponent.js";
myJSX = (
<div style={styles.someClass}>
This div content is styled by someClass
</div>
<div style={styles.someOtherClass}>
This div content needs to be styled by someOtherClass, but obviously this isn't working
</div>
);
Consider creating a serialized object, instead.
// Filename: MyComponentStyle.js
//Example styles
export const styles = {
someClass: { height: 10 },
someOtherClass: {
backgroundColor: 'red',
}
};
React doesn't work like your typical HTML/CSS/JS app. The thing to note that JSX may look like HTML but it is not HTML.
In your code, className is being defined as a string, which is expected, however, there's possibly no CSS being referred to in this document. Try to console.log it and see what you get.
...
Another possible solution is to simply have your style within the same component file. A common design choice for component styling is inline styling. This is especially useful for projects of medium-large scale, where managing files can get difficult.
Helpful references:
https://reactjs.org/docs/dom-elements.html#style
https://codeburst.io/4-four-ways-to-style-react-components-ac6f323da822

StencilJS | Apply host styles to component

I am trying to apply the styles from the website where a stencilJS component is included ... but don't know how.
import { Component } from '#stencil/core';
#Component({
tag: 'menu-component',
styleUrl: 'menu-component.css',
shadow: true
})
export class MyComponent {
render() {
return (
<div>
<h1>Hello World</h1>
<p id="red">This is JSX!</p>
</div>
);
}
}
The component is included like this:
<link rel="stylesheet" href="css/main.css" type="text/css" />
<script src='https://unpkg.com/component#0.0.2/dist/mycomponent.js'></script>
<my-component></my-component>
In my main.css file I have something like this:
#red{
color: red;
}
I would like the style to be applied to the element from the stencil component. Is this possible?
You can use css variables for this.
Look at the following code samples:
index.html
<my-component style="--text-color:red;"></my-component>
my-component.css
#red {
color: var(--text-color, black);
}
In the component's styling you assign a CSS variable as value to the text color of the class [id="red"]. In your application, you're now able to change the color by setting the value of the variable.
Your component has a "Shadow DOM", which serves the purpose of encapsulating everything, including styles in a separate DOM, so it pretty much exists to prevent you from overriding it's styles.
Previously there were some "shadow piercing" CSS directives like /deep/ and ::shadow, but they have been deprecated and are no longer functional.
So that's pretty much how it's supposed to be.
Now for workarounds:
create a shared css file and include it in both your component and your application - or
set the style using javascript from your host application using the shadowRoot property:
var div = document.querySelector('#comp').shadowRoot.querySelector('div#red');
div.style['color'] = 'red';
You should be able to use the :host psuedo selector in your stylesheet to apply host level styles:
:host {
style: value
}
You could easily bring in #stencil.sass for your style sheets, link here: https://github.com/ionic-team/stencil-sass/blob/master/readme.md
This will give you greater functionality with your styles in stencil.
EDIT:
I misunderstood and now see you want to manipulate outside of the component. You could supply a <slot /> element in your web component and add specifically styled elements from outside of the web components DOM. Link here: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot

Error with place holder on jsx

I have a simple react HTML input component and as it used on different sections/pages, I added some props for styling it and its placeholder. The problem is that sometimes I got an error on the compilation(we are using nextjs).
This the code:
{placeHolderColor && (<style jsx>{`input::placeholder{color:${placeHolderColor}}`}</style>)}
Basically, I'm using an inline If with Logical && Operator inside the render function to check if the prop placeHolderColor exists, and if exist add the style tag.
The error I'm getting:
Warning: Unknown prop jsx on tag. Remove this prop from the element.
The error only occurs when you reload the page. If you made a change and the hot code reloading run, there is no error. Not sure if the problem is the var inside the literal, the '::placeholder' pseudo-element or what. The code works anyway, and if the placeHolderColor var is defined the style is applied.
When I tested your code I got the same error(also on page load). After that I talked to a styled jsx dev with nickname #g (on github #giuseppeg) on ZEIT's #next slack channel https://zeit.chat/ and he confirmed that the conditional use of <style jsx> tag is not supported. Here is his explanation https://github.com/zeit/styled-jsx/issues/233.
Also, after removing conditional and just inserting your tag like this:
<style jsx>{'input::placeholder{color:${placeHolderColor}}'}</style>
I've got this error:
Module build failed: SyntaxError: Expected placeHolderColor to not come from the closest scope.
Styled JSX encourages the use of constants instead of props or dynamic values which are better set via inline styles or className toggling. See https://github.com/zeit/styled-jsx#dynamic-styles.
According to recommendations from https://github.com/zeit/styled-jsx#dynamic-styles, you basically should not add dynamic values into template literals inside <style jsx> tag (though you can put there constants and constant expressions starting from version 1.0.4 (see UPDATE at the bottom of the answer for details)). The reason behind the prohibition of using props/dynamic values, according to #giuseppeg comment in the slack thread https://zeit-community.slack.com/archives/C2U01UYEA/p1496928897076534, is following: "at the moment styled-jsx compiles and produces static code and therefore the hashes that make the final CSS unique won't change when the value of a variable like color changes"
So, as you see from documentation, it is recommended to use dynamic values via inline styles or className toggling. Unfortunately, styling pseudo-elements (placeholder etc.) in not possible via inline styles in React, so if you have finite number of possible colours then create a class for each colour case and use it like this:
const InputWithColouredPlaceholder = props => (
<div>
<input
placeholder="text"
className={
'placeholderColourClass' in props && props.placeholderColourClass
}
/>
<style jsx>{`
.reddy::placeholder {
color: red;
}
.greeny::placeholder {
color: green;
}
`}</style>
</div>
);
and render it like <InputWithColouredPlaceholder placeholderColourClass="reddy" />
It gets more complicated in case of large range of possible colours though. In this case I would recommend to ask for suggestions in #next channel on ZEIT's slack https://zeit.chat/.
UPDATE
Using constants and constant expressions in template literals should work in styled-jsx 1.0.4 (but nextjs currently depends on 1.0.3, and separate installation of styled-jsx will not help, so wait for update of nextjs to support styled jsx 1.0.4). It means that any constants that are not passed trough props and not created inside component should work (for example you can have a js file with constants for colours and import them into your input component). But it does not fit your case because you need a dynamic way.
I got error Warning: Unknown prop 'jsx' on <style> tag. Remove this prop from the element. For details, see FB react-unknown-prop. What does that jsx property in your style tag means? Just remove it?

Mixing React Components with HTML Blocks

Something that's always confused me is how many React examples show everything as components. But's let's say I only need to include some simple HTML alongside my components, like:
class Homepage extends Component {
render() {
return (
<Container>
<Hero />
<Features />
<div className="copyright">Copyright Some Company Name.</div>
</Container>
);
}
}
The div element will never be more than static text.
Should the <div> be moved into a pure component? Or is it ok to simplify the Homepage component by adding plain HTML this?
Sure it's ok. all in all it's HTML in the end. React components are set of html elements when you call the render function.
One rule of thumb i follow is: create a new component when you think a new responsibility is in order.
That div is a component just like any other, except it is a "primitive" one. There should be no problem mixing the primitive components from HTML with your own custom components.

Categories