Update className attribute - reactjs

For the avatarColor I am selecting a random color AKA -> rendomColor.
render() {
const { classes } = this.props;
let colorArr = [classes.redAvatar, classes.greenAvatar, classes.blueAvatar, classes.redAvatar];
const usersListedItems = this.state.ownersArr.map((owner, index) => {
return (
<Grid item xs={6} sm={3} key={owner.ownerId}>
<UsersListedItems
ownerId={owner.ownerId}
userName={owner.userName}
avatarColor={colorArr[Math.floor(Math.random() * colorArr.length)]}>
</UsersListedItems>
</Grid>
)
How can I update (in UsersListedItems component) the card style borderColor with the same random color that I calculated for the avatar?
const styles = (theme) => ({
root: {
flexGrow: 1,
},
card: {
borderRadius: '14px',
border: '1px solid',
borderColor: ?????
},
});
........
return (
<Card className={classes.card}>
<CardHeader
avatar={
<Avatar id="av" aria-label="Recipe" className={this.props.avatarColor}>
Thank you

There may be other ways, but at least you can do it through styles specifically:
<Card
className={classes.card}
style={{
borderColor: this.props.avatarColor
}} >
<CardHeader
avatar={
<Avatar id="av" aria-label="Recipe" className={this.props.avatarColor}>

Related

Get All TextField values from loop in Next.js when I press Submit button

first of all look at these below screenshots:
There are two tasks which I want to achieve:
There are two questions shown on the page using the array map method, by default I'm showing only one question, and when I press the next part button the second question will appear with the same question and a TextField (multiline). Now I've implemented a word counter in TextField but when I type something in 1st question the counter works properly. But when I go to the next question the here counter shows the previous question's word counter value, I want them to separately work for both questions.
When I click on the next part and again when I click on the previous part then the values from TextField are removed automatically. I want the values there if I navigate to the previous and next part questions. Also, I want to get both TextField values for a form submission when I press the Submit Test button.
Below are my codes for this page. I'm using Next.js and MUI
import { Grid, Typography, Box, NoSsr, TextField } from '#mui/material';
import PersonIcon from '#mui/icons-material/Person';
import Timer from '../../../components/timer';
import Button from '#mui/material/Button';
import { useState } from 'react';
import ArrowForwardIosIcon from '#mui/icons-material/ArrowForwardIos';
import { Scrollbars } from 'react-custom-scrollbars';
import AppBar from '#mui/material/AppBar';
import Toolbar from '#mui/material/Toolbar';
import ArrowBackIosIcon from '#mui/icons-material/ArrowBackIos';
import axios from '../../../lib/axios';
import { decode } from 'html-entities';
import { blueGrey } from '#mui/material/colors';
export default function Writing({ questions }) {
const [show, setShow] = useState(false);
const [show1, setShow1] = useState(true);
const [showQuestionCounter, setShowQuestionCounter] = useState(0);
const [wordsCount, setWordsCount] = useState(0);
return (
<>
<Box sx={{ flexGrow: 1 }}>
<AppBar position="fixed" style={{ background: blueGrey[900] }}>
<Toolbar>
<Grid container spacing={2} alignItems="center">
<Grid item xs={4} display="flex" alignItems="center">
<PersonIcon
sx={{ background: '#f2f2f2', borderRadius: '50px' }}
/>
<Typography variant="h6" color="#f2f2f2" ml={1}>
xxxxx xxxxx-1234
</Typography>
</Grid>
<Grid item xs={4} container justifyContent="center">
<Timer timeValue={2400} />
</Grid>
<Grid item xs={4} container justifyContent={'right'}>
<Button
variant="contained"
style={{ background: 'white', color: 'black' }}
size="small">
Settings
</Button>
<Button
variant="contained"
style={{
background: 'white',
color: 'black',
margin: '0px 10px',
}}
size="small">
Hide
</Button>
<Button
variant="contained"
style={{ background: 'white', color: 'black' }}
size="small">
Help
</Button>
</Grid>
</Grid>
</Toolbar>
</AppBar>
</Box>
<Box
sx={{
background: blueGrey[50],
height: '100%',
width: '100%',
position: 'absolute',
}}
pt={{ xs: 13, sm: 11, md: 10, lg: 11, xl: 11 }}>
{questions.map((question, index) =>
index === showQuestionCounter ? (
<Box
key={question.id}
px={3}
sx={{ background: '#f2f2f2', pb: 4 }}
position={{
xs: 'sticky',
sm: 'sticky',
lg: 'initial',
md: 'initial',
xl: 'initial',
}}>
<Box
style={{ background: '#f7fcff', borderRadius: '4px' }}
py={1}
px={2}>
<Box>
<Typography variant="h6" component="h6" ml={1}>
Part {question.id}
</Typography>
<Typography variant="subtitle2" component="div" ml={1} mt={1}>
<NoSsr>
<div
dangerouslySetInnerHTML={{
__html: decode(question.questions[0].question, {
level: 'html5',
}),
}}></div>
</NoSsr>
</Typography>
</Box>
</Box>
<Box
style={{
background: '#f7fcff',
borderRadius: '4px',
marginBottom: '75px',
}}
pt={1}
px={3}
mt={{ xs: 2, sm: 2, md: 2, lg: 0, xl: 3 }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={12} lg={6} md={6} xl={6}>
<Box
py={{ lg: 1, md: 1, xl: 1 }}
style={{ height: '50vh' }}>
<Scrollbars universal>
<Typography
variant="body1"
component="div"
style={{ textAlign: 'justify' }}
mr={2}>
<NoSsr>
<div
dangerouslySetInnerHTML={{
__html: decode(question.question_text, {
level: 'html5',
}),
}}></div>
</NoSsr>
</Typography>
</Scrollbars>
</Box>
</Grid>
<Grid
item
xs={12}
sm={12}
lg={6}
md={6}
xl={6}
mt={{ md: 4, lg: 4, xl: 4 }}>
<TextField
id={`${question.id}`}
label="Type your answer here"
multiline
name={`answer_${question.id}`}
rows={12}
variant="outlined"
fullWidth
helperText={`Words Count: ${wordsCount}`}
onChange={(e) => {
setWordsCount(
e.target.value.trim().split(/\s+/).length
);
}}
/>
</Grid>
</Grid>
</Box>
</Box>
) : null
)}
<Box sx={{ position: 'fixed', width: '100%', left: 0, bottom: 0 }}>
<Grid
container
style={{ background: blueGrey[300], display: 'flex' }}
py={2}
px={3}>
<Grid
item
xs={3}
sm={3}
lg={6}
md={6}
xl={6}
container
justifyContent={'start'}>
<Button
variant="contained"
style={{ background: 'white', color: 'black' }}
size="small">
Save Draft
</Button>
</Grid>
<Grid
item
xs={9}
sm={9}
lg={6}
md={6}
xl={6}
container
justifyContent={'end'}>
<Button
variant="contained"
size="small"
style={{
background: 'white',
color: 'black',
visibility: show1 ? 'visible' : 'hidden',
}}
endIcon={<ArrowForwardIosIcon />}
onClick={() => {
setShow((prev) => !prev);
setShowQuestionCounter(showQuestionCounter + 1);
setShow1((s) => !s);
}}>
Next Part
</Button>
{show && (
<>
<Box>
<Button
variant="contained"
style={{
background: 'white',
color: 'black',
margin: '0 10px',
visibility: show ? 'visible' : 'hidden',
}}
startIcon={<ArrowBackIosIcon />}
size="small"
onClick={() => {
setShow1((s) => !s);
setShowQuestionCounter(showQuestionCounter - 1);
setShow((prev) => !prev);
}}>
previous Part
</Button>
<Button variant="contained" color="success">
Submit Test
</Button>
</Box>
</>
)}
</Grid>
</Grid>
</Box>
</Box>
</>
);
}
export async function getServerSideProps(context) {
const { id } = context.query;
const token = context.req.cookies.token;
if (!token) {
context.res.writeHead(302, {
Location: '/',
});
context.res.end();
}
const res = await axios.get(`test/${id}/questions`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (res.data.success) {
return {
props: {
questions: res.data.data.questions,
},
};
}
}
The wordsCount state is shared between both questions, which means that when you go to the next question the state remains unchanged and shows the wordsCount from the first question. To solve it, each question needs to have it's own state which you can do by creating a Question component and mapping over it:
export default function Question({ question }) {
const [wordsCount, setWordsCount] = useState(0)
return (
<Box
// ...
>
{/* ... */}
<TextField
// ...
helperText={`${wordsCount} words`}
onChange={(e) => {
setWordsCount(e.target.value.trim().split(/\s+/).length)
}}
/>
{/* ... */}
</Box>
)
}
Then map over it:
{questions.map((question, index) =>
index === showQuestionCounter ? (
<Question key={question.id} question={question} />
) : null
)}
Currently, the value of TextField gets reset you the component is unmounted (i.e. when you go to the next question). You need to make the TextField component a controlled component, meaning that you store the value of the field in useState. And if you need to submit the value of TextField later, then you probably need to store the values in the parent component:
export default function Writing({ questions }) {
// ...
const [answers, setAnswers] = useState([])
function handleChange(id, answer) {
// copy the current answers
let newAnswers = [...answers]
// find the index of the id of the answer in the current answers
const index = newAnswers.findIndex((item) => item.id === id)
// if the answer does exist, replace the previous answer with the new one, else add the new answer
if (index) {
newAnswers[index] = { id, answer }
setAnswers(newAnswers)
} else {
setAnswers([...answers, { id, answer }])
}
}
return (
<>
{/* ... */}
{questions.map((question, index) =>
index === showQuestionCounter ? (
<Question
key={question.id}
question={question}
value={answers.find((item) => item.id === question.id)?.answer || ''}
handleChange={handleChange}
/>
) : null
)}
</>
}
In the Question component, add the handler.
export default function Question({ question, value, handleInputChange }) {
const [wordsCount, setWordsCount] = useState(0)
return (
<Box>
{/* ... */}
<TextField
helperText={`${wordsCount} words`}
value={value}
onChange={(e) => {
handleInputChange(question.id, e.target.value)
setWordsCount(e.target.value.trim().split(/\s+/).length)
}}
/>
{/* ... */}
</Box>
)
}
In the parent component (Writing) you should be able to use the values for form submission.

Putting gradient background using makeStyles

For some reason, it doesn't respect background: 'linear-gradient(to right, blue.200, blue.700)' under makeStyles. Why is that? All I need to do is put a gradient background on the entire space. <Container className={classes.root}> should probably be a div, what do you think?
import { useState, useEffect } from 'react';
import type { NextPage } from 'next';
import Container from '#mui/material/Container';
import Box from '#mui/material/Box';
import { DataGrid, GridColDef } from '#mui/x-data-grid';
import { createStyles, Grid, Paper, Theme, Typography } from '#mui/material';
import { makeStyles } from '#mui/styles';
import Skeleton from '#mui/material/Skeleton';
import FormOne from './../src/FormOne';
const LoadingSkeleton = () => (
<Box
sx={{
height: 'max-content',
}}
>
{[...Array(10)].map((_, index) => (
<Skeleton variant="rectangular" sx={{ my: 4, mx: 1 }} key={index} />
))}
</Box>
);
const columns: GridColDef[] = [
{ field: 'id', headerName: 'ID' },
{ field: 'title', headerName: 'Title', width: 300 },
{ field: 'body', headerName: 'Body', width: 600 },
];
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
height: '100vh',
overflow: 'auto',
background: `linear-gradient(to right, blue.200, blue.700)`,
},
})
);
const Home: NextPage = () => {
const classes = useStyles();
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
// fetch data from fake API
useEffect(() => {
setInterval(
() =>
fetch('https://jsonplaceholder.typicode.com/posts')
.then((response) => response.json())
.then((data) => {
setPosts(data);
setLoading(false);
}),
3000
);
}, []);
return (
<Container
maxWidth={false}
// sx={{
// height: '100vh',
// overflow: 'auto',
// background: `linear-gradient(to right, ${blue[200]}, ${blue[700]})`,
// }}
className={classes.root}
>
<Container component="main" maxWidth="lg" sx={{ mt: 3, mb: 3 }}>
<Grid container spacing={{ xs: 2, md: 3 }}>
<Grid item xs={6}>
<Paper sx={{ padding: 3 }}>
<Typography component="h1" variant="h4" align="center">
GUI #1
</Typography>
<FormOne />
</Paper>
</Grid>
<Grid item xs={6}>
<Paper sx={{ padding: 3 }}>
<Typography component="h1" variant="h4" align="center">
GUI #2
</Typography>
<FormOne />
</Paper>
</Grid>
<Grid item xs={12}>
<Paper sx={{ padding: 3 }}>
<DataGrid
sx={{ height: '650px' }} // either autoHeight or this
rows={posts}
columns={columns}
pageSize={10}
// autoHeight
rowsPerPageOptions={[10]}
disableSelectionOnClick
disableColumnMenu
disableColumnSelector
components={{
LoadingOverlay: LoadingSkeleton,
}}
loading={loading}
/>
</Paper>
</Grid>
</Grid>
</Container>
</Container>
);
};
export default Home;
I think its because you pass it as a string and it simply doesnt recognice what blue.200 is etc.
try:
background: `linear-gradient(to right, ${blue[200]}, ${blue[700])}`,
#Edit
You actualy need to import color that you want to use from #mui/color
import { blue } from "#mui/material/colors";
and then use it as I mentioned before
here is codesandbox preview and here is codesandbox code
hope this is what we want to achieve
Instead of using background use backgroundImage. This should fix the problem.
The code should be
backgroundImage: `linear-gradient(to right, blue[200], blue[700])`,

Problem with 100vh, content larger than my browser window

I need to make the content fit in the browser window, without showing the scroll bar, can someone help me?
I'm using Material-UI, follow the model in Sandbox.
screen
https://codesandbox.io/s/material-ui-grid-ylw6v?file=/src/App.js
Thanks for your help!
Try this css:
::-webkit-scrollbar {
display: none;
}
I managed to solve using the code posted on the link below.
https://github.com/mui-org/material-ui/issues/10739#issuecomment-817742141
Below is the code and the link in Sandbox.
import {
AppBar,
Box,
CssBaseline,
Grid,
IconButton,
makeStyles,
Toolbar,
Typography
} from "#material-ui/core";
import MenuIcon from "#material-ui/icons/Menu";
import "./styles.css";
const useStyles = makeStyles((theme) => {
/*
* This function creates a new object similar to `style`, but only keep
* `property` with the value set from `setNewValue`.
*
* For example given the `style`:
*
* const style = {
* minHeight: 56,
* '#media (min-width:0px) and (orientation: landscape)': { minHeight: 48 },
* '#media (min-width:600px)': { minHeight: 64 }
* }
*
* Then overrideExistingStyle(style, 'minHeight', (v) => v + 1) returns:
*
* {
* minHeight: 57,
* '#media (min-width:0px) and (orientation: landscape)': { minHeight: 49 },
* '#media (min-width:600px)': { minHeight: 65 }
* }
*
* We use overrideExistingStyel to dynamically compute the main content
* height. Since MUI AppBar minHeight depends on the screen size, We use
* overrideExistingStyel() to set the minHeight of the main content to (100vh
* - AppBar height).
*/
function overrideExistingStyle(style, property, setNewValue) {
return Object.fromEntries(
Object.entries(style)
.filter(([key, value]) => key === property || typeof value === "object")
.map(([key, value]) =>
typeof value === "object"
? [key, overrideExistingStyle(value, property, setNewValue)]
: [property, setNewValue(value)]
)
);
}
return {
main: {
display: "flex",
background: "#ccc",
...overrideExistingStyle(
theme.mixins.toolbar,
"minHeight",
(value) => `calc(100vh - ${value}px - (${theme.spacing(2)}px * 2))`
)
},
sidebar: {
width: 240,
background: "#F56638",
...overrideExistingStyle(
theme.mixins.toolbar,
"minHeight",
(value) => `calc(100vh - ${value}px)`
)
},
contentHeader: {
display: "flex",
background: "green",
height: theme.spacing(8)
},
box1: {
display: "flex",
background: "yellow",
...overrideExistingStyle(
theme.mixins.toolbar,
"minHeight",
(value) => `calc(100vh - ${value}px - (${theme.spacing(8)}px))`
)
},
box2: {
display: "flex",
background: "cyan",
...overrideExistingStyle(
theme.mixins.toolbar,
"minHeight",
(value) => `calc(100vh - ${value}px - (${theme.spacing(8)}px))`
)
},
box3: {
display: "flex",
background: "orange",
...overrideExistingStyle(
theme.mixins.toolbar,
"minHeight",
(value) => `calc(100vh - ${value}px - (${theme.spacing(8)}px))`
)
},
box4: {
display: "flex",
background: "gray",
...overrideExistingStyle(
theme.mixins.toolbar,
"minHeight",
(value) => `calc(100vh - ${value}px - (${theme.spacing(8)}px))`
)
}
};
});
export default function App() {
const classes = useStyles();
return (
<>
<CssBaseline />
<div className={classes.root}>
<AppBar position="static" color="primary">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="menu"
>
<MenuIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
News
</Typography>
</Toolbar>
</AppBar>
<div className={classes.main}>
<Box className={classes.sidebar}>
<h3>Sidebar</h3>
</Box>
<Grid container className={classes.content}>
<Grid item xs={12}>
<Box className={classes.contentHeader}>
<h3>Content Header</h3>
</Box>
</Grid>
<Grid item xs={2}>
<Box flex={1} overflow="auto" className={classes.box1}>
<h3>Box 1</h3>
</Box>
</Grid>
<Grid item xs={2}>
<Box flex={1} overflow="auto" className={classes.box2}>
<h3>Box 2</h3>
</Box>
</Grid>
<Grid item xs={2}>
<Box flex={1} overflow="auto" className={classes.box3}>
<h3>Box 3</h3>
</Box>
</Grid>
<Grid item xs={6}>
<Box flex={1} overflow="auto" className={classes.box4}>
<h3>Box 4</h3>
</Box>
</Grid>
</Grid>
</div>
</div>
</>
);
}
https://codesandbox.io/s/material-ui-grid-ajustado-qlmcw?file=/src/App.js

How to use theme in styles for custom functional components

When I try compiling the app it displays a certain error "TypeError:undefined has no properties" though I want to render styled components into the grid tags though in function based component method
const styles = theme => ({
root: {
height: '100vh',
},
image: {
},
paper: {
margin: theme.spacing(8, 4),
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main,
},
form: {
width: '100%',
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
});
const SignIn = (props) => {
const classes = this.props;
return(
<Grid container component="main" className={classes.root}>
<CssBaseline />
<Grid item xs={false} sm={4} md={7} className={classes.image} />
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
<div className={classes.paper}>
<Typography component="h1" variant="h5">
Welcome to web
</Typography>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<form className={classes.form} noValidate>
//content
</form>
</div>
</Grid>
</Grid>
);
}
export default withStyles(styles)(SignIn);
Access or destructure classes from props, not this.props.
const { classes } = props;
You can also destructure props right in the function definition.
const SignIn = ({ classes }) => {...

Set <Avatar> backgroundColor randomly

I have defined three backgroundColor in the style theme.
avatar: {
backgroundColor: red[500],
},
orangeAvatar: {
margin: 10,
color: '#fff',
backgroundColor: deepOrange[500],
},
purpleAvatar: {
margin: 10,
color: '#fff',
backgroundColor: deepPurple[500],
},
When ever the Avatar is loaded I would like to select one of them randomly.
<Card>
<CardHeader
avatar={
<Avatar id="av" aria-label="Recipe"
className={classes.avatar}>{this.props.userName.charAt(0).toLocaleUpperCase()}
</Avatar>}
title={this.props.userName} disableTypography={true}/>
<CardActionArea disabled={this.state.images.length == 1 ? true : false}>
<CardMedia
id={this.props.ownerId}
className={classes.media}
image={this.state.images[this.state.imageIndex]}
onClick={this.handleOnClick}
/>
</CardActionArea>
</Card>
Any advice how to do this?
Thank you
Several ways to do what you want. My suggestion: put the 3 classes in an array, pick a random number between 0 and 2 every time, and assign that class name:
<Avatar className={classes[Math.floor(Math.random() * 3)]}.../>
I was presented with the same need, perhaps this solution will also serve you, there is a function to generate the color at random and then call the function from the online style.
const useStyles = makeStyles((theme: Theme) =>
createStyles({
large: {
fontSize: "2.5rem",
width: 100,
height: 100
}
})
);
function randomColor() {
let hex = Math.floor(Math.random() * 0xFFFFFF);
let color = "#" + hex.toString(16);
return color;
}
...
return (
<Avatar
variant="square"
src={imageSrc}
alt={alt}
className={classes.large}
style={{
backgroundColor: randomColor()
}}
/>
)
ref:
Javascript random color
Avatar random backgroundColor on fallback
let classNameHolder = ["avatar","orangeAvatar","purpleAvatar"];
<Card>
<CardHeader
avatar={
<Avatar id="av" aria-label="Recipe"
className={classNameHolder[Math.floor(Math.random() * 3)]}>{this.props.userName.charAt(0).toLocaleUpperCase()}
</Avatar>}
title={this.props.userName} disableTypography={true}/>
<CardActionArea disabled={this.state.images.length == 1 ? true : false}>
<CardMedia
id={this.props.ownerId}
className={classes.media}
image={this.state.images[this.state.imageIndex]}
onClick={this.handleOnClick}
/>
</CardActionArea>
</Card>

Resources