We need components where the class passed as props should have more priority than the default class.
When passing classes as a prop, the component gives priority to the
class created in his own file.
Text.jsx
// this will be a component in another folder, it will be used in the whole app so it
// should haveDYNAMIC styling
function Text(props) {
const useStyles = makeStyles(theme => ({
default: {
fontSize: 18,
color: "black"
}
}));
const classes = useStyles();
return (
<div className={classNames(classes.default, props.className)}>
{props.children}
</div>
);
}
App.jsx
function App() {
const useStyles = makeStyles(theme => ({
title: {
fontSize: 80,
color: "red"
},
notGoodPractice: {
fontSize: "80px !important"
}
}));
const classes = useStyles();
return (
<div className="App">
<Text className={classes.title}>Text in here is 18px</Text>
<Text className={classes.notGoodPractice}>
Text in here is 80px
</Text>
</div>
);
}
React Snippet => CodeSandBox
You can prioritise classes passed as props this way.
Just make sure you don't apply makeStyles on it so that you can access them correctly in child.
import React from "react";
import ReactDOM from "react-dom";
import { makeStyles } from "#material-ui/core/styles";
// this is how we will use the Text component
function App() {
const style = {
title: {
fontSize: "80px",
color: "red",
"#media (max-width: 767px)": {
color: "green"
}
},
notGoodPractice: {
fontSize: "80px"
}
};
return (
<div className="App">
<Text class1={style.title}>Title should be with size 80px</Text>
<Text class1={style.notGoodPractice}>Title should be with size 80px</Text>
</div>
);
}
// this will be a component in another folder, it will be used throw the in app so it should be as DYNAMIC as possible
function Text(props) {
const useStyles = makeStyles(theme => ({
default1: {
fontSize: 18,
color: "black"
},
priorityClass: props => props.class1
}));
const { default1, priorityClass } = useStyles(props);
return <div className={`${default1} ${priorityClass}`}>{props.children}</div>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Check out live sandbox https://codesandbox.io/s/zen-voice-mb60t
Related
I'm trying to display the latestMessageText variable as bold and black if the props meet a certain condition. The useEffect method works, an ugly solution; How do I use Material UI's useStyle hook to execute my goal. I've tried passing props to useStyle, using them inside the makeStyles function, to no effect.
import React, { useEffect, useRef } from "react";
import { Box, Typography } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
justifyContent: "space-between",
marginLeft: 20,
flexGrow: 1,
},
username: {
fontWeight: "bold",
letterSpacing: -0.2,
},
previewText: {
fontSize: 12,
// color: "#9CADC8",
letterSpacing: -0.17,
color: (messages, otherUser) => {
if (messages.length && otherUser) {
const lastMessage = messages[messages.length - 1];
const { senderId, receiverHasRead } = lastMessage;
if (senderId === otherUser.id && !receiverHasRead) {
return 'black'
}
return "#9CADC8"
}
}
},
black: {
color: 'black',
fontWeight: 700,
}
}));
const ChatContent = (props) => {
const { conversation } = props;
const { latestMessageText, otherUser, messages } = conversation;
const classes = useStyles(messages, otherUser);
return (
<Box className={classes.root}>
<Box>
<Typography className={classes.username}>
{otherUser.username}
</Typography>
<Typography className={classes.previewText}>
{latestMessageText}
</Typography>
</Box>
</Box>
);
};
export default ChatContent;
I recommend here using A tiny (228B) utility for constructing className strings conditionally which is clsx.
I provided solution here.
First method with {[yourclass]: conditon} syntax:
import React, { useEffect, useRef } from "react";
import { Box, Typography } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
import clsx from 'clsx';
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
justifyContent: "space-between",
marginLeft: 20,
flexGrow: 1,
},
username: {
fontWeight: "bold",
letterSpacing: -0.2,
},
previewText: {
fontSize: 12,
color: "#9CADC8",
letterSpacing: -0.17,
},
black: {
color: 'black',
fontWeight: 700,
}
}));
const ChatContent = (props) => {
const classes = useStyles();
const typeographyEl = useRef();
const { conversation } = props;
const { latestMessageText, otherUser, messages } = conversation;
const lastMessage = messages?.length && messages[messages.length - 1];
const { senderId, receiverHasRead } = lastMessage;
return (
<Box className={classes.root}>
<Box>
<Typography className={classes.username}>
{otherUser.username}
</Typography>
<Typography ref={typeographyEl} className={clsx(classes.previewText, {[classes.black]: senderId === otherUser.id && !receiverHasRead})}>
{latestMessageText}
</Typography>
</Box>
</Box>
);
};
export default ChatContent;
Or second method with {conditon && yourstyle} syntax:
<Typography ref={typeographyEl}
className={clsx(classes.previewText, {(senderId === otherUser.id && !receiverHasRead) && classes.black})}>
{latestMessageText}
</Typography>
That's not the React way of handling this sort of function. You need to get the condition in a state variable (if you expect it to change by user interaction) or a constant variable otherwise, and set the class based on that.
const [messageRead, setMessageRead] = useState(false);
useEffect(() => {
if (messages.length) {
const { senderId, receiverHasRead } = messages[messages.length - 1];
setMessageRead(senderId === otherUser.id && !receiverHasRead);
}
}, [messages, otherUser]);
JSX
<Typography className={`${classes.previewText} ${messageRead ? classes.black : ''}`}>
{latestMessageText}
</Typography>
You can use the classnames or clsx npm package to simplify the ternary condition.
This question already has an answer here:
How do you get Material-UI Drawer to highlight the page it is currently at?
(1 answer)
Closed 1 year ago.
I'm using react-router and material UI. I integrated the routes with my material UI persist drawer list items with some custom styling like when I click on a list item it highlights it. But I'm facing an issue when I refresh the page my selected list item gets reset, even though I'm still on the same page. Can anyone tell me how do I persist the selected list item even if a page gets refreshed?
Inshort: Issue- when I refresh the page my drawer list item selected color get reset to top item even though I'm on the same page.
Here is the gif to demonstrate my issue
Here is my sandbox code link
Below is the code for the same
Note: I would suggest you go through my code sandbox it'll be better for you to lookup in code. Thank you
App.js
import { Switch, Route } from "react-router-dom";
import Login from "./pages/Login";
import Dashboard from "./pages/Dashboard";
import "./styles.css";
export default function App() {
return (
<div className="App">
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route path="/" exact component={Login} />
</Switch>
</div>
);
}
Dashboard.jxs for nested routes in dashboard
import { Switch, Redirect, Route, useRouteMatch } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import NavBar from "../components/NavBar";
const Dashboard = () => {
const { path } = useRouteMatch();
return (
<>
<NavBar>
<Switch>
<Route path={`${path}/about`} component={About} />
<Route path={`${path}/home`} component={Home} />
<Redirect to={`${path}/home`} />
</Switch>
</NavBar>
</>
);
};
export default Dashboard;
NavBar.js
import React from "react";
import clsx from "clsx";
import { makeStyles, useTheme } from "#material-ui/core";
import CssBaseline from "#material-ui/core/CssBaseline";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
import IconButton from "#material-ui/core/IconButton";
import MenuIcon from "#material-ui/icons/Menu";
import { useRouteMatch } from "react-router-dom";
import SideDrawer from "./SideDrawer";
const drawerWidth = 240;
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
appBar: {
transition: theme.transitions.create(["margin", "width"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
},
appBarShift: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
transition: theme.transitions.create(["margin", "width"], {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen
})
},
menuButton: {
marginRight: theme.spacing(2)
},
hide: {
display: "none"
},
drawer: {
width: drawerWidth,
flexShrink: 0
},
drawerPaper: {
width: drawerWidth
},
drawerHeader: {
display: "flex",
alignItems: "center",
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
justifyContent: "flex-end"
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
marginLeft: -drawerWidth
},
contentShift: {
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen
}),
marginLeft: 0
}
}));
export default function NavBar({ children }) {
const classes = useStyles();
const theme = useTheme();
const [open, setOpen] = React.useState(true);
const { url } = useRouteMatch();
const handleDrawerOpen = () => {
if (!open) {
setOpen(true);
} else {
setOpen(false);
}
};
return (
<div className={classes.root}>
<CssBaseline />
<AppBar
position="fixed"
className={clsx(classes.appBar, {
[classes.appBarShift]: open
})}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
className={clsx(classes.menuButton)}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap>
Persistent drawer
</Typography>
</Toolbar>
</AppBar>
{/* child compoent to render side drawer and state is passing to open & close */}
<SideDrawer open={open} />
<main
className={clsx(classes.content, {
[classes.contentShift]: open
})}
>
<div className={classes.drawerHeader} />
{children}
</main>
</div>
);
}
SideDrawer.js its a child component inside NavBar.js
import {
useTheme,
Divider,
Drawer,
IconButton,
List,
ListItem,
ListItemIcon,
makeStyles
} from "#material-ui/core";
import React from "react";
import ChevronLeftIcon from "#material-ui/icons/ChevronLeft";
import ChevronRightIcon from "#material-ui/icons/ChevronRight";
import DrawerList from "./DrawerList";
const drawerWidth = 240;
const useStyles = makeStyles((theme) => ({
drawer: {
width: drawerWidth,
flexShrink: 0
},
drawerPaper: {
width: drawerWidth
},
drawerHeader: {
display: "flex",
alignItems: "center",
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
justifyContent: "flex-end"
}
}));
const SideDrawer = (props) => {
const theme = useTheme();
const classes = useStyles();
const { open } = props;
return (
<Drawer
className={classes.drawer}
variant="persistent"
anchor="left"
open={open}
classes={{
paper: classes.drawerPaper
}}
>
<div className={classes.drawerHeader}>
<h1>Header</h1>
</div>
<Divider />
<List>
{/* my component to render list of icons in side drawer */}
<DrawerList />
</List>
</Drawer>
);
};
export default SideDrawer;
DrawerList.js child component of SideDrawer.js Issue is arising here
import React, { useState } from "react";
import ListItem from "#material-ui/core/ListItem";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import { DashboardOutlined, AddCircleOutline } from "#material-ui/icons";
import ListItemText from "#material-ui/core/ListItemText";
import { makeStyles, Typography } from "#material-ui/core";
import Box from "#material-ui/core/Box";
import { Link, useRouteMatch } from "react-router-dom";
const useStyles = makeStyles((theme) => ({
root: {
marginTop: theme.spacing(1)
},
iconStyle: {
margin: theme.spacing(0, 0),
color: "#676767"
},
iconTitle: {
margin: theme.spacing(0, 0, 0, 1),
color: "#676767"
},
listItem: {
"&.Mui-selected": {
// it is used to change external svg color during click
"& path": {
fill: "#fff"
},
margin: theme.spacing(1.5, 1)
}
}
}));
const DrawerList = ({ children }) => {
console.log(children);
const [selectedIndex, setSelectedIndex] = useState(0);
const classes = useStyles();
const { url } = useRouteMatch();
const itemList = [
{
text: "Home",
icon: <DashboardOutlined />,
keys: "home",
to: `${url}/home`
},
{
text: "Appointment",
icon: <AddCircleOutline />,
keys: "about",
to: `${url}/about`
}
];
const ListData = () =>
itemList.map((item, index) => {
const { text, icon, to, keys } = item;
return (
<ListItem
className={classes.listItem}
button
key={keys}
to={to}
component={Link}
selected={index === selectedIndex}
onClick={(e) => handleListItemClick(e, index)}
style={
selectedIndex === index
? {
background: "#3f51b5",
width: 200,
marginLeft: 8,
paddingLeft: 10,
borderRadius: 4,
boxShadow: "2px 3px 6px rgba(0, 0, 0, 0.3)"
}
: {}
}
>
<ListItemIcon
className={classes.iconStyle}
style={selectedIndex === index ? { color: "#fff" } : {}}
>
{icon}
<ListItemText>
<Typography
component="div"
className={classes.iconTitle}
style={selectedIndex === index ? { color: "#fff" } : {}}
>
<Box fontWeight={700} fontSize={13.8}>
{text}
</Box>
</Typography>
</ListItemText>
</ListItemIcon>
</ListItem>
);
});
const handleListItemClick = (e, index) => {
setSelectedIndex(index);
};
return (
<div className={classes.root}>
<ListData />
</div>
);
};
export default DrawerList;
You need to track the url to show the selected items instead of a useState that gets reseted:
const { url } = useRouteMatch()
const {pathname} = useLocation();
const itemList = [
{
text: "Home",
icon: <DashboardOutlined />,
keys: "home",
to: `${url}/home`
},
{
text: "About",
icon: <AddCircleOutline />,
keys: "about",
to: `${url}/about`
}
];
...
selected={pathname === to}
To persist data between page refresh, you can use localStorage API.
You need to initialize your state with the value from localStorage. And whenever you update your react state, you also need to update the value in localStorage, so that after page is refreshed, your component state gets initialized with this stored value.
For eg:
const storedOpen = JSON.parse(localStorage.getItem('drawerOpen'));
const [open, setOpen] = React.useState(storedOpen);
const handleDrawerOpen = () => {
if (!open) {
setOpen(true);
localStorage.setItem('drawerOpen', true);
} else {
setOpen(false);
localStorage.setItem('drawerOpen', false);
}
};
The following code, adapted from Material-UI docs for customizing Switch allows to set the switch color to blue:
import React from 'react'
import Switch from '#material-ui/core/Switch'
import {withStyles} from '#material-ui/core/styles'
const ColoredSwitch = withStyles({
switchBase: {
'&$checked': {
color: 'blue',
},
},
checked: {},
track: {},
})(Switch)
But when trying to adapt it so that the color can be set by component properties, it just doesn't work. Event the following code (which is only pseudo-dynamic) renders to a default switch:
const ColoredSwitch = withStyles({
switchBase: {
'&$checked': {
color: props => 'blue',
},
},
checked: {},
track: {},
})(Switch)
I guess I must be doing something wrong but can't figure out what.
Follow this example for passing props if you must use withStyles HOC: https://material-ui.com/styles/basics/#adapting-the-higher-order-component-api
const ColoredSwitch = withStyles({
switchBase: {
"&.Mui-checked": {
color: (props) => props.customchecked
}
},
checked: {},
track: {}
})((props) => {
const { classes, ...other } = props;
return <Switch classes={{ switchBase: classes.switchBase }} {...other} />;
});
You can also use makeStyles
const useStyles = makeStyles({
switchBaseChecked: {
"&.Mui-checked": {
color: (props) => props.color
}
}
});
export default function Switches() {
const props = { color: "green" };
const classes = useStyles(props);
return (
<Switch
color="primary"
classes={{
checked: classes.switchBaseChecked
}}
/>
);
}
I'm implementing a dark mode theme to understand React context. I have the same code for my heading component and it works fine. When I try to add the same for my main tag I get type error: _useContext is undefined.
import React, { useContext } from 'react';
import Heading from './heading/heading';
import ThemeToggle from './heading/themeToggle';
import ThemeContextProvider from './context/ThemeContex';
import './App.css';
import { ThemeContext } from './context/ThemeContex';
const App = () => {
const { light, dark, isLightTheme } = useContext(ThemeContext);
const theme = isLightTheme ? light : dark;
return (
<>
<ThemeContextProvider>
<div className="grid">
<>
<Heading />
<ThemeToggle />
</>
<main style={{ background: theme.bh, color: theme.color }}>
<div className="first-container">
<img src={require('./img/madeInAbyss.jpeg')} />
</div>
<div className="second-container"></div>
</main>
</div>
</ThemeContextProvider>
</>
);
};
export default App;
here is the context provider file which just has a color theme object and a state to toggle between dark and light mode
import React, { createContext, useState } from 'react';
export const ThemeContext = createContext();
const ThemeContextProvider = props => {
const [isLightTheme, setIsLightTheme] = useState(true);
const colorTheme = {
light: { ui: '#ddd', bg: '#eee' },
dark: { color: '#fff', bg: '#15202b' }
};
console.log(colorTheme);
const toggleTheme = () => {
setIsLightTheme(!isLightTheme);
};
return (
<ThemeContext.Provider
value={{
...colorTheme,
isLightTheme: isLightTheme,
toggleTheme: toggleTheme
}}>
{props.children}
</ThemeContext.Provider>
);
};
export default ThemeContextProvider;
You using ThemeContext value before it is initialized within ThemeContextProvider.
const App = () => {
// ThemeContext initial value is undefined (createContext())
// will throw a runtime error
const { light, dark, isLightTheme } = useContext(ThemeContext);
return (
<ThemeContextProvider>
{/* ThemeContext initialized only on ThemeContextProvider render */}
{/* after .Provider value is supplied */}
</ThemeContextProvider>
);
};
To fix it, provide an initial value:
// Should be in an outer scope.
const colorTheme = {
light: { ui: '#ddd', bg: '#eee' },
dark: { color: '#fff', bg: '#15202b' },
isLightTheme: true,
};
export const ThemeContext = createContext(colorTheme);
const ThemeContextProvider = props => {
...
return (
<ThemeContext.Provider
value={...}>
{props.children}
</ThemeContext.Provider>
);
};
I am new at React Native.
I'm working on a code that will change the background color of a box (card) according to the data that I will get from API. I want to check first if the title is like 'Taylor' make background red , if it is 'Fearless' make it green and so on.
Here is the API that I got information from :
http://rallycoding.herokuapp.com/api/music_albums
This is the code divided into several files.
First of them index.js
// Import a library to help to create a component
import React from 'react';
import { Text, AppRegistry, View } from 'react-native';
import Header from './src/components/header.js';
import AlbumList from './src/components/AlbumList.js'
// create a component
const App = () => (
<View>
<Header headerText={'Smart Parking'}/>
<AlbumList />
</View>
);
//render it to the device
AppRegistry.registerComponent('albums2', () => App);
second is AlbumList.js
import React, { Component } from 'react';
import { View } from 'react-native';
import axios from 'axios';
import AlbumDetail from './AlbumDetail.js'
class AlbumList extends Component {
state = { albums: [] };
componentWillMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response => this.setState({ albums: response.data }) );
}
renderAlbums() {
return this.state.albums.map(album =>
<AlbumDetail key={album.title} album={album} />
);
}
render() {
return(
<View>
{this.renderAlbums()}
</View>
);
}
}
export default AlbumList;
3rd is AlbumDetail.js
import React from 'react';
import {Text, View} from 'react-native';
import Card from './Card.js'
const AlbumDetail = (props) => {
return(
<Card>
<Text> {props.album.title} </Text>
</Card>
);
};
export default AlbumDetail;
4th is card which I need to change background of it
import React from 'react';
import { View } from 'react-native';
const Card = (props) => {
return (
<View style={styles.containerStyle}>
{props.children}
</View>
);
};
const styles = {
containerStyle:{
borderWidth: 1,
borderRadius: 2,
backgroundColor: '#ddd',
borderBottomWidth: 0,
shadowColor: '#000',
shadowOffset: {width: 0, height:2 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 1,
marginLeft: 5,
marginRight: 5,
marginTop: 10
}
};
export default Card;
last one is header
// Import libraries for making components
import React from 'react';
import { Text, View } from 'react-native';
// make a components
const Header = (props) => {
const { textStyle, viewStyle } = styles;
return(
<View style={viewStyle}>
<Text style={textStyle}>{props.headerText}</Text>
</View>
)
};
const styles ={
viewStyle:{
backgroundColor:'orange',
justifyContent: 'center',
alignItems: 'center',
height: 60,
},
textStyle: {
fontSize: 20
}
};
// make the component to the other part of the app
export default Header;
Basically you need to pass the title of the album as prop to the Card from the AlbumDetails component and then on each Card calculate the color to use and pass it in the style like this:
// AlbumDetails.js component
import React from 'react';
import {Text, View} from 'react-native';
import Card from './Card.js'
const AlbumDetail = (props) => {
return(
<Card title={props.album.title}>
<Text> {props.album.title} </Text>
</Card>
);
};
export default AlbumDetail;
// Card.js component
import React from "react";
import { View } from "react-native";
function calculateColor(title) {
let bgColor;
switch (title) {
case "Taylor":
bgColor = "red";
break;
case "Fearless":
bgColor = "green";
break;
default:
bgColor = "orange";
break;
}
return bgColor;
}
const Card = props => {
const { title } = props;
const backgroundColor = calculateColor(title);
return (
<View style={[styles.containerStyle, { backgroundColor: backgroundColor }]}>
{props.children}
</View>
);
};
const styles = {
containerStyle: {
borderWidth: 1,
borderRadius: 2,
backgroundColor: "#ddd",
borderBottomWidth: 0,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 1,
marginLeft: 5,
marginRight: 5,
marginTop: 10
}
};
export default Card;
Something like this should work:
Change the AlbumDetail to conditionally render the Card.
const AlbumDetail = props => {
if (props.album.title === 'Taylor') {
return (
<Card style={{ backgroundColor: 'red' }}>
<Text>{props.album.title}</Text>
</Card>
);
} else {
return (
<Card style={{ backgroundColor: 'green' }}>
<Text>{props.album.title}</Text>
</Card>
);
}
};
Override the default style of the card using the passed style prop.
const Card = props => {
return (
<View style={[styles.containerStyle, props.style]}>{props.children}</View>
);
};
accepted answer is great just its a bad habit to do things in render.
i also not sure since every title has a color why wouldnt the server send this in the object props in first place ? :)
class AlbumList extends Component {
state = { albums: [] };
componentDidMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response=> Array.isArray(response.data) ? response.data : []) // alittle validation would not hurt :) !
.then(data => this.setState({ albums: data }) );
}
selectHeaderColorForAlbum( album ){
let headerColor = 'red';
// switch(album.title){ ..you logic }
return headerColor;
}
renderAlbums() {
return this.state.albums.map(album =>
<AlbumDetail key={album.title} album={album} color={this.selectHeaderColorForAlbum(album)} />
);
}
render() {
return(
<View>
{this.renderAlbums()}
</View>
);
}
}
const Card = (props) => {
return (
<View style={[styles.containerStyle,{color:props.headerColor}]}>
{props.children}
</View>
);
};
this is easier your logic will render only once.
also notice that react >16.0 depricated componentWillMount, so use DidMount