How to add custom CSS to existing styled component? - reactjs

I have an issue related to styled component.
I have a prewritten Select component and I want to write my own select component by using that.
Here are some codes that I wrote.
in the Comopnent.tsx
...
import Select from './Select'
const StyledSelect = styled(Select)`
// CUSTOMIZED STYLES
height: 25px;
border-radius: 10px;
cursor: default;
`
...
const Component1:React.FC = () => {
...
return (
<StyledSelect
options={[
{
label: t('Hot'),
value: 'hot',
},
{
label: t('APR'),
value: 'apr',
},
{
label: t('Multiplier'),
value: 'multiplier',
},
{
label: t('Earned'),
value: 'earned',
},
{
label: t('Liquidity'),
value: 'liquidity',
},
]}
onChange={handleSortOptionChange}
/>
)
}
...
in the Select.tsx
...
const DropDownContainer = styled.div<{ isOpen: boolean; width: number; height: number }>`
cursor: pointer;
width: ${({ width }) => width}px;
position: relative;
...
`
const Select:React.FunctionComponent<SelectProps> = ({options, onChange}) => {
...
return (
<DropDownContainer isOpen={isOpen} ref={containerRef} {...containerSize}>
{containerSize.width !== 0 && (
<DropDownHeader onClick={toggling}>
<Text>{options[selectedOptionIndex].label}</Text>
</DropDownHeader>
)}
<ArrowDropDownIcon color="text" onClick={toggling} />
<DropDownListContainer>
<DropDownList ref={dropdownRef}>
{options.map((option, index) =>
index !== selectedOptionIndex ? (
<ListItem onClick={onOptionClicked(index)} key={option.label}>
<Text>{option.label}</Text>
</ListItem>
) : null,
)}
</DropDownList>
</DropDownListContainer>
</DropDownContainer>
)
}
...
But CUSTOMIZED STYLES don't work.
I have some similar experience with adding custom CSS to the existing styled component like the above but I don't know why that doesn't work.
What is my fault?

You forgot to accept a property className and set it on needed element.
Below is an example. Pay attention to the className in the props and set it to the container.
To style the component, you need to pass the className props. And in the component where you get that className prop, you need to assign it to the element that needs to be styled, like so:
Notice how Link takes a className and assigns it to Element "a".
const Link = ({ className, children }) => (
<a className={className}>
{children}
</a>
);
const StyledLink = styled(Link)`
color: palevioletred;
font-weight: bold;
`;
For more detail, check official docs.
https://styled-components.com/docs/basics#styling-any-component
const Select:React.FunctionComponent<SelectProps> = ({options, onChange, className}) => {
...
return (
<DropDownContainer className={className} isOpen={isOpen} ref={containerRef} {...containerSize}>
{containerSize.width !== 0 && (
<DropDownHeader onClick={toggling}>
<Text>{options[selectedOptionIndex].label}</Text>
</DropDownHeader>
)}
<ArrowDropDownIcon color="text" onClick={toggling} />
<DropDownListContainer>
<DropDownList ref={dropdownRef}>
{options.map((option, index) =>
index !== selectedOptionIndex ? (
<ListItem onClick={onOptionClicked(index)} key={option.label}>
<Text>{option.label}</Text>
</ListItem>
) : null,
)}
</DropDownList>
</DropDownListContainer>
</DropDownContainer>
)
}
...

Related

Change background color of div by clicking in react

I have three div in the component(which can be more than three as well). I want to change their color when they will be clicked. If again I will click, they will get back their old color. In my code if I am clicking any one div, all div s are changing, Can you help me to do it for particular div?
The code is:
import React,{useState} from 'react'
export default function ChangeColor() {
let [colorState,changeState]=useState(['red','green','blue']);
let [isActive,setIsActive]=useState(true);
return (
<>
{colorState.map((color,index)=>{
return(
<React.Fragment key={index}>
<div style={{width:'100px',height:'100px',backgroundColor:isActive?`${color}`:'yellow' }}
onClick={()=>{isActive?setIsActive(false) :setIsActive(true)}}>
<p>{color}</p>
</div>
</React.Fragment>
)})
}
</>
)
}
All 3 div are changing together because only a single value is used to control the state.
To solve this, you could make isActive an object that contain a Boolean value for each color, so its structure could be something like this:
{red: true, green: false, blue: false}
This way, each of the div can set the styles based on condition like:
backgroundColor: isActive[color] ? 'yellow' : color
Full example: (live demo on stackblitz)
import React, { useState } from 'react';
export default function ChangeColor() {
const [colorState, changeState] = useState(['red', 'green', 'blue']);
const [isActive, setIsActive] = useState({});
const toggleActive = (color) =>
setIsActive((prev) => {
if (prev[color]) return { ...prev, [color]: false };
return { ...prev, [color]: true };
});
return (
<>
{colorState.map((color, index) => {
return (
<React.Fragment key={index}>
<div
style={{
width: '100px',
height: '100px',
backgroundColor: isActive[color] ? 'yellow' : color,
cursor: 'pointer',
}}
onClick={() => toggleActive(color)}
>
<p>{isActive[color] ? 'yellow' : color}</p>
</div>
</React.Fragment>
);
})}
</>
);
}

Show/Hide multiple elements of each button click with matching indexes in React JS

I have a scenario where I have 2 different components ( buttons and an info container). On each button click I am trying to display each matched info container. I am able to achieve the desired functionality in my buttons, but when I pass state back to my other component I am only able to display the matched index. My desired result is if I clicked a button in my nav and it has an active class all my "info container" should remain visible until the "active" class is toggled/removed.
JS:
...
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ onChange, id, styles, discription }) => {
const [toggleThisButton, setToggleThisButton] = useState(false);
const handleClick = (index) => {
setToggleThisButton((prev) => !prev);
onChange(index);
};
return (
<>
<Avatar
className={toggleThisButton ? styles.orange : ""}
onClick={() => handleClick(id)}
>
{id}
</Avatar>
{JSON.stringify(toggleThisButton)}
{/* {toggleThisButton && <div className={styles.info}>{discription}</div> } */}
</>
);
};
const ToggleContainer = ({ discription, className }) => {
return <div className={className}> Content {discription}</div>;
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [value, setValue] = useState(false);
const handleChange = (newValue) => {
setValue(newValue);
console.log("newValue===", newValue);
};
return (
<>
<div className={classes.wrapper}>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem
id={id}
styles={classes}
discription={d}
onChange={handleChange}
/>
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
{data.map((d, id) => {
return (
<ToggleContainer
className={
value === id
? clsx(classes.elWrapper, "active")
: classes.elWrapper
}
key={id}
styles={classes}
discription="Hello"
/>
);
})}
</div>
</>
);
}
Codesanbox:
https://codesandbox.io/s/pedantic-dream-vnbgym?file=/src/App.js:0-2499
Codesandbox : https://codesandbox.io/s/72166087-zu4ev7?file=/src/App.js
You can store the selected tabs in a state. That way you don't need to render 3 (or more) <ToggleContainer>. In <ToggleContainer> pass the selected tabs as props and render the selected tabs content in <ToggleContainer>.
import React, { useState } from "react";
import "./styles.css";
import { makeStyles } from "#material-ui/core/styles";
import Avatar from "#material-ui/core/Avatar";
import { deepOrange } from "#material-ui/core/colors";
import clsx from "clsx";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ onChange, id, styles, discription }) => {
const [toggleThisButton, setToggleThisButton] = useState(false);
const handleClick = (index) => {
onChange(discription, !toggleThisButton);
setToggleThisButton((prev) => !prev);
};
return (
<>
<Avatar
className={toggleThisButton ? styles.orange : ""}
onClick={() => handleClick(id)}
>
{id}
</Avatar>
{JSON.stringify(toggleThisButton)}
{/* {toggleThisButton && <div className={styles.info}>{discription}</div> } */}
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item, idx) => (
<div key={idx}>Content {item}</div>
))}
</div>
);
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
// action : False -> Remove, True -> Add
const handleChange = (val, action) => {
let newVal = [];
if (action) {
// If toggle on, add content in selected state
newVal = [...selected, val];
} else {
// If toggle off, then remove content from selected state
newVal = selected.filter((v) => v !== val);
}
console.log(newVal);
setSelected(newVal);
};
return (
<>
<div className={classes.wrapper}>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem
id={id}
styles={classes}
discription={d}
onChange={handleChange}
/>
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer styles={classes} selected={selected} />
</div>
</>
);
}

How do I provide a container for popover component?

I decided to use react-simple-popover for my popover needs. Documentation is here: https://github.com/dbtek/react-popover. The problem is that the documentation is for stateful components (class components). The container prop takes in this of the current class component, but I am using stateless component with hooks. What do I pass in place of the "this" keyword in this way?
import React, { useRef, useState } from "react";
import styled from "styled-components";
import Popover from "react-simple-popover";
import { HelpQuestion } from "../UI/Common";
const Wrapper = styled.div`
margin-bottom: 1rem;
`;
export const TextInput = styled.input`
border: 1px solid
${(props) => (props.dark ? "#37394B" : props.theme.lightGray)};
padding: 0.5rem 1rem;
width: 100%;
border-radius: 10px;
color: ${(props) => (props.dark ? "white" : "black")};
background-color: ${(props) => (props.dark ? "#37394B" : "white")};
`;
export default function TextField({
label,
value,
onChange,
onClick,
error,
type,
placeholder,
help,
dark,
disabled,
}) {
const [isPopoverOpen, setPopoverOpen] = useState(false);
const popoverRef = useRef(null);
const componentRef = useRef(null);
return (
<Wrapper ref={componentRef}>
<div style={{ display: "flex", alignItems: "center" }}>
<TextInput
value={value}
onChange={!disabled ? onChange : () => {}}
onClick={onClick}
placeholder={placeholder}
type={type}
dark={dark}
disabled={disabled}
/>
{help && (
<div
style={{ marginLeft: "0.5rem" }}
onClick={() => setPopoverOpen(!isPopoverOpen)}
ref={popoverRef}
>
<HelpQuestion className="fas fa-question" />
</div>
)}
</div>
{error && <Error>{error}</Error>}
<Popover
placement="left"
container={componentRef.current} //doesnt work
target={popoverRef}
show={isPopoverOpen}
onHide={() => setPopoverOpen(false)}
>
<p>{help}</p>
</Popover>
</Wrapper>
);
}
How do I provide a container for popover component?
What do I pass in place of the "this" keyword in this way?
No, I don't think you can. View the source.
const Popover = props => {
if (
ReactDOM.findDOMNode(props.container) &&
ReactDOM.findDOMNode(props.container).parentElement.parentElement !==
document.body
) {
ReactDOM.findDOMNode(props.container).style.position = 'relative';
}
return (
<Overlay
show={props.show}
onHide={props.onHide}
placement={props.placement}
container={props.container}
target={p => ReactDOM.findDOMNode(props.target)}
rootClose={props.hideWithOutsideClick}
>
<PopoverContent
showArrow={props.showArrow}
arrowStyle={props.arrowStyle}
innerStyle={props.style}
style={props.containerStyle}
>
{props.children}
</PopoverContent>
</Overlay>
);
};
It doesn't appear to be well maintained and it's using ReactDOM.findDOMNode which is practically deprecated. I tried this and also tried a small class-based component wrapper. Nothing worked. Each time the reported error referred to the current ref value (componentRef.current) not being a react component.
For what its worth I suggest using a more functional component friendly Popover component. Here's an example using Material-UI's Popover component.
function TextField({
label,
value,
onChange,
onClick,
error,
type,
placeholder,
help,
dark,
disabled
}) {
const [isPopoverOpen, setPopoverOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
const clickHandler = (e) => {
setPopoverOpen((open) => !open);
setAnchorEl(e.target);
};
return (
<Wrapper>
<div style={{ display: "flex", alignItems: "center" }}>
<TextInput
value={value}
onChange={!disabled ? onChange : () => {}}
onClick={onClick}
placeholder={placeholder}
type={type}
dark={dark}
disabled={disabled}
/>
{help && (
<div style={{ marginLeft: "0.5rem" }} onClick={clickHandler}>
<HelpOutlineIcon />
</div>
)}
</div>
{error && <Error>{error}</Error>}
<Popover
anchorEl={anchorEl}
open={isPopoverOpen}
onClose={() => setPopoverOpen(false)}
anchorOrigin={{
vertical: "center",
horizontal: "left"
}}
transformOrigin={{
vertical: "center",
horizontal: "right"
}}
>
<p>{help}</p>
</Popover>
</Wrapper>
);
}

Adding image draft-js-image-plugin

I've tried multiple different ways to try to attach an image in draft-js.
My attempts followed the example in the tutorial https://www.draft-js-plugins.com/plugin/image. For the "Add Image Button Example", the full source of which can be found here https://github.com/draft-js-plugins/draft-js-plugins/tree/master/docs/client/components/pages/Image/AddImageEditor. I have also tried to follow this medium article that does not use the image plugin to do a similar thing https://medium.com/#siobhanpmahoney/building-a-rich-text-editor-with-react-and-draft-js-part-2-4-persisting-data-to-server-cd68e81c820.
If I set the initial state with the image already embedded it renders. However, when using a mechanism that adds it later on it doesn't seem to work. I've read some mentions of various version of plugins / draft.js having issues.
My versions:
"draft-js-image-plugin": "^2.0.7",
"draft-js": "^0.11.6",
import { EditorState } from 'draft-js';
import Editor from 'draft-js-plugins-editor';
import createInlineToolbarPlugin, { Separator } from 'draft-js-inline-toolbar-plugin';
import createImagePlugin from 'draft-js-image-plugin';
import 'draft-js/dist/Draft.css';
import 'draft-js-inline-toolbar-plugin/lib/plugin.css';
import {
ItalicButton,
BoldButton,
UnderlineButton,
HeadlineOneButton,
CodeButton,
UnorderedListButton,
OrderedListButton,
BlockquoteButton,
} from 'draft-js-buttons';
interface TextEditorProps {
label?: string;
value: EditorState;
onChange?: (editorState: EditorState) => void;
className?: string;
disabled?: boolean;
}
const useStyles = makeStyles((theme) => ({
label: {
margin: theme.spacing(1)
},
editor: {
boxSizing: 'border-box',
border: '1px solid #ddd',
cursor: 'text',
padding: '16px',
borderRadius: '2px',
marginBottom: '2em',
boxShadow: 'inset 0px 1px 8px -3px #ABABAB',
background: '#fefefe',
minHeight: '50vh'
}
}));
const inlineToolbarPlugin = createInlineToolbarPlugin();
const imagePlugin = createImagePlugin();
const { InlineToolbar } = inlineToolbarPlugin;
const plugins = [inlineToolbarPlugin, imagePlugin];
const TextEditor:React.FunctionComponent<TextEditorProps> = ({label, value, onChange, className, disabled }: TextEditorProps) => {
const editor = React.useRef(null);
const focusEditor = () => {
editor.current.focus();
}
React.useEffect(() => {
focusEditor()
}, []);
const classes = useStyles();
const insertImage = () => {
onChange(imagePlugin.addImage(value, "https://ichef.bbci.co.uk/news/976/cpsprodpb/12A9B/production/_111434467_gettyimages-1143489763.jpg"));
}
return (
<Box className={className} onClick={focusEditor}>
{label && <InputLabel className={classes.label}>{label}</InputLabel>}
<div className="menuButtons">
<button className="inline styleButton">
<i
className="material-icons"
style={{
fontSize: "16px",
textAlign: "center",
padding: "0px",
margin: "0px"
}}
onClick={insertImage}
>
image
</i>
</button>
</div>
<Box className={!disabled && classes.editor} >
<Editor
ref={editor}
editorState={value}
onChange={onChange}
plugins={plugins}
spellCheck={true}
readOnly={disabled}
/>
{<InlineToolbar>
{(externalProps) => (
<>
<BoldButton {...externalProps} />
<ItalicButton {...externalProps} />
<UnderlineButton {...externalProps} />
<CodeButton {...externalProps} />
<Separator {...externalProps} />
<UnorderedListButton {...externalProps} />
<OrderedListButton {...externalProps} />
<BlockquoteButton {...externalProps} />
<HeadlineOneButton {...externalProps} />
</>
)}
</InlineToolbar>}
</Box>
</Box>
)
}
You editorState change is overriding the change from insertImage. You are triggering focusEditor along with onClick insertImage
Don't need additional plugins, if you don't mind getting hands dirty and learning how to insert into content.
<i
className="material-icons"
style={{
fontSize: "16px",
textAlign: "center",
padding: "0px",
margin: "0px"
}}
onClick={() => insertImage('https://ichef.bbci.co.uk/news/976/cpsprodpb/12A9B/production/_111434467_gettyimages-1143489763.jpg')}
>
custom insertImage function, in your case, editorState should be replaced with 'value'
import { EditorState, AtomicBlockUtils } from “draft-js”; //<-- need add this import
const insertImage = ( url ) => {
const contentState = editorState.getCurrentContent();
const contentStateWithEntity = contentState.createEntity(
'IMAGE',
'IMMUTABLE',
{ src: url },)
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const newEditorState = EditorState.set( editorState, { currentContent: contentStateWithEntity });
onChange(AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, '')) //Update the editor state
};
image upload with draftjs

React Avoid Re-Render for Functional Component

I am using Hooks and how can I avoid from re-render? My purpose of asking this quesiton , I am using scroll for overflow item from div height and when I focus on a item using onMouseOver, my funtional component is rendering itself and scroll is returning initial position. Therefore I dont delete last item of div. Could you any advice to avoid re-rendering ?
My component :
import React from 'react'
type removeDisplayItem = {
dataId: number;
display: boolean
}
const PlayerCalendar = () => {
const [removeDisplayData, setRemoveDisplayData] = useState<removeDisplayItem>({ dataId: -1, display: false })
const handleRemove = (dataId: number, display: boolean) => {
const removeData = {
dataId: dataId,
display: display
}
setRemoveDisplayData(removeData)
}
const renderScheduleContentCell = (data: scheduleItem) => {
return (
<>
<div className="cell-content-wrapper" style={removeDisplayData.dataId === data.id && removeDisplayData.display === true ? { display: "none" } : { display: "flex" }}
onMouseOver={() => handleDisplayRemove(data.id, true)}>
<div className="start-time-text">{moment(data.startTime).format("HH:mm")}</div>
<div className="dash"> - </div>
<div className="end-time-text"> {moment(data.endTime).format("HH:mm")} </div>
</div>
<div className="cell-content-wrapper" onMouseLeave={() => handleDisplayRemove(data.id, false)}
style={removeDisplayData.dataId === -1 ? { display: "none" } : removeDisplayData.dataId === data.id && removeDisplayData.display === true ? { display: "flex", justifyContent: "space-between", backgroundColor: "#8c9296", color: "white", cursor: "pointer" } : { display: "none" }}
onClick={() => setCalendarRemoveItemConfirm(true)}>
<div className="dash"> </div>
<div className="start-time-text">Remove</div>
<div className="end-time-text"><i className="fas fa-trash-alt"></i></div>
</div>
</>
)
}
return (
{renderScheduleContentCell(exampleData)}
)
}
export default PlayerCalendar;
Try to use the memo HOC and wrap it around PlayerCalendar component.
React.memo can be use to optimize your component. When you have the same props / state it can be used to memoize the data.

Resources