SOLUTION
Remove the style={{ fill: 'green' }} from the component and add this to the css file:
/*index.css*/
/* INFO: the sidebarleft-button contains a child that is an <svg> */
.sidebarleft-button-active > * {
fill: rgb(192, 192, 192);
}
.sidebarleft-button:hover > * {
fill: rgb(192, 192, 192);
}
PROBLEM(That is now solved)
I am working on a SideBar for my Application. The Sidebar contains Buttons. Each Button contains an Icon. I want to change the color of the icon if I hover with my mouse over the button. I tried many things and I wonder why no re-rendering is triggered because the icon changed and a change means re-rendering. I can see that the style.fill gets changed to blue in the inspector window of my browser.
I show you my code. The comments inside the code may help you.
//sideBarLeft.jsx
import React, { useState } from 'react';
import SideBarButton from './sideBarButton';
import { DIAGRAMTYPE } from '../diagramComponent/diagramUtils';
import { ReactComponent as BarChartIcon } from '../../icon/barChart.svg';
const SideBarLeft = (props) => {
const [btn0Active, setBtn0Active] = useState(true);
const [btn1Active, setBtn1Active] = useState(false);
const deactivateOtherButtonsAndActiveThisButton = (
idOfButtonThatWasClicked
) => {
switch (idOfButtonThatWasClicked) {
case 0:
setBtn1Active(false);
setBtn0Active(true);
break;
case 1:
setBtn0Active(false);
setBtn1Active(true);
break;
default:
setBtn0Active(false);
setBtn1Active(false);
}
};
return (
<div className={'sidebarleft'}>
<SideBarButton
active={btn0Active}
id={0}
deactivateOtherButtonsAndActiveThisButton={
deactivateOtherButtonsAndActiveThisButton
}
icon={<BarChartIcon style={{ fill: 'green' }} />}
diagramType={DIAGRAMTYPE.barChartPos}
/>
<SideBarButton
active={btn1Active}
id={1}
deactivateOtherButtonsAndActiveThisButton={
deactivateOtherButtonsAndActiveThisButton
}
icon={<BarChartIcon style={{ fill: 'green' }} />}
diagramType={DIAGRAMTYPE.barChartWordOccurence}
/>
</div>
);
};
export default SideBarLeft;
import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../../state/index';
const SideBarButton = (props) => {
const dispatch = useDispatch();
const { switchToDiagramType } = bindActionCreators(actionCreators, dispatch);
const [cssClass, setCssClass] = useState('sidebarleft-button');
const onClickFn = () => {
props.deactivateOtherButtonsAndActiveThisButton(props.id);
switchToDiagramType(props.diagramType);
};
const [icon, setIcon] = useState(props.icon);
const buttonHandler = () => {};
const onMouseEnter = (component) => {
console.log('in onMouseEnter()');
const classOfComponent = component.target.classList.value;
if (classOfComponent === 'sidebarleft-button-active') {
console.log('component is active');
} else if (classOfComponent === 'sidebarleft-button') {
console.log('before');
console.log(props.icon); //even without mousenter, the component is already blue...
let changedIcon = props.icon;
props.icon.props.style.fill = 'blue';
setIcon(changedIcon); //in the inspector
//there is props->style->fill == 'blue'
//why does it not get rendered then in blue???
console.log('after');
console.log(changedIcon);
console.log('component is not active');
// console.log(component);
}
};
console.log('beforebefore');
console.log(icon);
return (
<button
onClick={onClickFn}
className={cssClass}
onMouseEnter={(component) => onMouseEnter(component)}
>
{icon}
</button>
);
};
export default SideBarButton;
Why not a css or sass approach?
.sidebarleft-button-active {
&:hover {
fill: blue;
}
}
Related
I want to change the parent of a Node in a way that
<div>
<children/>
</div>
becomes
<div>
<NewParent>
<children/>
</NewParent>
</div>
I need this to put a mui Tooltip above a component that overflows with ellipsis.
I implemented a small algorithm to find the needed element but when I try to use portals for this case this happens. enter image description here
My NewParent becomes the sibbling of the old parent.
Later I learned that usePortal brings the children to the parent and doesn't wrap the parent to the children so my question is what can I do to wrap a new parent to the node and make the old parent be the grandfather as per my first example
Current component
import React, { useRef, useEffect, useState } from 'react';
import { Tooltip } from '#mui/material';
import { Theme } from '#mui/material';
import { makeStyles } from '#mui/styles'
import { GridCellProps, GridCell } from '#mui/x-data-grid';
import { createPortal } from 'react-dom';
const useStyles = makeStyles<Theme>(() => ({
overflowEllipsis: {
width: '100%',
},
}))
const OverflowTip = React.forwardRef(({ children, ...props }: GridCellProps) => {
const [ portaled, setPortaled ] = useState(false)
const textElementRef = useRef<HTMLDivElement>();
const TooltipRef = useRef<HTMLDivElement>();
const classes = useStyles()
const compareSize = () => {
if (!textElementRef.current) {
return
}
const compare =
textElementRef.current.scrollWidth > textElementRef.current.clientWidth;
setHover(compare);
};
const findLowestChildren = (currentElement) => {
if (!currentElement) {
return
}
if (currentElement.children.length === 0) {
console.log(currentElement);
console.log(TooltipRef);
setPortaled(true)
createPortal(currentElement, TooltipRef.current)
currentElement.className += ` ${classes.overflowEllipsis}`
}
const arr = [].slice.call(currentElement.children);
return arr.forEach((ch) => {
if (ch.tagName === 'DIV' || ch.tagName === 'P' || ch.tagName === 'SPAN') {
return findLowestChildren(ch)
}
});
}
// compare once and add resize listener on "componentDidMount"
useEffect(() => {
compareSize();
window.addEventListener('resize', compareSize);
if (!portaled) {
findLowestChildren(textElementRef.current)
}
}, []);
// remove resize listener again on "componentWillUnmount"
useEffect(() => () => {
window.removeEventListener('resize', compareSize);
}, []);
// Define state and function to update the value
const [ hoverStatus, setHover ] = useState(false);
// console.log(props);
return (
<div ref={textElementRef}>
<GridCell
{...props}>
{children}
</GridCell>
<div ref={TooltipRef} className="wwwwwwww"><Tooltip title="QWEW"><></></Tooltip></div>
</div>
);
// return (
// <Tooltip
// title={children}
// disableHoverListener={!hoverStatus}
// >
// <BoxContainer
// ref={textElementRef}
// style={{
// whiteSpace: 'nowrap',
// overflow: 'hidden',
// textOverflow: 'ellipsis',
// }}>
// <GridCell
// {...props}
// >
// {children}
// </GridCell>
// </BoxContainer>
// </Tooltip>
// );
});
export default OverflowTip;
I am new to Redux .I am making an Visual Workflow platform using Reactjs , Redux & React-flow. User can add a node by inputting name of node and it's type in Palette (Right side as shown in picture). I pass the new node name and it's type to Redux dispatch . Dispatching and updating the state is working fine (I have checked it by printing it on console) , state is updating but I am not getting the updated state automatically just like whenever we update a hook in Reactjs it's changes are shown wherever hook variable is used. But here it's not working like that . I am printing the updated state's length in top in middle div (where nodes are shown) it's only showing 0 even I add a new object to state.
I have searched about it on internet and found that Connect function in Redux will help , I also implemented it . But no solution .
I have been searching for it for last 2 days but can't figure out where's the problem .
If anyone knows where's the problem , what I am missing/overlooking . Please let me know , it will be great help.
Graph.js (where I want state to update automatically):
import React, { useState, useEffect } from 'react';
import ReactFlow, { Controls, updateEdge, addEdge } from 'react-flow-renderer';
import { useSelector ,connect} from 'react-redux';
import input from '../actions/input';
const initialElements = [
{
id: '1',
type: 'input',
data: { label: 'Node A' },
position: { x: 250, y: 0 },
}]
function Graphs(props) {
const onLoad = (reactFlowInstance) => reactFlowInstance.fitView();
const [elements, setElements] = useState(initialElements);
// const [state,setState]=useState(useSelector(state => state.nodeReducers))
useEffect(() => {
if (props.elements.length) {
console.log("useEffect in", props.elements)
setElements(els => els.push({
id: (els.length + 1).toString(),
type: 'input',
data: props.elements.data,
position: props.elements.position
}));
return;
}
console.log("outside if ",props.elements)
}, [props.elements.length])
const onEdgeUpdate = (oldEdge, newConnection) =>
setElements((els) => updateEdge(oldEdge, newConnection, els));
const onConnect = (params) => setElements((els) => addEdge(params, els));
return (
<ReactFlow
elements={elements}
onLoad={onLoad}
snapToGrid
onEdgeUpdate={onEdgeUpdate}
onConnect={onConnect}
>
{console.log("in main", props.elements)}
<Controls />
<p>hello props {props.elements.length}</p>
</ReactFlow>
)
}
const mapStateToProps=state=>{
return{
elements:state.nodeReducers
}
}
const mapDisptachToProps=dispatch=>{
return{
nodedispatch:()=>dispatch(input())
}
}
export default connect(mapStateToProps,mapDisptachToProps)(Graphs);
nodeReducers.js (Reducer):
const nodeReducers=(state=[],action)=>{
switch (action.type) {
case "ADD":
state.push(...state,action.NodeData)
console.log("in node reducers",state)
return state;
default:
return state;
}
}
export default nodeReducers;
input.js(Action):
const input = (obj = {}) => {
console.log("in action",obj)
return {
type: "ADD",
NodeData: {
type: 'input',
data: { label: obj.NodeValue },
position: { x: 250, y: 0 }
}
}
}
export default input;
PaletteDiv.js (Right side palette div where I take user input):
import React, { useState } from 'react'
import '../styles/compoStyles/PaletteDiv.css';
import { makeStyles } from '#material-ui/core/styles';
import { FormControl, TextField, InputLabel, Select, MenuItem, Button } from '#material-ui/core';
import { connect } from 'react-redux';
import input from '../actions/input';
function PaletteDiv(props) {
const [nodename, setNodename] = useState();
const [nodetype, setNodetype] = useState();
const [nodevalue, setNodevalue] = React.useState('');
const handleChange = (event) => {
setNodevalue(event.target.value);
setNodetype(event.target.value);
};
const useStyles = makeStyles((theme) => ({
margin: {
margin: theme.spacing(1),
width: "88%"
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
}
}));
const styles = {
saveb: {
float: "left",
margin: "auto"
},
cancelb: {
float: "right"
},
inputfield: {
display: "block",
width: "100%",
margin: "0"
}
}
const classes = useStyles();
const useStyle = makeStyles(styles);
const css = useStyle();
return (
<div id="myPaletteDiv">
<div className="heading">
<h1>Palette</h1>
</div>
<div className="palette">
<label >WorkFlow Name</label>
<TextField id="outlined-basic" fullwidth className={css.inputfield} variant="outlined" onChange={e => setNodename(e.target.value)} />
<label >Type of Node</label>
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel id="demo-simple-select-outlined-label">Node</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={nodevalue}
onChange={handleChange}
label="Age"
>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10}>Step</MenuItem>
<MenuItem value={20}>Condition</MenuItem>
</Select>
<Button variant="contained" color="primary" onClick={(e) => {
e.preventDefault();
const node = {
NodeType: nodetype,
NodeValue: nodename
}
props.nodedispatch(node)
console.log("done dispatching")
}}>
Add Node
</Button>
</FormControl>
</div>
</div>
)
}
const mapStateToProps = state => {
return {
elements: state.nodeReducers
}
}
const mapDisptachToProps = dispatch => {
return {
nodedispatch: (node) => dispatch(input(node))
}
}
export default connect(mapStateToProps, mapDisptachToProps)(PaletteDiv);
ScreenShot :
Here you can see I have a tag in middle div's top. It's showing "hello props 0". I want 0 (zero) to change whenever I add a node .
Thanking You
Yours Truly,
Rishabh Raghwendra
Don't push a data directly into a state into state, instead reassign the state using spreading operators
const nodeReducers = (state = [],action) => {
switch (action.type) {
case "ADD":
state = [...state, action.NodeData];
console.log("in node reducers",state);
return state;
default:
return state;
}
}
export default nodeReducers;
It's because your reducers must be pure functions. Reducer must work with the state, like with immutable data. You have to avoid side effects in your reducers
https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers#rules-of-reducers
I agree with the previous answer, but, as I said earlier, here we can avoid side effect like reassing:
const nodeReducers = (state = [],action) => {
switch (action.type) {
case "ADD":
return [...state, action.NodeData];
default:
return state;
}
}
export default nodeReducers;
Also, according to the best practices, highly recommended to write your action types to constants and use them in your actions and reducers instead of strings.
// actionTypes.js
export const ADD = "ADD";
// then import it in your reducer and action
I have created a custom button component so that I don't have to always reuse them. but I have a problem and it is when executing a function.
The function is in the parent component, and what I want is to execute this function in the same parent component, but since I create a button component then I have to pass the function first to the button component and this is where I have the problem.
if I put func = {hello ()} the function is executed when entering the view, and if I put func = {hello} then clicking the button does nothing.
when I introduce the name of a route it works perfectly, the problem is when introducing a function of the parent component
CustomButtons.js
import React, { useState, useContext } from "react";
import { View } from 'react-native'
import { Button, Text } from 'react-native-elements';
import Icon from 'react-native-vector-icons/FontAwesome';
import { ThemeContext } from 'react-native-elements';
import { useNavigation } from '#react-navigation/native'
export default function CustomButtons(props) {
const { theme } = useContext(ThemeContext);
const navigation = useNavigation()
const themeButtons = {
btnClose: theme.colors.btnClose,
btnMain: theme.colors.btnMain,
btnAction: theme.colors.btnAction
}
const iconName = props.icon
const routeName = props.route
const actionFunc = props.func
const buttonName = props.name
const buttonType = props.type == 'icon' ? 'clear' : props.type == 'out' ? 'outline' : 'solid'
const iconColor = props.iconColor == null ? 'white' : props.iconColor
const buttonTheme = () => {
const theme = props.theme
if (theme == 'main') return themeButtons.btnMain
if (theme == 'close') return themeButtons.btnClose
if (theme == 'sec') return themeButtons.btnAction
}
const btnActionHandle = () => {
if (routeName) return navigation.navigate(routeName)
else {
return actionFunc
}
}
return (
<Button
title={buttonName}
icon={
<Icon
name={iconName}
color={iconColor}
size={15}
/>
}
buttonStyle={{ backgroundColor: buttonTheme() }}
onPress={btnActionHandle}
type={buttonType}
/>
);
}
parent component
.
.
.
function hello() {
console.log('Hello')
}
<CustomButtons
icon="trash"
type="icon"
iconColor="black"
func={hello}
/>
.
.
.
It should be noted that all the rest of the code works as expected except for the onPress = {} in the customButtons.js component
You forgot to execute/invoke/call it.
return actionFunc()
const btnActionHandle = () => {
if (routeName) return navigation.navigate(routeName)
else {
return actionFunc()
}
}
Also the same function can be re-written as below, no need for else block:
const btnActionHandle = () => {
if (routeName) {
return navigation.navigate(routeName)
}
return actionFunc()
}
We have created a notification system that uses the material ui Snackbar with an action button and close button. I'm trying to add a listener event for enter so that specific notification's action will fire and close the Snackbar. I attempted to do this when the component is mounted, but the components mount when the application loads they are just not shown until their state is set to open. This resulted in all the actions attached to the Snackbars firing at once. I then attempted to use a ref but had no success. Below I will show the code for the button that calls the notifications and the notification component itself. I'm looking for suggestions on how to close the active Snackbar and fire off its action with enter without activating the other mounted notifications.
UPDATE: I changed the key from enter to spacebar and it works as desired. It seems the issue lies with the enter key itself.
https://material-ui.com/api/root-ref/#__next
import React from 'react';
import { connect } from 'react-redux';
import { withStyles } from '#material-ui/core/styles';
import IconButton from '#material-ui/core/IconButton';
import DeleteIcon from '#material-ui/icons/Delete';
import Tooltip from '#material-ui/core/Tooltip';
import { NotifierConfirm, enqueueInfo } from '#paragon/notification-tools';
import { deleteDocument } from '../../actions/documents';
import { getSelectedDocument } from '../../selectors/documents';
import { jobIsLocked } from '../../modules/jobLocking'; // eslint-disable-line
const styles = ({
border: {
borderRadius: 0,
},
});
class DeleteDocument extends React.Component {
state = {
deleteDocumentOpen: false,
}
onDeleteFile = () => {
if (jobIsLocked()) {
return;
}
this.setState({ deleteDocumentOpen: true });
}
closeDeleteDocument = () => {
this.setState({ deleteDocumentOpen: false });
};
onConfirmDelete = () => {
this.props.onDeleteFile(this.props.selectedDocument.id);
this.setState({ deleteDocumentOpen: false });
}
render() {
const { classes } = this.props;
return (
<div>
<Tooltip disableFocusListener id="delete-tooltip" title="Delete Document">
<div>
<IconButton
className={`${classes.border} deleteDocumentButton`}
disabled={(this.props.selectedDocument == null)}
onClick={this.onDeleteFile}
>
<DeleteIcon />
</IconButton>
</div>
</Tooltip>
<NotifierConfirm
open={this.state.deleteDocumentOpen}
onClose={this.closeDeleteDocument}
onClick={this.onConfirmDelete}
message="Are you sure you want to DELETE this document?"
buttonText="Delete"
/>
</div>
);
}
}
const mapStateToProps = (state) => {
const selectedDocument = getSelectedDocument(state);
return {
selectedDocument,
};
};
function mapDispatchToProps(dispatch) {
return {
onDeleteFile: (documentId) => {
dispatch(deleteDocument(documentId));
},
enqueueInfo,
};
}
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(DeleteDocument));
import React from 'react';
import { withStyles, WithStyles, StyleRulesCallback } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import Snackbar from '#material-ui/core/Snackbar';
import IconButton from '#material-ui/core/IconButton';
import CloseIcon from '#material-ui/icons/Close';
import RootRef from '#material-ui/core/RootRef';
interface NotifierConfirmProps {
open: boolean;
onClose: any;
onClick: () => void;
message: string;
messageSecondary?: any;
buttonText: string;
}
type OwnProps = NotifierConfirmProps & WithStyles<typeof styles>;
const styles: StyleRulesCallback = () => ({
snackbar: {
marginTop: 85,
zIndex: 10000000,
'& div:first-child': {
'& div:first-child': {
width: '100%',
},
},
},
close: {
padding: 8,
marginLeft: 8,
},
buttonColor: {
backgroundColor: '#F3D06E',
},
messageDiv: {
width: '100%',
}
});
class NotifierConfirmComponent extends React.Component<OwnProps> {
notifierRef: React.RefObject<{}>;
constructor(props: OwnProps) {
super(props);
// create a ref to store the textInput DOM element
this.notifierRef = React.createRef();
this.focusNotifier = this.focusNotifier.bind(this);
}
keyPressHandler = (event: any) => {
if (!this.props.open) return;
if (event.keyCode === 27) {
this.props.onClose();
}
if (event.keyCode === 13) {
this.props.onClick();
}
}
focusNotifier() {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
// this.notifierRef.current.focus(); this will not work
}
componentDidMount() {
document.addEventListener('keydown', this.keyPressHandler, false);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.keyPressHandler, false);
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<RootRef rootRef={this.notifierRef}>
<Snackbar
className={classes.snackbar}
anchorOrigin={{
vertical: 'top',
horizontal: 'center',
}}
open={this.props.open}
onClose={this.props.onClose}
ContentProps={{
'aria-describedby': 'message-id',
}}
message={
<div className={classes.messageDiv} id="message-id">
{this.props.message}<br />
{this.props.messageSecondary}
</div>}
action={[
<Button
className={`${classes.buttonColor} confirmActionButton`}
variant="contained"
key={this.props.buttonText}
size="small"
onClick={this.props.onClick}
>
{this.props.buttonText}
</Button>,
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={this.props.onClose}
>
<CloseIcon />
</IconButton>,
]}
/>
</RootRef>
</React.Fragment>
);
}
}
export const NotifierConfirm = withStyles(styles)(NotifierConfirmComponent);
The answer for this was changing the event listener to keyup instead of
keydown. Deduced this from this post. Why do Enter and Space keys behave differently for buttons?
So I'm importing a custom component TextButton and packaging it inside of another OutlinedButton. I export the class OutlinedButton expecting to see both the props passed and the new styling added to be rendered. However, only the props are being correctly rendered. The extra styling that I added does not appear at all. Any thoughts as to why this occurs?
import React, { Component } from 'react';
import TextButton from './TextButton';
class OutlinedButton extends Component {
render() {
return (
<TextButton {...this.props} style={styles.outlineButtonStyle} />
);
}
}
const styles = {
outlineButtonStyle: {
borderWidth: 1
}
};
export default OutlinedButton;
TextButton class (it's a bit long)
import React, { Component } from 'react';
import { Text, TouchableOpacity } from 'react-native';
import PropTypes from 'prop-types';
class TextButton extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillMount() {}
componentWillReceiveProps(newProps) {
if (newProps.theme !== this.props.theme) {
this.determineTheme(newProps.theme);
}
if (newProps.size !== this.props.size) {
this.determineSize(newProps.size);
}
}
// set the theme
determineTheme = function (theme) {
if (theme === 'primary') {
return {
color: '#0098EE'
};
} else if (theme === 'secondary') {
return {
color: '#E70050'
};
} else if (theme === 'default') {
return {
color: '#E0E0E0'
};
}
return {
color: '#E0E0E0'
};
}
// set the size
determineSize = function (size) {
if (size === 'small') {
return {
fontSize: 16
};
} else if (size === 'medium') {
return {
fontSize: 22
};
} else if (size === 'large') {
return {
fontSize: 28
};
}
return {
fontSize: 22
};
}
render() {
const { onPress, children, theme, size } = this.props;
return (
<TouchableOpacity onPress={onPress}>
<Text style={[this.determineTheme(theme), this.determineSize(size)]}>{children}</Text>
</TouchableOpacity>
);
}
}
TextButton.propTypes = {
onPress: PropTypes.func,
title: PropTypes.string,
theme: PropTypes.string,
size: PropTypes.string
};
export default TextButton;
You are not using the style prop passed down to your TextButton component:
render() {
const { onPress, children, theme, size, style } = this.props;
return (
<TouchableOpacity onPress={onPress} style={style}>
<Text style={[this.determineTheme(theme), this.determineSize(size)]}>{children}</Text>
</TouchableOpacity>
);
}
When you set style as below in <TextButton> Component, you are not setting the style on the component, but passing it as props to the component. So you have to access it in <TextButton> as this.props.style and apply it in the child component as Tholl mentioned below. Hope you got it.
render() {
return (
<TextButton {...this.props} style={styles.outlineButtonStyle} />
);
}
}
const styles = {
outlineButtonStyle: {
borderWidth: 1
}
};
SImple example: https://codesandbox.io/s/wn9455x58