Extending regular React components with styled-components - javascript

The docs for styled-components show its default styled export accepts a single argument:
styled
This is the default export. This is a low-level factory we use to create the styled.tagname helper methods.
Arguments
component / tagname
Description
Either a valid react component or a tagname like 'div'.
I've emphasized "valid react component" here because they explicitly aren't saying this has to be a React component created by styled, although traditionally that is how this is used (as well as documented under their Extending Styled section). An example of this is shown below:
const RedBox = styled.div`
border: 1px solid black;
color: red;
`;
// Traditionally, the argument you pass to `styled` is
// a react element *created by a previous `styled` call*
const BlueBox = styled(RedBox)`
color: blue;
`;
function Example() {
return (
<div>
<RedBox>I am a red box</RedBox>
<BlueBox>I am a blue box</BlueBox>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://unpkg.com/react#17.0.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/react-is#17.0.1/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/styled-components#5.2.1/dist/styled-components.js"></script>
<div id="root"></div>
Nothing unsurprising with the above.
However, my question is what if you pass a non styled component as the argument to the styled call? Shouldn't that returned element also get the styles applied?
Consider the following simple example:
// Create two simple components, one functional, one class-based
const BoxFunctional = (props) => <div>{props.children}</div>;
class BoxClass extends React.Component {
render() {
return <div>{this.props.children}</div>
}
}
// Here, I pass a functional React Component to `styled`
const RedBoxFunctional = styled(BoxFunctional)`
color: red;
border: 1px solid black;
`;
// Again, passing another regular React component, this time a class component
const RedBoxClass = styled(BoxClass)`
color: red;
border: 1px solid black;
`;
function Example() {
return (
<div>
<p>The below two Boxes are regular react elements:</p>
<BoxFunctional>I am a functional box</BoxFunctional>
<BoxClass>I am a class box</BoxClass>
<hr />
<p>The below two boxes <em>should</em> be styled:</p>
<RedBoxFunctional>I am a functional red box</RedBoxFunctional>
<RedBoxClass>I am a class red box</RedBoxClass>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://unpkg.com/react#17.0.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/react-is#17.0.1/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/styled-components#5.2.1/dist/styled-components.js"></script>
<div id="root"></div>
Running the above snippet you can see that the styled components are not styled, despite them extending a "valid" react component.
Is there something I'm missing? Are the docs just incorrect? Can styled only apply styles to an existing react component that is made by a previous styled call?

I don't really know where the confusion lies, you can pass any React component to the styled Higher Order Component.
The issue you have is that you aren't trying to style the BoxFunctional or BoxClass components, but rather you are trying to style the JSX they render. You just need to proxy the className prop through to what each renders.
Styling any Component
The styled method works perfectly on all of your own or any
third-party component, as long as they attach the passed className
prop to a DOM element.
const BoxFunctional = (props) => (
<div className={props.className}>{props.children}</div>
);
class BoxClass extends Component {
render() {
return <div className={this.props.className}>{this.props.children}</div>;
}
}
Demo

Related

Create dynamic tags with Styled Components

Is there a way to create a dynamic tag using styled-components?
For example:
const MyComponent = styled(CustomTag)``;
<MyComponent element="h1" />
You can use as prop by default on components created with styled-components. If in your example CustomTag is also a styled-component that styles a native element (e.g.:)
const CustomTag = styled.h1`
color: red;
`;
then you can do
const MyComponent = styled(CustomTag)`
font-size: 64px;
`;
<MyComponent as="span">Something</MyComponent>
and you'll end up with a <span> tag with font-size of 64px and red text color. Of course you can also use as prop on CustomTag so you don't necessarily need MyComponent.
Maybe this will help you:
Add you code in your typescript
class Test extends HTMLElement {
connectedCallback() {
this.innerHTML = `<h1>Hello World...</h1>`;
this.style.color = "red";
}
}
customElements.define('test', Test);
after compiling refer the js file in you HTML and you can use it like <test></test>

How can React add and remove work together?

I am new to react and am trying to toggle a body class using two different buttons. One is supposed to add a class using an onClick event and the other is supposed to remove the class. Below is an example of my code.
Right now in the console I can see the event fire twice but the class remains. As I stated I am new to React so I know I may be doing this incorrectly.
bodyFixed() {
document.body.classList.add('body-fixed');
}
bodyRelative() {
document.body.classList.remove('body-fixed');
}
You are trying to modify the dom directly like you would with vanilla js or JQuery, but this is not how react is meant to be used. React creates a virtual dom that you create and manage, and then react handle changing the page for you.
I recommend following a guide like this one to learn basic setup and concepts (skip to the part where he uses JSX).
I can further point you in the right direction if you show your whole component file.
You want to toggle a className prop value in the React way.
The React way is having a state prop and having a handler function that will toggle the state value, rather than manipulating the DOM node directly (the way you're doing it).
I would suggest you to take a look at React Main Concepts: Handling events and later once you feel a little bit more comfortable to read about Virtual DOM and Reconciliation in React.
Here's how can you do it:
const { classNames } = window
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isToggled: true
}
this.toggleClass = this.toggleClass.bind(this)
}
toggleClass() {
const { isToggled } = this.state
this.setState({
isToggled: !isToggled
})
}
render() {
const { isToggled } = this.state
const className = classNames({
'body-fixed': isToggled
})
return <div className={className}>
<div>Current `className`: <b>{ className }</b></div>
<button onClick={this.toggleClass}>Toggle class</button>
</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script src="https://unpkg.com/classnames#2.2.6/index.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container"></div>
I was able to use the code I listed earlier. I had my onClick events positioned incorrectly. Here is an example of the code I used:
bodyFixed() {
document.body.classList.add('body-fixed');
}
bodyRelative() {
document.body.classList.remove('body-fixed');
}
<Toggle>
{({on, getTogglerProps, setOn, setOff, toggle, showAlert}) =>
<div>
<button className="search-icon-top" {...getTogglerProps()}>{on ? <img className="times" onClick={this.bodyRelative} src={require('img/times.svg')} alt=" " /> : <i className="fa fa-search" onClick={this.bodyFixed}></i>}</button>
<div>
{on ? <TodaySearchBox /> : ''}
</div>
</div>}
</Toggle>
This is just a start for now. Thank you for the input.
EDIT: I am open to suggestions. Like I said I am new to React.

How can I pass props to base component in styled-component?

As an example, let's say I've a component that can take in props like this:
const testComponent = (props: {isBold: boolean}) => {
if(props.isBold)
return <strong><div>hello</div></strong>
return <div>hello</div>
}
In this case, my example component that can take in props and the result depends on the props given to it.
Now, if I extend this component in styled-components, how can I pass my props into the base component? The idea is something like this:
const styledTestComponent = styled(testComponent({isBold: true}))`
width: 100%;
opacity: 0.5
/* etc etc... */
`
Well, obviously not going to work. This part will fail: styled(testComponent({isBold: true}))
But the idea is that what I want to do is to use CSS to style a particular instance of a component. So in that case, I will need to pass a pre-defined props to the base component, testComponent.
How can I achieve this?
Update:
I've come up with a quick example to illustrate the issue. The code below attempts to style a react component MyCustomImage as a styled-component StyledMyCustomImage. When this is run, you can see that StyledMyCustomImage does render itself as MyCustomImage. However, the CSS styles are not applied.
const MyCustomImage = props => (
<img
src={`https://dummyimage.com/${props.width}x${props.height}/619639/000000`}
/>
);
const StyledMyCustomImage = styled(MyCustomImage)`
border: 2px dotted red;
`;
function App() {
return (
<div className="App">
<h3>Test passing props from styled component to base component</h3>
<StyledMyCustomImage width="600" height="400" />
</div>
);
}
I've created a sandbox for this demo: https://codesandbox.io/s/k21462vjr5
Update 2:
Oh! Thanks to #SteveHolgado's answer, I've gotten it to work! I didn't know styled component will pass the CSS as a prop to its base component! Here's the code after adding in the class name for future reference:
const MyCustomImage = props => (
<img
src={`https://dummyimage.com/${props.width}x${props.height}/619639/000000`}
className={props.className}
/>
);
const StyledMyCustomImage = styled(MyCustomImage)`
border: 2px dotted red;
`;
The sadnbox of the working demo: https://codesandbox.io/s/j4mk0n8xkw
Try this, it should work
const StyledTestComponent = styled(testComponent)`
width: 100%;
opacity: 0.5
/* etc etc... */
`
and pass the prop to instance in this way.
<StyledTestComponent isBold />
Feedbacks are welcome. I have not checked it working, but feels it will work
Note: I checked and it's working. Should work for you.
When you use the styled function like that, your wrapped component will get passed a prop called className, which you need to apply to the element that you want the styles to affect:
const testComponent = (props) => {
return <div className={props.className}>hello</div>
}
You will have access to all props in your styles, which you can use like this:
const styledTestComponent = styled(testComponent)`
width: 100%;
opacity: 0.5;
font-weight: ${props => props.isBold ? "bold" : "normal"};
/* etc etc... */
`

Cannot log DOM elements on the mounted hook of vue.js

For some reason, on the mounted hook I cant seem to log DOM elements on the browser while I am potentially lokking to loop through elements or , as my final resort just work on a specific index of a HTML Collection:
the following is the vue component I am having an issue with:
<template>
<full-page ref="fullpage" id="fullpage" :options="options">
<slider class="section" :auto="false">
<slider-item v-animate-css="'fadeIn'">
<h1 class="mainTitle">PROJECT GORILLA</h1>
</slider-item>
<slider-item v-for="bkg in bkgImg" :style="{backgroundSize:'cover',
backgroundImage: 'url(' + bkg + ')'}">
<h1 class="mainTitle">PROJECT GORILLA</h1>
</slider-item>
</slider>
</full-page>
</template>
<script>
import { Slider, SliderItem } from 'vue-easy-slider'
import pinkBkg from '#/assets/img/IMG_2473.jpg'
import redBkg from '#/assets/img/IMG_4674.jpg'
import blueBkg from '#/assets/img/IMG_4716.jpg'
import greenBkg from '#/assets/img/IMG_2013.jpg'
export default {
data(){
return {
options:{
licenseKey:null
},
bkgImg:[pinkBkg,redBkg,blueBkg,greenBkg]
}
},
components: {
Slider,
SliderItem
},
mounted(){
let slides = document.getElementsByClassName("slider-item");
console.log(slides[0]);
}
}
</script>
<style>
.slider-item:nth-of-type(1) { background-color:black;}
.slider-item > .wrap {
display:flex;
justify-content: center;
align-items:center;
}
h1.mainTitle {
position:fixed;
z-index:99;
color:white !important;
}
.slider-item {
z-index:98 !important;
}
</style>
Bare in mind that I am currently using the webpack template for vue-cli. To be honest jQuery has crossed my mind as a last resort but I really don't want to
resort to that because its is important that the application has a decent performance...
in this case console.log returns undefined. But If I copy and past the code on the browser then it would work fine.
When your component is mounted, it doesn't necessarily mean the child components within it are fully rendered.
As you can see in your template, there are no HTML elements with class="slider-item". I imagine these appear later when the SliderItem components are rendered.
What you can do is add a ref attribute to any element or component you want to reference.
For example
<slider-item ref="sliderItem" v-animate-css="'fadeIn'">
<h1 class="mainTitle">PROJECT GORILLA</h1>
</slider-item>
<slider-item ref="sliderItemRepeater" v-for="bkg in bkgImg"
:style="{backgroundSize:'cover', backgroundImage: 'url(' + bkg + ')'}">
<h1 class="mainTitle">PROJECT GORILLA</h1>
</slider-item>
Then in your mounted hook, you can access
this.$refs.sliderItem // the first, non-repeating component
this.$refs.sliderItemRepeater // an array of the repeated components
See https://v2.vuejs.org/v2/guide/components-edge-cases.html#Accessing-Child-Component-Instances-amp-Child-Elements

--Store DOM elements that I use in React so I don't keep pulling them?

This is for both regular DOM elements and jQuery elements. I want the actual elements to be stored some where in the class. Is there a reference to a common pattern for doing this?
Also, I'm aware that in general you should not use jQuery with React but I need the jQuery functions to make my menu work and don't have time for a refactor but it works fine.
import React from 'react';
import $ from 'jquery';
class MobileMenu extends React.Component {
constructor (props) {
super(props);
this.clickHandler1 = this.clickHandler1.bind(this);
this.clickHandler2 = this.clickHandler2.bind(this);
this.clickHandler3 = this.clickHandler3.bind(this);
}
clickHandler1 () {
this.toggleMenu();
}
clickHandler2 () {
// MenuPage.flip('fave');
this.toggleMenu();
this.toggleMarker($A.el('#nav_fave'));
}
clickHandler3 () {
// MenuPage.flip('splash');
this.toggleMenu();
this.toggleMarker($A.el('#nav_splash'));
}
toggleMarker (current_item) {
if ((this.previous_item !== undefined) && (this.previous_item !== current_item)) {
this.previous_item.style.borderBottom = '';
}
if (this.previous_item !== current_item) {
current_item.style.borderBottom = '3px solid #31baed';
}
this.previous_item = current_item;
}
toggleMenu () {
if (window.getComputedStyle($A.el('#top_menu_list'), null).display === 'block') {
$('#icon_bars').toggleClass('active');
$('#top_menu').slideToggle();
}
}
// ... snip
}
export default MobileMenu
You can try the ref pattern.
Keep in mind that you are using react the wrong way, one of its best features is the super fast rendering due to the virtual DOM and Diff algorithm.
Which you are ruing!!! :)
EDIT as a followup to your comment
The ref attribute will allow you to add an object to your react component class, this object is a reference to the actual DOM element.
So basically you can wrap it with a jQuery method and do whatever you need.
With That being said, again i really advice against that!
An example:
class App extends React.Component {
constructor(props){
super(props);
this.changeColor = this.changeColor.bind(this);
}
changeColor(e){
$(this.myelement).addClass('highlight');
}
render() {
return (
<div>
<h3>Do not use jQuery with React!</h3>
<div ref={(el) => {this.myelement = el}} onClick={this.changeColor}>
Click me!
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.highlight {
border: 2px solid #eee;
background: #333;
color: #ccc;
width: 100px;
height: 50px;
text-align:center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
EDIT #2
You are now asking how to modify elements (adding a css class for example) in react.
Well this is entirely a different question which i'm sure there are several good answers on SO, but i will give you a small example anyway.
I'm using the same code as the example above but now i'm doing it without jQuery.
As for css and styling in general with react i urge you to read about the different approaches which i won't explain here (css modules, styled components etc..).
Keep in mind that this is a really small and simple example and i used 1 of many patterns out there to tackle this challenge.
const MyComponent = ({highlight, onClick}) => {
const cssClassName = highlight && 'highlight'; // this will be undefined or 'highlight'
return(
<div className={cssClassName} onClick={onClick} >Click me for a COMPONENT example!</div>
);
}
class App extends React.Component {
constructor(props){
super(props);
this.state = {
isDivClicked: false,
isComponentClicked: false
}
this.changeDiv = this.changeDiv.bind(this);
this.changeComponent = this.changeComponent.bind(this);
}
changeDiv(e){
this.setState({isDivClicked: true});
}
changeComponent(e){
this.setState({isComponentClicked: true});
}
render() {
const {isDivClicked, isComponentClicked} = this.state;
return (
<div>
<h3>Do not use jQuery with React!</h3>
<div onClick={this.changeDiv} className={isDivClicked && 'highlight'}>
Click me for a simple DIV example!
</div>
<hr/>
<MyComponent onClick={this.changeComponent} highlight={isComponentClicked}/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.highlight {
display:inline-block;
border: 2px solid #eee;
background: #333;
color: #ccc;
width: auto;
height: 50px;
text-align:center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Why not use a basic object array and push them by ID?
I created a simple demo. Maybe there is a more elegant way to do this with react, but using jquery as a helper this is pretty easy.
https://jsfiddle.net/cfnb4fkw/
var domCache = new Array();
function cacheDomObj(domObj){
domCache[$(domObj).attr('id')] = domObj;
}
function getDomObj(id){
return domCache[id];
}
function demo(){
cacheDomObj($("#domElement1"));
alert("Element cached" + domCache.length);
$("#domElement1").remove();
alert("Removed element");
var retreivedElement = getDomObj('domElement1');
$("#container").append(retreivedElement);
}
demo();
You could integrate this into your class and speed it up a little if you wanted to make cacheDomObj more verbose...
function cacheDomObj(id,obj){
domCach[id] = obj;
}

Categories