How I can (using react way) add a myCustomClass in div by role attribute (presentation)? Component don't expose any way to add my custom class and I dont change all componets (using css way), only this component.
<div class="css-1dozdou">
<div role="presentation" class="myCustomClass css-l0iinn"><div ..
Well, as I understood you, you have some third-party component, so you can't change its code. Then you should add some "magic" to overcome the problem.
First of all you should define a ref. Add it to the troubled component or its parent div as parameter ref={ref}. From now on you have a DOM object. Next you should search for div with role via native JS and add the class.
const App = () => {
const ref = useRef(null);
useEffect(() => {
const divs = ref.current.querySelector('[role="presentation"]');
if (divs.length) {
divs[0].add('myCustomClass');
}
})
return (
<div>
<h1>Change a class of a div inside child</h1>
<div ref={ref}><ThirdPartyComponent /></div>
</div>
);
};
const ThirdPartyComponent = () => {
return (
<div class="css-1dozdou">
<div role="presentation" class="myCustomClass css-l0iinn">
Some component
</div>
</div>
);
};
Related
Let's say you have a navbar and when you're using this component on your homepage you want it to have a certain background color and display property, but when you use that same navbar component on another page in your application you want to change these CSS properties. Seeing as the component has one CSS file linked how would you change the style of a component depending on where it is being rendered?
My personal favourite method nowadays is styled components. Your component might look something like this:
// NavBar.js
import styled from 'styled-components'
const StyledDiv = styled.div`
width: 100%;
height: 2rem;
background-color: ${props => props.bgColor};
`
const NavBar = (bgColor) => {
return <StyledDiv bgColor={bgColor}>
}
Then to use it in your different contexts you simply pass the color prop:
// homepage.js
<NavBar bgColor="red" />
// otherpage.js
<NavBar bgColor="#123ABC" />
Styled components are becoming a very popular way of doing things, but be aware that there are a huge array of ways you can do this.
https://styled-components.com/
(Code not tested)
Well If you just want to use plain CSS then you can change the className based on route so the styles also changes.
Example:
import { useLocation } from "react-router-dom";
const Navigation = () => {
let location = useLocation();
...
return(
<nav className={location.pathname === "/home" ? "homepage-navbar" : "default-navbar"}>
...
</nav>
)
}
You can write longer condition for multiple pages as well.
Other better thing you can do is pass the location.pathname and value of className as prop.
import { useLocation } from "react-router-dom";
const Home = () => {
let location = useLocation();
...
return (
<>...
<Navigation location={location.pathname} styleClass={"homepage-navbar"}/>
</>
)
}
const Navigation = ({location, styleClass}) => {
...
return(
<nav className={location === "/home" ? styleClass : "default-navbar"}>
...
</nav>
)
}
So now you can pass different values for className from different components and get different styles for the navbar.
i am trying to toggle a class with useState, but i imported the class using module (not globally, xxx.module.css).
is it possible to toggle the class without changing them to global?
example:
import styles from 'xxx.module.css'
function app(){
const [active, setActive] = useState(false);
return()
<div className={active ? "active" : " "}><p>Hy there!</p> </div>
<button onClick={() => setActive(!active)}>toggle me</button>
}
export default app
with a css
.active{
display: flex;
}
its something like that, but if i use module to import css it won't really work since it require {styles.active} (i've already tried changing the class name to {styles.active} its not really working).
is there any way to work around this? thanks!
My solution:
import s from './x.module.css'
import {useState} from 'react'
const App = () => {
const [active, setActive] = useState(false)
return (
<div>
<div className={active ? s.active : null}>Hi world!</div>
<button onClick={()=> setActive(!active)}>Activate!</button>
</div>
)
}
x.module.css:
.active {
color: black;
}
I personally use the classnames library for this:
const className = classnames({ [styles.active]: active });
Alternatively, you can set up the functionality yourself:
const className = active ? styles.active : undefined;
and then: <div className={className}>
You must use className as styles.active and it must not be between quotation marks.
So your division tag must like that:
<div className={active ? styles.active : " "}>
...some JSX...
</div>
PS: The code you write here to specifing the problem have full of errors. I don't think in real code you did this terrible mistakes but I want to indicate this stiutaion.
I have the following class with a function, that opens a modal (open_modal(...)) in a separate file to a component as I have a large number of modals that use this functionality.
import open from "open";
import $ from "jquery";
class ReactHelpers {
static open_webpage(page_url) {
open(page_url);
}
static open_modal(overlay_id, modal_id) {
$(overlay_id).css("display", "block");
$(modal_id).css("display", "block");
}
static close_modal(overlay_id, modal_id) {
$(overlay_id).css("display", "none");
$(modal_id).css("display", "none");
}
}
export default ReactHelpers;
I am trying to assert that the open_modal function has added css to the divs in question as below:
it('should close the modal', function () {
const wrapper = shallow(
<div id="overlay_id">
<div id="modal_id">
<p>modal</p>
</div>
</div>
)
const overlay = wrapper.find('#overlay_id')
const modal = wrapper.find('#modal_id')
ReactHelpers.open_modal(overlay, modal);
console.log('OVERLAY ', overlay);
expect(overlay.prop('style')).toHaveProperty('display', 'block');
expect(modal_style).toHaveProperty('display', 'block');
});
Further, I'm sure to how the open_webpage function would be tested as this is a library function. In my other tests in my other components, I'm mocking this so it's never actually been tested.
Any help is greatly appreciated.
Thanks
To test style of dom elements:
You should mount the component (using mount), instead of just creating it (using shallow).
Since you're changing the style of dom element directly, You should test the style of the dom element (component.getDOMNode().style.display), instead of testing the react style property (component.prop.style).
example:
import $ from "jquery";
it("should create a div and changes its color to red", () => {
const wrap = mount(
<div id="red_el"></div>
);
const el = wrap.find("#red_el").getDOMNode()
$(el).css("color", "red");
expect(el.style.color).toEqual("red");
});
In your case:
it("should open modal", () => {
const wrapper = mount(
<div>
<div id="overlay" style={{ display: "none" }}>
<div id="modal" style={{ display: "none" }}>
overlay
</div>
</div>
</div>
);
const overlay = wrapper.find("#overlay").getDOMNode();
const modal = wrapper.find("#modal").getDOMNode();
ReactHelpers.open_modal(overlay, modal);
expect(overlay.style.display).toEqual("block");
expect(modal.style.display).toEqual("block");
});
See it live on codesandbox (switch to the tests tab to run the tests .)
I'm currently experimenting with StencilJS to create some web components.
Now I know that there is <slot /> and named slots and all that stuff. Coming from React, I guess slot is similar to children in React. You can do a lot of stuff using children in React. Things I often did:
Check if any children are provided
Iterate over children to do something to each child (e.g. wrap it in a div with a class etc.)
How would you do that using slot/web components/stencilJS?
I can get the Host Element of my web component in Stencil using
#Element() hostElement: HTMLElement;
I use my component like
<my-custom-component>
<button>1</button>
<button>2</button>
<button>3</button>
</my-custom-component>
I want to render something like
render() {
return slottedChildren ?
<span>No Elements</span> :
<ul class="my-custom-component">
slottedChildren.map(child => <li class="my-custom-element>{child}</li>)
</ul>;
}
Kind regards
Using slots you don't need to put a condition in your render function. You can put the no children element (in your example the span) inside the slot element and if no children are provided to the slot it will fall back to it.
For example:
render() {
return (
<div>
<slot><span>no elements</span></slot>
</div>
);
}
Answering the comment you wrote - you can do such a thing but with some coding and not out of the box. Every slot element has an assignedNodes function. Using that knowledge and the understanding of Stencil component life cycle you can do something such as:
import {Component, Element, State} from '#stencil/core';
#Component({
tag: 'slotted-element',
styleUrl: 'slotted-element.css',
shadow: true
})
export class SlottedElement {
#Element() host: HTMLDivElement;
#State() children: Array<any> = [];
componentWillLoad() {
let slotted = this.host.shadowRoot.querySelector('slot') as HTMLSlotElement;
this.children = slotted.assignedNodes().filter((node) => { return node.nodeName !== '#text'; });
}
render() {
return (
<div>
<slot />
<ul>
{this.children.map(child => { return <li innerHTML={child.outerHTML}></li>; })}
</ul>
</div>
);
}
}
This is not an optimal solution and it will require that the style of the slot should have display set to none (cause you don't want to show it).
Also, it will only work with simple elements that only need rendering and not requiring events or anything else (cause it only uses them as html string and not as objects).
Thank you for the answer Gil.
I was thinking of something similar before (setting state etc. - because of timing issues that might come up). I didn't like the solution though, because you're then doing a state change within componentDidLoad, which will trigger another load just after the component did load. This seems dirty and unperfomant.
The little bit with innerHTML={child.outerHTML} helped me alot though.
It seems like you can also simply do:
import {Component, Element, State} from '#stencil/core';
#Component({
tag: 'slotted-element',
styleUrl: 'slotted-element.css',
shadow: true
})
export class SlottedElement {
#Element() host: HTMLDivElement;
render() {
return (
<div>
<ul>
{Array.from(this.host.children)
.map(child => <li innerHTML={child.outerHTML} />)}
</ul>
</div>
);
}
}
I thought you might run into timing issues, because during render() the child elements of the host have already been removed to make space for whatever render() returns. But since shadow-dom and light-dom coexist nicely within the host component, I guess there shouldn't be any issues.
I don't really know why you have to use innerHTML though. Coming from React I'm used to doing:
{Array.from(this.host.children)
.map(child => <li>{child}</li>)}
And I thought that is basic JSX syntax and that since Stencil is also using JSX I could do that, too. Doesn't work though. innerHTML does the trick for me. Thanks again.
EDIT: The timing issues I mentioned will appear if you're not using shadow-dom though. Some strange things start to happen an you'll end up with a lot of duplicate children.
Though you can do (might have side effects):
import {Component, Element, State} from '#stencil/core';
#Component({
tag: 'slotted-element',
styleUrl: 'slotted-element.css',
shadow: true
})
export class SlottedElement {
children: Element[];
#Element() host: HTMLDivElement;
componentWillLoad() {
this.children = Array.from(this.host.children);
this.host.innerHTML = '';
}
render() {
return (
<div>
<ul>
{this.children.map(child => <li innerHTML={child.outerHTML} />)}
</ul>
</div>
);
}
}
So what's the best pratice for using className in react. In specific multiple class names. I'm reading through the documentation and I don't really get a clear answer. I've seen things like:
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};
function HelloWorldComponent() {
return <div style={divStyle}>Hello World!</div>;
}
but is there a way for me to do something like this?
import React from 'react';
import ReactDOM from 'react-dom';
import './css/landing.css';
import './css/w3.css';
class Home extends React.Component {
const homeClasses = 'bgimg-1 w3-display-container w3-opacity-min';
render() {
return (
<div className={homeClasses}>
<h1>SUP</h1>
</div>
);
}
}
ReactDOM.render(
<Home />,
document.getElementById('root')
);
or even just list then out in the class name section?
It depends what your component should/will do.
If your component is fairly static you will want to use a built in style like your first example:
const mystyle = {
width: '100%',
...
}
<div style={mystyle}>...</div>
However, there are better ways that allow your component to be more dynamic for re-use, for instance using a class method to generate the style from props passed to it, like in this render function:
render() {
// User's preference or Default Style:
const defaultStyle = {
width: this.props.style.width || '100%',
height: this.props.style.height || '100%',
}
//if this.props.className doesn't exist use mycssclass
const defaultClassName = this.props.className || 'mycssclass'
return (
<div className={defaultClassName} style={defaultStyle}>...</div> )
Following this logic you can use the ternary operator to change the css class name based on props. A common solution is use an isActive state property and use it to determine which class should be used.
render() {
const activeClassName = this.props.className + ' mycomponent-active'
return (
<div className={this.props.isActive ? activeClassName : this.props.className} style={ this.props.style }
</div>);
}
Another common, but complex way to set your component's style is to use a function that will return a given style object, and use it like the first example.
Ultimately, you should decided whether you would like your component to be styled by the designer/user or should look the same no matter where it is used... if it is styled by the designer, just expose the CSS class name from props to the component or define a default:
<div className={this.props.className || 'someclassName'}>...</div>
otherwise, use an example above.
Yes, you can do this! Take a look at the snippet below:
class Example extends React.Component {
cssClasses = 'demo demo2';
render() {
return (
<div className = { this.cssClasses }>
Hello World
</div>
);
}
}
ReactDOM.render( <Example/> , document.getElementById('app'));
.demo {
color: blue
}
.demo2 {
font-size: 20px;
}
<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='app'></div>
Your error was the definition of the homeClasses. You can't declare it like
const homeClasses = '...';
Because, on the way that you did, homeClasses is a property of your component. You should not use const. Just:
homeClasses = '...';
And you forgot to use the this reference, because the homeClasses is an attribute of your component.
<div className={this.homeClasses}>
<h1>SUP</h1>
</div>
Exists and one away for this problem. You can use and read this data from file (example data.json) where can use this data like props of that.