In trying to keep React code as reusable as possible, I have often passed CSS classes as a property in React components. The use case for this is that these components will function exactly the same but can look different depending on where they are in the page.
Is passing a CSS class as a property in a React component acceptable, or are there better ways of accomplishing the use case above?
Quick, simplified example:
const ToolTipButton = ({ buttonClass, onButtonClick, children }) => (
<button
className={buttonClass}
onClick={onButtonClick}
data-toggle="pages-tooltip"
data-placement="top"
title="Do Something"
>
{children}
</button>
);
<ToolTipButton buttonClass={'ButtonClass1'} onButtonClick={this.doesSomething}/>
<ToolTipButton buttonClass={'ButtonClass2'} onButtonClick={this.doesSomething}>
// Text and other stuff
</ToolTipButton>
Css selectors are chainable, so with just pure css, you can have individual styles based on the container. The button component can have a static class such as tool-tip-button, and have the stylesheet scope it to the containing component. E.g.
.tool-tip-button {
color: black;
}
.component-a .tool-tip-button {
color: green;
}
.component-b .tool-tip-button {
color: red;
}
Note that react components can (and are suggested to) have their own individual stylesheet, e.g. ComponentA.css and ComponentB.css, and simply import them:
// ComponentA.js
import './ComponentA.css'
As long as your class definitions are in scope, passing class names as props is perfectly acceptable. In cases where class definitions are not in scope, the alternative is to pass a style definition object. For example:
const ToolTipButton = ({ style, onButtonClick, children }) => (
<button
style={style}
onClick={onButtonClick}
data-toggle="pages-tooltip"
data-placement="top"
title="Do Something"
>
{children}
</button>
);
<ToolTipButton style={{backgroundColor: 'red'}} onButtonClick={this.doesSomething}/>
Some component libraries (e.g Material UI) allow you to pass both a class name and a style definition object as props. Generally it's better just to use a class name (to avoid defining styles in your js) if possible.
Related
I'm in a learning phase of react and I've been trying to nest style of parent in component in child component via class in react.js. How to do it?
What I've tried until now:
function Card(props) {
const classes = 'card' + props.className;
return (
<div className={classes}>
{props.children}
</div>
)
}
export default Card
I applied style on class 'card' in a CSS file.
Parent component JS code:
import Card from './UI/Card';
function ExpenseItem(props){
return (
<Card className="expense-item">
<ExpenseDate date={props.date} />
<div className="expense-item__description">
<h2>{props.title}</h2>
<div className="expense-item__price">{`₹ ${props.amount}`}</div>
</div>
</Card>
)
}
Now, there are various classes in parent component that are styled accordingly. And I'd like to nest those classes in child component for those style to work.
You can see in the code the way I tried to nest the classes, but it isn't working.
const classes = 'card' + props.className;
What am I doing wrong? And how should I correct it?
You missed a space after the card class name, otherwise i don't see any other issues in your code. Try below, hope it works
const classes = 'card ' + props.className;
Just one note, this will make your components dependent on each other. One of best things about React is that it allows you to have separation of concerns. So Card component is doing Card component staff, displaying some information and needs not be dependent on a parent in this case ExpenseItem.
One way to do it is to use styled-components.
I'm trying to add style to an element in my return of a react component, but I want to achieve this without adding a class. My text editor auto fills a style option, but I believe the syntax is wrong, since when trying to add a function on an onClick event, its a little different when its in the return of a react element. For example, instead of
onClick=function()
its
onClick={() => {function()}}
I'm hoping that instead of style={"background-color: green;"} its a different syntax to actually allow style changes once it hits the dom.
In-line styles can be done, and here is a code example as you have not provided one.
for example, lets inline style an h1 tag
<h1 style={{background-color:'green', color:'white'}}>This is a tilte</h1>
more can be found here
additionally, I would not recommend inline styling as it's not industry-standard and can cause your code to become bloted.
Style tags in react can indeed contain a references to functions.
I am not fully sure if you are working with React component classes or component functions, but your syntax can besides as follows. Create a variable called customStyle which will contain a function that returns your required style object:
customStyle = () => { return { color: 'red' } };
You can then reference customStyle inside markup as follows:
<div style={this.customStyle()}>My Element</div>
idont know if i understood your question well, You can achieve what you want by making a style state, then mutate it whatever style you want with setState
const [style, setStyle] = useState({})
const App = () => {
return <div style={style}>
<button onClick={() => setStyle({color: 'red'})}>handler button </button>
</div>
}
i am wondering is there a way to dynamically import a css file into react.
say i have a css file named style-light.css and i have another named style-dark.css.
is there a way to use react state or some other method to dynamically import the css files into the component based on the current value of the state ?
eg
import "../style-light.css" // default import
export default function TestImport(){
const [switch, setSwitch] = useState("light");
if(switch === "dark"){
import "../style-dark.css" // adds the style-dark import below the light one to overwrite the light css.
}
return (
<div>
<button onClick={()=> setSwitch("dark")}> click to get dark css </button>
</div>
)
}
bascially something like that?
any insight will be helpful. thanks
Option 1
For that I would recommend that you use CSS variables and only one CSS file. You can change the values of your variables based on a class applied on your page body. For example:
variables.css
:root {
--textColor: black;
}
[class="dark-theme"] {
--textColor: white;
}
With javascript you can add or remove the class from your html document, like this
component.js
document.documentElement.classList.add('dark-theme')
document.documentElement.classList.remove('dark-theme')
On your components styles you can use the variables with var(--variable)
component.css
.my-component-class {
color: var(--textColor);
}
Option 2
Use some CSS-IN-JS library like styled-components or emotion, you can use a ThemeProvider to change our theme accordingly with some state in your application. See this example here: https://styled-components.com/docs/advanced#theming
I wrote a react component in render props way,it will call children function with 3 react component object ( not sure the name exactly, the variable generated by executing jsx (<div>...</div>) );
<PaginatedTable> Usage example:
<PaginationTable data={data} ...otherprops>
{({ SearchBar, Table, PaginationBar })=>
(<div>
{SearchBar}
{Table}
{PaginationBar}
</div>)
}
</PaginationTable>
with render props, I'm so glad that I can custom these 3 child component object very easily such as rearrange order or adding custom elements between these three.
{({ SearchBar, Table, PaginationBar })=>
(<div>
{PaginationBar}
<h1> my custom search bar text </h1>
{SearchBar}
{Table}
</div>)
}
But now I wish more than arrange order inside , I wish I can move {SearchBar} out of to the same layer of 's sibling 's children such as this picture.
working demo: https://codesandbox.io/s/23q6vlywy
I thought this may be anti-pattern to the unidirectional data flow of React.
Extract {SearchBar} to another independent component then use it as <SearchBar ... /> inside of <ToolBarArea /> is what I learnd from React Doc.
But in this way, I have to do "lifting state up" and write similar function and states already had in <PaginationTable /> like below **text** parts are functions and states already had in <PaginationTable />
class ToolBarArea extends Component{
render(){
return(
<div>
// text
<SearchBar onChange={**this.props.onSearchBarChange**} />
//.... other text or elements
</div>);
}
}
class ContainerArea extends Component {
state={
**searchBarText:'',**
tableData : [{...}, {...}]
}
**onSearchBarTextChange = (event)=>{
this.setState({ searchBarText: event.target.value });
}
filterdTableData = ()=> this.state.tableData.filter(d=>d.name.includes(this.state.searchBarText);
**
}
I really hope there is a way I can simply move the variable {SearchBar} in the render props function out to without knowing is in the parent or parent's sibling or anywhere in the DOM tree.
such as
<ToolBarArea>
{SearchBar} // SearchBar from <PaginationTable />
</ToolBarArea>
Is there a way to reuseonSearchBarTextChange and filtedTableData functions and these **text** codes I already wrote in <PaginationTable /> ?
I believe you hit the nail on the head when you referred to lifting state. If you already wrote a similar function then your best option may be to 'abstract' that function so that it applies to both use cases. You could use a simple flag to differentiate the unique execution each needs. Then finally pass the function down to both components.
If you're adamant about avoiding this approach you could technically get around it by using event listeners to handle data transfer or watch variables in the window but this is for sure an anti-pattern.
So, expect two simple components that I have built:
import {Input} from 'semantic-ui-react';
import {Select} from 'semantic-ui-react';
const CategoriesDropdown = ({categories, onCategorySelected, selectedCategory}) => {
const handleChange = (e, {value})=>{
onCategorySelected(value);
};
return (
<Select placeholder="Select category" search options={categories} onChange={handleChange} value={selectedCategory} />
);
};
const IdentifiersInput = ({identifiers, onIdentifiersChanged}) => {
return (
<Input placeholder="Enter identifiers..." value={identifiers} onChange={onIdentifiersChanged}/>
);
};
Nothing fancy so far.
But now, I am building another component that displays those two in a flexbox row:
<Box>
<CategoriesDropdown categories={categories} selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}/>
<IdentifiersInput identifiers={identifiers} onIdentifiersChanged={this.changeIdentifiers}/>
</Box>
Unfortunately they are both displayed right next to each other without any margin in between.
Usually, I would just add a margin-left style to the second element, but because it is a React component, that doesn't work. Using style={{marginLeft: '20px'}} doesn't work as well, because the IdentifiersInput component doesn't use it.
I know that I can fix it by doing this: <Input style={style} ... inside the IdentifiersInput component.
However, this seems to be a very tedious way of achieving this goal. Basically, I have to add this to every single component I am writing.
I clearly must be missing something here. How am I supposed to apply such layout CSS properties to React components?
I think I understand.
1) Applying CSS directly to React Components does not work--I can confirm that.
2) Passing props down to the low level elements is tedious, confirmed but viable.
Notice hasMargin prop:
<Box>
<CategoriesDropdown
categories={categories}
selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}
/>
<IdentifiersInput
identifiers={identifiers}
onIdentifiersChanged={this.changeIdentifiers}
hasMargin
/>
</Box>
Possible input:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className, hasMargin }) => {
return (
<Input
className={className}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
style={hasMargin ? ({ marginLeft: '0.8rem' }) : ({})}
/>
);
};
NOTE: I do not like style as much as I like adding an additional class because classes can be adjusted via media queries:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className, hasMargin }) => {
const inputPosition = hasMargin ? `${className} margin-sm` : className
return (
<Input
className={inputPosition}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
/>
);
};
If you find inputPosition too verbose as shown above:
className={hasMargin ? `${className} margin-sm` : className}
3) You could accomplish it using a divider Component, sacreligious yet rapidly effective
<Box>
<CategoriesDropdown
categories={categories}
selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}
/>
<div className="divider" />
<IdentifiersInput
identifiers={identifiers}
onIdentifiersChanged={this.changeIdentifiers}
/>
</Box>
You can use media queries and control padding at any breakpoints if desired.
4) CSS pseudo-elements or pseudo-classes, I don't see any mention of them in answers so far.
MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes
CSS Tricks: https://css-tricks.com/pseudo-class-selectors/
Usually, when you have a random collection of DOM elements, you can calculate a way using CSS to wrangle them into the correct position. The list of available pseudo-classes is in that MDN link. It honestly helps to just look at them and reason about potential combinations.
My current issue is I don't know what is in <Box /> other than it probably has a div with display: flex; on it. If all we have to go on is that and the div is called <div className="Box">, maybe some CSS like this will fix it:
.Box {
display: flex;
}
.Box:first-child {
margin-right: 0.8rem;
}
This is why it is extremely important to know exactly what the surrounding elements will or can be, and exactly which CSS classes/IDs are nearby. We are basically trying to hook into something and correctly identify the left child in Box and add margin to the right of it, or target the right child and add margin to the left of it (or depending on everything, target both and split the additional margin onto both).
Remember there is also ::before and ::after. You are welcome to get creative and find a solution that involves position:relative and position: absolute and adds no markup.
I will leave my answer like that for now, because I think either you already thought about pseudo-selectors, or you will quickly find something that works :)
That or the divider is actually quite viable. The fact you can use media queries alleviates you from concern of future management or scalability of the components. I would not say the same about <div style={{}} />.
As your component specializes another single component it would be a good practice to pass any props your wrapper does not care for to the wrapped component. Otherwise you will loose the ability to use the api of the original <Input>component including passing styles to it:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, ...props}) = (
<Input
{...props}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
/>
);
There may be valid cases where you explicitly want to prevent users to be able to pass props to the wrapped component but that does not look like one of those to me.
I clearly must be missing something here. How am I supposed to apply
such layout CSS properties to React components?
You did not miss something. A react component has no generic way to be styled because it is no DOM element. It can have a very complicated and nested DOM representation or no representation at all. So at some point you as the designer of the component have to decided where the styles, ids and class names should be applied. In your case it is as easy as passing these props down and let the <Input> and <Select>component decide. I find that to be quite elegant rather than tedious.
I see several ways to do it, but the easiest I see would be to pass a className to IdentifiersInput like so:
<IdentifiersInput className="marginLeft" identifiers={identifiers} onIdentifiersChanged={this.changeIdentifiers}/>
Inside IdentifiersInput I would just set that class to the Input:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className}) => {
return (
<Input className={className} placeholder="Enter identifiers..." value={identifiers} onChange={onIdentifiersChanged}/>
);
};
Semantic UI's Input element can receive a className prop.
I would then just use CSS or SCSS to add styles to that particular class. In this case, the margin you want.