Why isn't my react component's css updating dynamically? - css

I added redux to create-react-app and i've been trying to get a navigator to work. I do this by having the active "page link" highlighted. The code I use for this is a combination of react hooks (using state to remember current page) and the npm package classNames.
classNames(object['key'] && classes.activeItem)
So here I have object['key'] evaluate to true when that particular item is activated so that the item gains the activeItem class.
When I replace object['key'] with true, it works. When I console.log object['key'] after I click it, it also evaluates to true.
Why isn't this working? Thanks!
import React, { useEffect, memo } from 'react';
import { bindActionCreators, compose } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { withStyles } from '#material-ui/core/styles';
import { loadPage } from './actions';
import { uploadFile } from '../uploadFile/actions';
import _ from 'lodash';
const styles = theme => ({
item: {
paddingTop: 4,
paddingBottom: 4,
color: 'rgba(255, 255, 255, 0.7)',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.08)',
},
},
itemPrimary: {
color: 'inherit',
fontSize: theme.typography.fontSize,
'&$textDense': {
fontSize: theme.typography.fontSize,
},
},
itemActiveItem: {
color: '#4fc3f7',
},
textDense: {}
});
function Navigator(props) {
const { classes, curPage, onUploadFile, onPageChange, dispatch, ...other } = props;
let activePage = {
'invite': false,
}
useEffect(() => {
if(!curPage){
onPageChange('search');
}
activePage = _.mapValues(activePage, () => false);
activePage[curPage] = true
});
return (
<Drawer variant="permanent" {...other}>
<List disablePadding>
<ListItem button className={classNames(classes.logo)}>
<img src={require("assets/img/logo.png")} alt={''}/>
</ListItem>
<ListItem className={classes.categoryHeader} >
<ListItemText classes={{ primary: classes.categoryHeaderPrimary }}>
Files
</ListItemText>
</ListItem>
<ListItem
button
dense
className={classNames(classes.item, activePage['invite'] && classes.itemActiveItem)}
onClick={() => {onPageChange('invite')}}
>
<ListItemIcon><PeopleIcon /></ListItemIcon>
<ListItemText classes={{ primary: classes.itemPrimary, textDense: classes.textDense }}>
Invite To Your Team
</ListItemText>
</ListItem>
</List>
</Drawer>
);
}
Navigator.propTypes = {
classes: PropTypes.object.isRequired,
onPageChange: PropTypes.func.isRequired,
onUploadFile: PropTypes.func.isRequired
};
const mapStateToProps = (state) => {
const { curPage } = state.app;
return { curPage };
};
const mapDispatchToProps = (dispatch) => {
return {
onPageChange: bindActionCreators(loadPage, dispatch),
onUploadFile: bindActionCreators(uploadFile, dispatch),
dispatch
};
};
const withConnect = connect(
mapStateToProps,
mapDispatchToProps
);
export default compose(withConnect, memo, withStyles(styles))(Navigator);

Note that function passed to the useEffect hook is always run after the render.
Your useEffect doesn't cause a re-render for component to see the changes. Only change to state causes a re-render. If you want a re-render, you need to use useState hook first, and then you need to setState from within the useEffect hook. Or, you could just run these two lines as part of a render (removing them from the useEffect hook, putting them outside):
activePage = _.mapValues(activePage, () => false);
activePage[curPage] = true
useEffect(() => {
if(!curPage){
onPageChange('search');
}
});
But as I'm looking at your code, I think you could just use curPage === 'invite' && classes.itemActiveItem instead of activePage['invite'] && classes.itemActiveItem and remove those unnecessary lines related to activePage object. It would make things much easier.

Related

MUI alert component conflicts in css

I'm using Material UI 5.10.11 and the MuiAlert component sometimes goes wrong.
This is what it should looks like when severity='error'
However in some pages, it looks like this
Below are my codes. Can anyone have a look at it and try to figure out what's wrong with my work?
import React, { useState, useEffect } from 'react';
import { Snackbar } from '#mui/material';
import MuiAlert from '#mui/material/Alert';
const Alert = React.forwardRef(function Alert(props, ref) {
return <MuiAlert elevation={24} ref={ref} variant="filled" {...props} />;
});
export default function MessageDialog(props) {
const defaultPosition = {
vertical: 'top',
horizontal: 'center'
};
const autoHideDuration = 5000;
const [open, setOpen] = useState(false);
useEffect(() => {
let timeoutId;
if (props.open) {
setOpen(true);
timeoutId = setTimeout(() => {
setOpen(false);
}, autoHideDuration);
}
return () => {
clearTimeout(timeoutId);
};
}, [props.open]);
return (
<>
<Snackbar
anchorOrigin={{
vertical: defaultPosition.vertical,
horizontal: defaultPosition.horizontal
}}
open={open}
>
<Alert severity={props.type} sx={{ width: '100%' }}>
{props.children}
</Alert>
</Snackbar>
</>
);
}
Many thanks in advance!
The fault is due to the css that I spotted out in the screenshots.
It looks like problem comes from Mui component class name which is MuiPaper-root. In page with white background it overrites because probably some other Mui components have same class name and you are using white background with this component or you have a css file with white background assigned to MuiPaper-root class in that page

Material-ui: Set tab background color based on passed prop to a styledTab

I have prepared this custom tab to style it as I want:
import { withStyles } from "#material-ui/core/styles";
const StyledTab = withStyles((theme) => ({
root: {
backgroundColor: "yellow",
},
}))((props) => {
const { shouldSetBackgroundColorToOrange } = props;
return <Tab {...props} />;
});
This is how it's used:
<StyledTab label={"Room"} shouldSetBackgroundColorToOrange={true} />;
I'd like to set its color to orange based on the shouldSetBackgroundColorToOrange prop that's passed to it.
But, I couldn't find a way to do this.
Have a look at the code below and in this working codesandbox
In using the button but you can easily adopt it in your code
import React from "react";
import { createStyles, makeStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
interface styleProps {
shouldSetBackgroundColorToOrange: boolean;
}
const useStyles = makeStyles((theme) =>
createStyles({
root: {
backgroundColor: ({shouldSetBackgroundColorToOrange}: styleProps) =>
shouldSetBackgroundColorToOrange ? "orange" : "yellow"
}
})
);
function TestComponent() {
const classes = useStyles({ shouldSetBackgroundColorToOrange: true });
return (
<Button variant="contained" className={classes.root}>
Button
</Button>
);
}
export default TestComponent;

changing Material-ui table style in row.getRowProps()

In my react application I'm using Material-UI enhanced table which is based on react-table and I would like to change the style of their rows.
Reading from documentation (https://material-ui.com/api/table-row/) in the component TableRow should be used the prop "classes" to change the style, but in the MaterialUi code props are read this way:
<TableRow {...row.getRowProps()}>
My question is how can I use the prop classes if the TableRow props are added automatically? I thought I needed to have this:
<TableRow classes="rowStyle ">
where rowStyle is:
const styles = {
rowStyle : {
padding: 10,
border: "1px solid red"
}
};
But obviously I can't this way, how can I add "classes" to the getRowProps() and the new style in it?
I couldn't find an explanation or a good example in official documentation or stackOverflow
Many thanks for the help
EnhancedTable.js:
import React from "react";
import Checkbox from "#material-ui/core/Checkbox";
import MaUTable from "#material-ui/core/Table";
import PropTypes from "prop-types";
import TableBody from "#material-ui/core/TableBody";
import TableCell from "#material-ui/core/TableCell";
import TableContainer from "#material-ui/core/TableContainer";
import TableFooter from "#material-ui/core/TableFooter";
import TableHead from "#material-ui/core/TableHead";
import TablePagination from "#material-ui/core/TablePagination";
import TablePaginationActions from "./TablePaginationActions";
import TableRow from "#material-ui/core/TableRow";
import TableSortLabel from "#material-ui/core/TableSortLabel";
import TableToolbar from "./TableToolbar";
import {
useGlobalFilter,
usePagination,
useRowSelect,
useSortBy,
useTable,
} from "react-table";
const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, ...rest }, ref) => {
const defaultRef = React.useRef();
const resolvedRef = ref || defaultRef;
React.useEffect(() => {
resolvedRef.current.indeterminate = indeterminate;
}, [resolvedRef, indeterminate]);
return (
<div>
<Checkbox ref={resolvedRef} {...rest} />
</div>
);
}
);
const inputStyle = {
padding: 0,
margin: 0,
border: 0,
background: "transparent",
};
// Create an editable cell renderer
const EditableCell = ({
value: initialValue,
row: { index },
column: { id },
updateMyData, // This is a custom function that we supplied to our table instance
}) => {
// We need to keep and update the state of the cell normally
const [value, setValue] = React.useState(initialValue);
const onChange = (e) => {
setValue(e.target.value);
};
// We'll only update the external data when the input is blurred
const onBlur = () => {
updateMyData(index, id, value);
};
// If the initialValue is changed externall, sync it up with our state
React.useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return (
<input
style={inputStyle}
value={value}
onChange={onChange}
onBlur={onBlur}
/>
);
};
EditableCell.propTypes = {
cell: PropTypes.shape({
value: PropTypes.any.isRequired,
}),
row: PropTypes.shape({
index: PropTypes.number.isRequired,
}),
column: PropTypes.shape({
id: PropTypes.number.isRequired,
}),
updateMyData: PropTypes.func.isRequired,
};
// Set our editable cell renderer as the default Cell renderer
const defaultColumn = {
Cell: EditableCell,
};
const EnhancedTable = ({
columns,
data,
setData,
updateMyData,
skipPageReset,
}) => {
const {
getTableProps,
headerGroups,
prepareRow,
page,
gotoPage,
setPageSize,
preGlobalFilteredRows,
setGlobalFilter,
state: { pageIndex, pageSize, selectedRowIds, globalFilter },
} = useTable(
{
columns,
data,
defaultColumn,
autoResetPage: !skipPageReset,
// updateMyData isn't part of the API, but
// anything we put into these options will
// automatically be available on the instance.
// That way we can call this function from our
// cell renderer!
updateMyData,
},
useGlobalFilter,
useSortBy,
usePagination,
useRowSelect,
(hooks) => {
hooks.allColumns.push((columns) => [
// Let's make a column for selection
{
id: "selection",
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox. Pagination is a problem since this will select all
// rows even though not all rows are on the current page. The solution should
// be server side pagination. For one, the clients should not download all
// rows in most cases. The client should only download data for the current page.
// In that case, getToggleAllRowsSelectedProps works fine.
Header: ({ getToggleAllRowsSelectedProps }) => (
<div>
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div>
),
},
...columns,
]);
}
);
const handleChangePage = (event, newPage) => {
gotoPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setPageSize(Number(event.target.value));
};
const removeByIndexs = (array, indexs) =>
array.filter((_, i) => !indexs.includes(i));
const deleteUserHandler = (event) => {
const newData = removeByIndexs(
data,
Object.keys(selectedRowIds).map((x) => parseInt(x, 10))
);
setData(newData);
};
const addUserHandler = (user) => {
const newData = data.concat([user]);
setData(newData);
};
// Render the UI for your table
return (
<TableContainer>
<TableToolbar
numSelected={Object.keys(selectedRowIds).length}
deleteUserHandler={deleteUserHandler}
addUserHandler={addUserHandler}
preGlobalFilteredRows={preGlobalFilteredRows}
setGlobalFilter={setGlobalFilter}
globalFilter={globalFilter}
/>
<MaUTable {...getTableProps()}>
<TableHead>
{headerGroups.map((headerGroup) => (
<TableRow {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<TableCell
{...(column.id === "selection"
? column.getHeaderProps()
: column.getHeaderProps(column.getSortByToggleProps()))}
>
{column.render("Header")}
{column.id !== "selection" ? (
<TableSortLabel
active={column.isSorted}
// react-table has a unsorted state which is not treated here
direction={column.isSortedDesc ? "desc" : "asc"}
/>
) : null}
</TableCell>
))}
</TableRow>
))}
</TableHead>
<TableBody>
{page.map((row, i) => {
prepareRow(row);
return (
<TableRow {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<TableCell {...cell.getCellProps()}>
{cell.render("Cell")}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[
5,
10,
25,
{ label: "All", value: data.length },
]}
colSpan={3}
count={data.length}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
ActionsComponent={TablePaginationActions}
/>
</TableRow>
</TableFooter>
</MaUTable>
</TableContainer>
);
};
EnhancedTable.propTypes = {
columns: PropTypes.array.isRequired,
data: PropTypes.array.isRequired,
updateMyData: PropTypes.func.isRequired,
setData: PropTypes.func.isRequired,
skipPageReset: PropTypes.bool.isRequired,
};
export default EnhancedTable;
Correct me if i'm wrong, but i think the first step to your solution is the correct definition of styles for the material-ui element. I can't say for certain if there is another way to generate these styles, but a simple object such as:
const inputStyle = { padding: 0, margin: 0, border: 0, background: "transparent", };
will probably not work. You probably have to use the material styles for that.
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles({
root: {
border: "1px solid red",
padding: 10
},
});
and then you have to use the style hook in the component definition:
const classes = useStyles();
When overriding a material-ui definition this part is the important one to define which part is going to overriden (not allowed to include pictures yet, sorry):
Material-Ui CSS keys for Table Row
Then you can override the style <TableRow classes={{ root: classes.root }}> or in your case maybe more like <TableRow classes={{ root: classes.root }} {...row.getRowProps()}>
An additional problem you might face is that you have to override the style of the TableCells too, because the overlap the TableRow border.
I have here a code sandbox that is by no means perfect, but should help you on the right track: https://codesandbox.io/s/material-demo-535zq?file=/demo.js
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles(() => ({
rowStyle : {
padding: 10,
border: "1px solid red"
}
}));
const EnhancedTable = ()=>{
const classes = useStyles();
return(
<TableRow className={classes.rowStyle}/>
)
}

unable to manage state using redux in react native project

I'm new to react native development. And I have created this sample note application using Redux for react native. In this application user must be able to add a note and that note needs to be managed via redux store.
And I have created required action and reducer for adding note functionality.
However I was not able to add a new note to the store. I tried debugging and when it reaches the "ADD_Note" case in reducer, it jump into default case automatically. And whatever the string value or note that pass via action, it becomes 'undefined' in the reducer.
Please refer my code for adding note screen
import React, {useState, useEffect} from 'react';
import {
StyleSheet,
View,
Text,
TextInput,
Button,
FlatList,
} from 'react-native';
import {useSelector, useDispatch} from 'react-redux';
import * as NoteActions from '../store/actions/Note'; // import note actions
const NoteScreen = () => {
const [note, setNote] = useState('');
const dispatch = useDispatch(); // use dispatch
const noteHandler = text => {
setNote(text);
};
return (
<View style={styles.container}>
<View>
<TextInput
style={styles.textInput}
placeholder="Enter a note"
onChangeText={noteHandler}
value={note}
/>
<Button
title="Add"
onPress={() => {
console.log(note);
dispatch(NoteActions.addNote(note));
}}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
width: '100%',
margin: 10,
},
textInput: {
width: '100%',
marginBottom: 10,
textAlign: 'center',
borderBottomWidth: 1,
},
});
export default NoteScreen;
And below is the action js file.
// action types
export const ADD_NOTE = 'ADD_NOTE'
//action creators
export function addNote(note){
return{
type:ADD_NOTE,
date:note
}
}
And below is the reducer js file
// initial state
const initialState ={
noteList:[]
}
// import actions
import ADD_NOTE from '../actions/Note';
function noteReducer (state=initialState, action){
debugger;
switch(action.type){
case ADD_NOTE:
const newNote = action.data
return{
...state,
noteList: state.noteList, newNote
}
default:
return state;
}
}
export default noteReducer;
Please help me.
Thanks in advance,
Yohan
You need to add layer of dispatch at your action, also watch the typo date instead data
export const addNote = (note) => (dispatch, getState) => {
return dispatch({
type:ADD_NOTE,
data :note
})
}

Changing styles during clicking

I have ReactJS project and I want to change colour of button during clicking. I know that it is a Ripple API but it's very incomprehensible to use it. Could someone advise me how can I do that?
I've tried to create two elements - parent and child - and changed background of child to transparent while clicking. Unfortunately I have also 'classes' object responsible for changing class if button is active and it is just not working.
My code below:
import React, { Component } from 'react';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import PropTypes from 'prop-types';
import styles from './MydButton.style';
class MyButton extends Component {
constructor(props) {
super(props);
this.state = {
isClicked: false
};
}
handleClick = () => {
this.setState({ isClicked: !this.state.isClicked });
}
render() {
const {
classes,
children,
color,
disabled,
className,
onClick,
type,
border,
...props
} = this.props;
const myClass = this.state.isClicked ? 'auxClass' : 'buttonDefaultRoot';
return (
<div className={classes.parentRoot} >
<Button
classes={{
root: disabled
? classes.buttonDisabledRoot
: classes.buttonRoot,
label: disabled
? classes.buttonLabelDisabled
: classes.buttonLabel,
}}
{...props}
onClick={this.handleClick}
className={myClass}
disabled={disabled}
type={type === undefined ? 'button' : type}
>
{children}
</Button>
</div>
)
}
};
MyButton.propTypes = {
children: PropTypes.string.isRequired,
disabled: PropTypes.bool,
classes: PropTypes.object.isRequired,
};
MyButton.defaultProps = {
disabled: false,
};
export default withStyles(styles)(MyButton);
and styles:
const buttonRoot = {
border: 0,
height: 48,
width: '100%',
}
export default theme => ({
buttonDefaultRoot: {
...buttonRoot,
transition: 'all 1s ease-in-out',
backgroundImage: 'linear-gradient(to right, #F59C81, #E65DA2, #E65DA2, #B13A97, #881E8E)',
boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.16)',
backgroundSize: '300% 100%',
marginTop: 0,
'&:hover': {
backgroundPosition: '100% 0%',
transition: 'all 1s ease-in-out',
}
},
parentRoot: {
...buttonRoot,
backgroundColor: 'red',
backgroundSize: '300% 100%',
marginTop: 36,
},
auxClass: {
backgroundImage: 'none',
},
Material UI Core for ReactJS
The documentation is very good. I have updated my answer to accomodate the specific needs of this question. I have also included two general solutions for anyone who stumbles upon this question.
Tailored Solution:
Changes background color of button from classes.buttonDefaultRoot (a color defined by owner of question) to the gradient defined by the owner of this question.
First step, have a variable stored in state. You can call it whatever you want, but I'm calling bgButton. Set this to this.props.classes.buttonDefaultRoot like so:
state = {
bgButton: this.props.classes.buttonDefaultRoot,
}
Next, you want to define your function that will handle the click. Again, call it what you want. I will call it handleClick.
handleClick = () => {
const { classes } = this.props; //this grabs your css style theme
this.setState({ bgButton: classes.parentRoot.auxClass }); //accessing styles
};
A couple of things are happening here. First, I am destructuring props. So, I am creating a new const variable called classes that has the same value as this.props.classes. The classes contains a set of objects that defines your css styles for your buttons, margins, etc. You can access those styles just like you would if you were trying to get the value of a prop in an obj.
In this case you can access your button style by doing, classes.buttonDefaultRoot. That takes care of your handle click function.
Last step: render the button. In your render method you want to grab your bgButton from state like so:
render() {
const { bgButton } = this.state;
Then you want to assign your className of your button to bgButton and add the onClick functionality like this (this follows the Material UI Core documentation):
<Button variant="contained" color="primary" className={classNames(bgButton)} onClick={this.handleClick}>Button Name</Button>
Putting it all together you get this:
import React, { Component } from "react";
import Button from "#material-ui/core/Button";
import PropTypes from "prop-types";
import classNames from "classnames";
import { withStyles } from "#material-ui/core/styles";
export default theme => ({ ... }) //not going to copy all of this
class MyButton extends Component {
state = {
bgButton: null
};
handleClick = () => {
const { classes } = this.props;
this.setState({ bgButton: classes.parentRoot.auxClass });
};
render() {
const { bgButton } = this.state;
return (
<div className={classes.container}>
<Button
variant="contained"
color="primary"
className={classNames(bgButton)}
onClick={this.handleClick}
>
Custom CSS
</Button>
</div>
);
}
}
MyButton.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(MyButton);
General Solution
This solution is for those who want to use the predefined colors, i.e. default, primary, secondary, inherit. This implementation does not need the PropTypes or className imports. This will change the color from the predefined blue to the predefined pink. That's it.
state = {
bgButton: "primary",
}
handleClick = () => {
this.setState({ bgButton: "secondary" });
}
render() {
const { bgButton } = this.state;
return(
...
<Button
onClick = {this.handleClick}
variant = "contained" //checked Material UI documentation
color={bgButton}
> ..etc.
General Solution 2
To accommodate your custom styles to the button, you would have to import PropTypes and classNames and take a similar approach as the tailored solution above. The only difference here will be my syntax and class name. I am closely following the documentation here so you can easily follow along and readjust where necessary.
import React, { Component } from "react";
import Button from "#material-ui/core/Button";
import PropTypes from "prop-types";
import classNames from "classnames";
import { withStyles } from "#material-ui/core/styles";
import purple from "#material-ui/core/colors/purple";
const styles = theme => ({
container: {
display: "flex",
flexWrap: "wrap"
},
margin: {
margin: theme.spacing.unit
},
cssRoot: {
color: theme.palette.getContrastText(purple[500]),
backgroundColor: purple[500],
"&:hover": {
backgroundColor: purple[700]
}
},
bootstrapRoot: {
boxShadow: "none",
textTransform: "none",
fontSize: 16,
padding: "6px 12px",
border: "1px solid",
backgroundColor: "#007bff",
borderColor: "#007bff",
fontFamily: [
"-apple-system",
"BlinkMacSystemFont",
'"Segoe UI"',
"Roboto",
'"Helvetica Neue"',
"Arial",
"sans-serif",
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"'
].join(","),
"&:hover": {
backgroundColor: "#0069d9",
borderColor: "#0062cc"
},
"&:active": {
boxShadow: "none",
backgroundColor: "#0062cc",
borderColor: "#005cbf"
},
"&:focus": {
boxShadow: "0 0 0 0.2rem rgba(0,123,255,.5)"
}
}
});
class MyButton extends Component {
state = {
bgButton: null
};
handleClick = () => {
const { classes } = this.props;
this.setState({ bgButton: classes.cssRoot });
};
render() {
const { classes } = this.props; //this gives you access to all styles defined above, so in your className prop for your HTML tags you can put classes.container, classes.margin, classes.cssRoot, or classes.bootstrapRoot in this example.
const { bgButton } = this.state;
return (
<div className={classes.container}>
<Button
variant="contained"
color="primary"
className={classNames(bgButton)}
onClick={this.handleClick}
>
Custom CSS
</Button>
</div>
);
}
}
MyButton.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(MyButton);
A tip. You no longer need a constructor or to bind methods.
Hope this helps.

Resources