Preventing a multitude of style-related React props using CSS Modules - css

In my project, I have a Text component:
import React from 'react'
import { bool, string, number, element, func, object, any } from 'prop-types'
import cx from 'classnames'
import s from './styles/Text.css'
const Text = (props) => {
const renderMultiLines = () => {
const items = props.children.split(' ')
return items.map(text => <div style={{ marginBottom: '2px' }} className={s.multiLineItem}>{text}</div>)
}
return (
<div
className={cx(
s.text,
props.ellipsis && s.ellipsis,
props.isUpperCase && s.isUpperCase,
props.isBold && s.bold,
props.isExtraBold && s.extraBold,
props.isExtraSpaced && s.extraSpaced,
props.multiLine && s.multiLine,
props.active && s.underlined,
props.active && s.primary,
s[props.color],
s[props.size],
)}
onClick={props.onClick}
style={props.style}
>
{!props.multiLine && props.children}
{props.multiLine && renderMultiLines()}
</div>
)
}
Text.defaultProps = {
isBold: false,
isExtraSpaced: false,
isExtraBold: false,
children: '',
color: '',
ellipsis: false,
size: 'extraExtraSmall',
isUpperCase: false,
onClick: undefined,
style: {},
active: false,
}
Text.propTypes = {
isBold: bool,
isExtraSpaced: bool,
isExtraBold: bool,
children: any,
color: string,
ellipsis: bool,
size: string,
isUpperCase: bool,
onClick: func,
style: object,
active: bool,
}
export default Text
As you can see, the intention is that you pass in props to change the layout of the text.
The idea is that you only ever end up with a limited amount of styles for text, and you never end up with slight variations.
An example of the usage of this component in my project looks like this:
<Text
onClick={() => this.handleGlobalChangeDate(Number(n))}
isBold
size="medium"
color="primary"
active={Number(n) === activeDay && visibleMonth === activeMonth}
>{n}</Text>
This looks messy, and I feel like isBold, size, color etc should not need to be displayed here and passed in as props. Rather, they should be handled in the CSS and refer to the project's variables.css file.
In this way I would like to be able to attach a className to the Text component in question.
However, because it's a component and not simply a div, for example, the className would merely get passed to the component as a prop.
How can I use CSS to deal with this issue instead of using a multitude of style-related props?

Try classnames, a great way to add classes dynamically based upon props or state.
With this you can do:
var btnClass = classNames({
btn: true,
'btn-pressed': this.state.isPressed,
'btn-over': !this.state.isPressed && this.state.isHovered
});
return <button className={btnClass}>{this.props.label}</button>;
Then apply in your component:
<div className={btnClass}>Something</div>

Related

React createElement does not add children of type string when mutiple children are part of inner html

I am using React.createElement(...) to dynamically generate a basic react site using a json file.
The json file is array of JSON objects which represent an element (and its children). The JSON object for an individual element looks like this.
{
"id": "e960f0ad-b2c5-4b0b-9ae0-0f6dd19ca27d",
"render": true,
"component": "small",
"styles":[],
"classes":[],
"children": [
"©2017",
{
"id": "04fa3b1a-2fdd-4e55-9492-870d681187a4",
"render": true,
"component": "strong",
"styles":[],
"classes":[],
"children": [
"Awesome Company"
]
},
", All Rights Reserved"
]
}
this object "should" ideally generate the following html code
<small>©2017 <strong>Awesome Company</strong>, All Rights Reserved</small>
I have created react components to render these html elements for example
Small.jsx
import React from 'react'
const Small = ({ id, className, style, children }) => {
return (
<small id={id} style={style} className={className}>
{children}
</small>
)
}
export default Small
Similarly I have created jsx for other html elements like anchor tag, div, footer, button etc
There is renderer.js which is called by App.js to render each component in the json file
import React from 'react'
import Button from "../components/Button";
import Input from "../components/Input";
import Header from "../components/header/Header";
import Footer from "../components/footer/Footer";
import Div from "../components/containers/Div";
import Article from "../components/containers/article/Article";
import Fragment from "../components/containers/Fragment";
import Anchor from "../components/Anchor";
import Nav from "../components/Nav";
import Heading1 from "../components/typography/Heading1";
import Strong from "../components/typography/Strong";
import Small from "../components/typography/Small";
const componentMap = {
button: Button,
input: Input,
header: Header,
footer: Footer,
div: Div,
article: Article,
fragment: Fragment,
a: Anchor,
nav: Nav,
h1: Heading1,
small: Small,
strong: Strong
};
const generateComponentStyles = (styles) => {
let mappedStyles = {};
styles.forEach(style => {
mappedStyles[style.name] = style.value;
});
return mappedStyles;
}
function renderer(config) {
if (typeof componentMap[config.component] !== "undefined") {
//creating children array for this element
let elementChildren = [];
if(config.children && config.children.length > 0) {
for (let index = 0; index < config.children.length; index++) {
const child = config.children[index];
if(typeof config.children === "string") {
elementChildren.push(child);
} else {
elementChildren.push(renderer(child));
}
}
}
return React.createElement(
componentMap[config.component],
{
id: config.id,
key: config.id,
className: config.classes ? config.classes : null,
style: config.styles ? generateComponentStyles(config.styles) : null
},
elementChildren
);
}
}
export default renderer;
The problem is even when the <small> tag is generated with the inner html of <strong> but it skips inserting the pure text within the small and strong tags, so the elements come out as empty.
This is what is generated on the site
<small id="e960f0ad-b2c5-4b0b-9ae0-0f6dd19ca27d" class=""><strong id="04fa3b1a-2fdd-4e55-9492-870d681187a4" class=""></strong></small>
As you can see it did not insert the text.
If I however do not supply an array to the "children" attribute but just give a simple string then it shows up. This same thing happens on an anchor tag.
Based on this link: https://reactjs.org/docs/react-api.html#createelement the createElement takes the third param as array of children.
I even tried to wrap my text in an empty fragment to pass it but it did not work either.
How do I insert a plain text within the inner html of an anchor, small, strong etc when there are other children to be inserted as well?
This might be a simple one, because as far as I understood it mainly works with the exemption of the string nodes.
Please check the condition:
typeof config.children === "string"
should be
typeof child === "string"

In React, how can I apply a CSS transition on state change, re-mount, or re-render?

Say I have a React functional component with some simple state:
import React, { useState } from 'react'
import { makeStyles } from "#material-ui/core"
export default function Basket() {
const [itemCount, setItemCount] = useState<number>(0)
return (
<div>
<Count count={itemCount} />
<button onClick={() => setItemCount(itemCount + 1)}>
Add One
</button>
</div>
)
}
function Count({count}: {count: number}) {
const classes = useStyles()
return (
<div className={classes.count}>
{count}
</div>
)
}
const useStyles = makeStyles({
count: {
backgroundColor: "yellow",
transition: "backgroundColor 2s ease" // ???
}
}
I want the Count component to apply a property whenever the count changes and then remove it again; say, turn on backgroundColor: yellow for 2 seconds and then gradually fade it over 1 second. What's the simplest way to achieve this?
Note that presumably this could be either triggered by a state change on the parent or by a re-rendering of the child. Alternatively, I could add the key property to <Count/> to force a re-mount of the child:
<Count
key={itemCount}
count={itemCount}
/>
Any of those would be acceptable; I'm looking for the simplest, cleanest solution, hopefully one that doesn't require additional state and is compatible with Material-UI styling APIs.
Just an idea.
const Component = () => {
useEffect(() => {
// will trigger on component mount
return () => {
// will trigger on component umount
}
}, [])
}
...
document.getElementById('transition').classList.add('example')
You can use useEffect along with useRef containing a reference to the element or directly getting it with document.getElementById and then update the transition class that way in component mount/unmount. Not sure if it'll work, I haven't tested it myself.

How to change the colour of ReactSelect component?

Hello I am using the React select component, import ReactSelect, {ValueType} from 'react-select';
Currently my component looks like this:
This is the code for it:
<div>
<ReactSelect
className='react-select react-select-top'
classNamePrefix='react-select'
id='displayLanguage'
menuIsOpen={this.state.openMenu}
menuPortalTarget={document.body}
options={timeOptions}
clearable={false}
onChange={this.onChange}
onKeyDown={this.handleKeyDown}
value={this.state.selectedOption}
onMenuClose={this.handleMenuClose}
onMenuOpen={this.handleMenuOpen}
aria-labelledby='changeInterfaceLanguageLabel'
isDisabled={false}
/>
</div>
I want to change the colour of it to grey to look more like this but I am not sure how to:
Try this out,
found here https://react-select.com/styles
const customStyles = {
option: (provided, state) => ({
...provided,
borderBottom: '1px dotted pink',
color: grey,
padding: 20,
}),
control: () => ({
// none of react-select's styles are passed to <Control />
width: 200,
}),
singleValue: (provided, state) => {
const opacity = state.isDisabled ? 0.5 : 1;
const transition = 'opacity 300ms';
return { ...provided, opacity, transition };
}
}
const App = () => (
<Select
styles={customStyles}
options={...}
/>
);
React-select has a prop styles where you set an object that has a list of predefined properties for styling.
You can find more info here: https://react-select.com/styles
But to makes this more easy for you, in your situation you would write something like this.
<ReactSelect
className='react-select react-select-top'
classNamePrefix='react-select'
id='displayLanguage'
menuIsOpen={this.state.openMenu}
menuPortalTarget={document.body}
options={timeOptions}
clearable={false}
onChange={this.onChange}
onKeyDown={this.handleKeyDown}
value={this.state.selectedOption}
onMenuClose={this.handleMenuClose}
onMenuOpen={this.handleMenuOpen}
aria-labelledby='changeInterfaceLanguageLabel'
isDisabled={false}
styles={{
control: (styles) => ({
...styles,
background: '#eee'
})
}}
/>

Add CSS for html selector based on React state?

I'd like to set overflow-y: hidden for the html selector (not an element) based on whether a React class component state variable is true. Is that possible?
If you mean you want to apply the overflow-y to the actual HTML tag then putting this code in the render worked for me
...
render() {
let html = document.querySelector('html');
this.state.test === "test" ? html.style.overflowY = "hidden" : html.style.overflowY = "visible";
return (
....
)
};
You can do
function MyComponent() {
// Set your state somehow
const [something, setSomething] = useState(initialState)
// Use it in your className`
return <div className={!!something && 'class-name'} />
}
If you have multiple class names to work with, a popular package is (aptly named) classnames. You might use it like so:
import cx from 'classnames'
function MyComponent() {
const [something, setSomething] = useState(initialState)
return <div className={cx({
'some-class' : something // if this is truthy, 'some-class' gets applie
})} />
}
Yes, It's possible. You can do this.
function App() {
const [visible, setVisible] = useState(false);
useEffect(() => {
const htmlSelector = document.querySelector("html");
htmlSelector.style.overflowY = visible ? "unset" : "hidden";
}, [visible]);
return (
<button onClick={() => setVisible(prevState => !prevState)}>
Toggle overflow
</button>
);
}
See the full example on CodeSandbox
You can use the style property to set inline CSS:
<div style={{ overflowY: hide ? 'hidden' : 'auto' }}>

Wordpress Gutenberg autocomplete - saving attributes

I want to develop a custom block that will let the user pick some information from autocomplete. I manage to create the autocomplete component on edit function.
A user can select an item but i don't know how to handle the attribute save.
I'm trying to save the selected item as attribute package_name. I created the onChange function on Autocomplete component but event.target.value is undefined.
Here is my code from block.js
const { __ } = wp.i18n; // Import __() from wp.i18n
const { AlignmentToolbar,
BlockControls,
registerBlockType } = wp.blocks;
const { RichText } = wp.editor;
const { Autocomplete, } =wp.components;
const MyAutocomplete = () => {
const autocompleters = [
{
name: 'fruit',
triggerPrefix: '#',
options: [
{ name: 'Apple', id: 1 },
{ name: 'Orange', id: 2 },
{ name: 'Grapes', id: 3 },
{ name: 'test', id: 4 },
],
getOptionLabel: option => (
<span>
{ option.name }
</span>
),
getOptionKeywords: option => [ option.name ],
isOptionDisabled: option => option.name === 'Grapes',
getOptionCompletion: option => (
<abbr title={ option.name }>{ option.name }</abbr>
),
}
];
function onChangeAuto(newContent){
console.log('autocompletexx '+newContent);
}
function onSelectAuto(event){
console.log(event.target);
console.log( event.target.value);
}
return (
<div>
<Autocomplete completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<div
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
onChange={onChangeAuto }
onSelect={onSelectAuto}
>
</div>
) }
</Autocomplete>
<p class="autocomplete_p">Type # for triggering the autocomplete.</p>
</div>
);
};
registerBlockType( 'residence-gutenberg-block/membership-package-settings', {
title: __( 'Residence - Membership Package Settings' ), // Block title.
icon: 'shield',
category: 'common',
keywords: [
__( 'membership-package-settings' ),
],
attributes:{
package_id:{
type:'string',
select:'p'
},
package_name:{
type:'string',
},
},
edit: function( props ) {
const { attributes: {package_name}, className,setAttributes,isSelected } = props;
return (
<div className={ props.className }>
<form>
<label className="wpresidence_editor_label">Current Package: {package_name}</label>
<MyAutocomplete></MyAutocomplete>
</form>
</div>
);
},
save: function( props ) {
// Rendering in PHP
return null;
},
} );
Passing down onChange, onSelect to the div element won't work, because these attributes are only applicable to the form field elements (as input, select, etc.).
I checked the documentation and the source code and didn't find any details or official approaches for dealing with the case.
However, I'm seeing two possible approaches for getting the selected value:
1. Using Autocomplete onReplace prop
Looking into the Autocomplete's source code, I noticed that onSelect callback invokes onReplace prop with the selected option as array. It may not fit all the cases, but you can give it a try! It may be enough for your case! You can try to add your handler to the onReplace as follows:
<Autocomplete
onReplace={ option => { console.log(option) } }
completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<div
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
/>
) }
</Autocomplete>
2. Listen for <div /> changes manually
You can add onInput, onBlur listeners to the <div />, having an uncontrolled react div component and when the div's value is changed then we can keep the changed value in your parent component's state.
Here's a great discussion, which describes these technique: React.js: onChange event for contentEditable
The good think is that there's already a plugin (based on this discussion) that can do it for you: react-contenteditable.
Firstly you have to convert your <MyAutocomplete /> component to be a statefull (not functional) and then:
import ContentEditable from 'react-contenteditable'
// Boilerplate ...
<Autocomplete completers={ autocompleters }>
{ ( { isExpanded, listBoxId, activeId } ) => (
<ContentEditable
html={this.state.html}
onChange={this.handleChange}
contentEditable
suppressContentEditableWarning
aria-autocomplete="list"
aria-expanded={ isExpanded }
aria-owns={ listBoxId }
aria-activedescendant={ activeId }
/>
) }
</Autocomplete>
Conclusion
I'm surprised that in the Autocomplete's documentation there aren't any details for this case. I guess it's because of the following statement (27.08.2018):
Gutenberg is being developed on GitHub, and you can try an early beta
version today from the plugin repository. Though keep in mind it’s not
fully functional, feature complete, or production ready.
However, one of both mentioned approaches above will help you, until they provide a complete API to work with their components.
I would suggest you to keep wrapping the Wordpress's <Autocomplete /> with your own component - in order to easily refactor your code later, when they release the complete API.
If you have questions, feel free to comment below.

Resources