Material UI Rating wrapping long values - reactjs

I am currently trying to work around the Material UI rating component and how to do a flex-wrap if the icons overflow the width of the parent component.
If I try to add flex-wrap: wrap to the rating component, it actually wraps the icons but the interactive functionality stops working pas the first line.
Here is a code example below to better demonstrate this:
Code Example in CodeSandbox
Is there a way to make it work with flex-wrap? If anyone could help I will very much appreciate.

I have decided that was better to build one by myself with the ability to wrap if the max value is big.
Will leave it here so someone who might have the same issue as me can use it.
CustomRating.js
import React, { useState } from 'react'
import { Tooltip } from '#mui/material'
import './CustomRating.css'
function CustomRating({ max, value, onChange, icon, emptyIcon }) {
const [innerValue, setInnerValue] = useState(value)
const checkIfIconInsideValue = (index) => {
return value >= index + 1
}
const handleMouseHover = (e, index) => {
if (e.type === 'mouseenter') {
setInnerValue(index)
return
}
setInnerValue(value - 1)
}
return (
<Tooltip title={innerValue} placement='top'>
<div className='custom-rating-main-div'>
{Array.from({ length: max }).map((elem, index) => {
return (
<div
className={`custom-rating-icon-div ${checkIfIconInsideValue(index) ? 'filled' : ''}`}
key={index}
onClick={() => onChange(index + 1)}
onMouseEnter={(e) => handleMouseHover(e, index)}
onMouseLeave={(e) => handleMouseHover(e, index)}
>
{checkIfIconInsideValue(index) || innerValue >= index ? icon : emptyIcon}
</div>
)
})}
</div>
</Tooltip>
)
}
export default CustomRating
CustomRating.css
.custom-rating-main-div {
display: flex;
flex-wrap: wrap;
}
.custom-rating-icon-div {
cursor: pointer;
}
.custom-rating-icon-div.filled > svg {
fill: #61634f
}
.custom-rating-icon-div > svg {
fill: rgba(97, 99, 79, 0.5)
}
.custom-rating-icon-div:hover > svg {
fill: #61634f;
transform: scale(1.2);
}
As you may notice this is specific to my problem but can be very easily adapted to any case.
keep in mind that this is very rough and can be updated to better follow conventions and for better performance, but for now it is my solution

Related

React: prevent list from rerendering all elements on prop change

I'm trying to recreate the effect shown at https://hexed.it/
When you hover over either list the corresponding byte in the other list is also highlighted. I figured a panel with each list inside it that had a state with the current hovered byte would do but it seems that React wants to re-render the entire list or do something strange every time resulting in larger files being unbearably slow.
I see a lot of "use memo! use the useCallback hook!" when searching and I've tried... it's still slow and I'm not sure why. It seems like it's only rendering the updated HexByte but it's still unacceptably slow for large files.
Sandbox: https://codesandbox.io/s/flamboyant-ellis-btfk5s
Can someone help me quicken/smooth out the hovering?
I solved it using this answer: Prevent DOM element re-render in virtualized tree component using react-window
In short the things I've learned:
memo has no effect if a component has a useState in it
Large lists of data should be rendered using a library like react-window
The cell rendering function as mentioned in the answer above can't be part of a parent component
As an example for anyone coming here, the new HexPanel class looks like so
import Box from '#mui/material/Box';
import { memo } from 'react';
import { FixedSizeGrid as Grid, areEqual } from 'react-window';
const HexByte = memo(function HexByte(props) {
const onMouseEnter = () => {
props.onHover(props.index);
//setInside(true);
}
const onMouseLeave = () => {
//setInside(false);
}
const onClick = () => {
//setClicked(true);
}
return (
<span
style={{
display: 'inline-block',
padding: '5px',
backgroundColor: props.hoverIndex == props.index ? '#777' : 'transparent',
color: 'darkblue'
}}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{props.byte}
</span>
)
}, (prevProps, nextProps) => nextProps.hoverIndex != nextProps.index);
const Cell = memo(function({ data, columnIndex, rowIndex, style }) {
return (
<div style={style}>
<HexByte byte={data.hex[rowIndex][columnIndex]} onHover={data.onHover} hoverIndex={data.hoverIndex} index={`${rowIndex}${columnIndex}`} />
</div>
)
}, areEqual);
const HexPanel = (props) => {
return (
<Box
sx={{
fontFamily: 'Source Code Pro',
display: 'flex',
flexDirection: 'column',
}}
>
<Grid
columnCount={16}
columnWidth={30}
height={900}
itemData={props}
rowCount={props.hex.length}
rowHeight={35}
width={500}
>
{Cell}
</Grid>
</Box>
)
}
export default HexPanel;

how i can change background color of full page using onclick event in reactjs?

I need to solve a question that is asking to change the body background colours while clicking the button I need to change four colours how I can solve it? in react.
import React, { useState } from 'react';
import './App.css';
function App() {
var colors=[
{
value:1,label:"red"
},
{
value:2,label:"green"
}
]
var [setbgcolor,ddlvalue]=useState(colors.label)
var ddlhandle=e=>{
ddlvalue(e.label)
}
return (
<div className="App">
<style>{'body {background-color:'+setbgcolor+'}'}</style>
<button onClick={ddlhandle} >click</button>
<select options={colors} onChange={ddlhandle}></select>
</div>
);
}
export default App;
You would need two states, one for holding the select's value, and other for holding current bg color. Similarly, you need two handlers, one for select and other for button. Also, you need to use option element, inside select to display the options.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
var colors = [
{
value: 1,
label: "red"
},
{
value: 2,
label: "green"
}
];
var [inputValue, setInputValue] = useState(colors[0].label);
var [bgcolor, setbgColor] = useState(colors[0].label);
var ddlhandle = (e) => {
setInputValue(e.target.value);
};
var buttonHandle = () => {
setbgColor(inputValue);
};
return (
<div
style={{ backgroundColor: bgcolor, width: "100vw", height: "100vh" }}
className="App"
>
<button onClick={buttonHandle}>click</button>
<select onChange={ddlhandle}>
{colors.map((color) => (
<option value={color.label}>{color.label}</option>
))}
</select>
</div>
);
}
CodeSandbox Link - https://codesandbox.io/s/zealous-williamson-58qf8?file=/src/App.js
Edit: Since you need to change the color of entire body, you need to set width: "100vw" and height: "100vh" of div.
you should use two className or two style for the background
and handle a parameter for change between className or style
import React, { useState } from 'react';
import './App.css';
function App() {
var colors=[
{
value:1,label:"red"
},
{
value:2,label:"green"
}
]
var [setbgcolor,ddlvalue]=useState(colors[0].label)
var ddlhandle=e=>{
ddlvalue(e.target.value)
}
return (
**<div className="App" style={{backgroundColor:`${setbgcolor}`}}>**
<button onClick={ddlhandle} >click</button>
<select options={colors} onChange={ddlhandle}></select>
</div>
);
}
export default App;
If you want with a single click of a button to cicle thru an array of possible colors to change the background color, you can simply store the array index of the current one and +1 to it until you reach the limit then reset back to 0.
Heres a short example:
Created an array with objects that represent what colors can be picked
const colors = [
{ title: "Red", color: "#de1212" },
{ title: "Blue", color: "#122dde" },
{ title: "Green", color: "#32a852" },
{ title: "Yellow", color: "#d7de12" }
];
Then created a state where you can store the index of the current selected color. NOTE giving a default index of 0
const [colorValue, setColorValue] = useState(0);
Then simply, create a simple method that onClick will execute. This method will increate the index by 1 until it reaches same number as there are objects in the array. Once reached the limit it will reset it to 0
const handleClick = () =>
setColorValue(colorValue + 1 < colors.length ? colorValue + 1 : 0);
Now in a render its up to you how you render it. I simply did this to present it
<div className="App" style={{ backgroundColor: colors[colorValue].color }}>
<h1>Selected color: {colors[colorValue].title}</h1>
<button className="btn" onClick={handleClick}>
Click to change color
</button>
</div>
Live example of this code
And if you required to do this with a select and only with a button onClick to read select value and set it as color, the situation is the same just add 1 more state where you seperatly store select value OR use a ref. The rest is the same.
Also, as a note, do not use var. Its so old. Use let, for variables that values change, and const, for variables whos value do not change.

Use event handler to change button appearance

I would like to build out my functional component BoldButton in which when the user clicks either the lowercase a or uppercase A, the text of the button is changed to bold. Below is my work in progress.
import React, { useEffect, useState } from 'react';
const BoldButon = () => {
const [color, setColor] = useState("black")
useEffect(() => {
})
const changeColor = () => {
setColor("black")
}
return (
<div>
<button text={{style:color}} onClick={changeColor}>
A
</button>
<button text={{style:color}} onClick={changeColor}>
a
</button>
</div>
)
}
export default BoldButon;
This was one problem I had when doing a mock technical interview so I would like to know how to solve it when using a functional component. **Not sure if I need to utilize useEffect in order to solve this.
Currently, there are a few problems with syntax to address:
text={{style:color}} not sure what this is supposed to do: text is not a standard or custom prop that you have set anywhere on the button component. Also, say you were to change the color of the button's text (which you could do with style={{color}} then you would be able to change it from let's say black to gray or black to yellow for different emphasis, but you seemed to say that you wanted different font-weight (bold vs not bold) is controlled by the CSS property font-weight (and that can be set in HTML or JSX through the style property that all elements can have.
if you have nothing in useEffect probably you can/should get rid of it. useEffect is useful for if the state of something changes and you need a side effect you can put it there (or it can be used as a strange componentDidMount which will only run after all parents have had their useEffect run.
For something like this it might be better to use a class and then you can set a number of style properties with a single string: like is-emphasized is a commonly used class that can change the font-weight to 600 and the color to black from a dark gray (if necessary).
You probably want individual states for each individual button (as you want each button to be bold if it was clicked: it seems based on your question).
.is-emphasized {
font-weight: 600;
color: #000;
}
But if not the simplest fastest way to approach this is:
<button
style={{fontWeight: bold ? 'bold' : 'normal'}}
onClick={changeWeight}
>
A
</button>
<button
style={{fontWeight: !bold ? 'bold' : 'normal'}}
onClick={changeWeight}
>
a
</button>
function BoldButon() {
const [capitalABold, setCapitalABold] = React.useState(false)
const [aBold, setaBold] = React.useState(false);
const changeColor = () => {
setColor("black")
}
return (
<div>
<button
className={capitalABold ? 'is-emphasized' : ''}
onClick={() => setCapitalABold(!capitalABold)}
>
A
</button>
<button
className={aBold ? 'is-emphasized' : ''}
onClick={() => setaBold(!aBold)}
>
a
</button>
</div>
)
}
ReactDOM.render(<BoldButon/>, document.getElementById("root"));
* {
color: #333;
font-size: 14px;
}
.is-emphasized {
color: #000;
font-weight: 600;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You don't need useEffect for this kind of functionality, you can utilize the useState hook to manage the variable that determines if the user has clicked on the button. Also, you can use the useMemo hook to keep the style object which returns either bold or normal. Please note that the toggleBold toggles the font weight so if a user is clicking multiple times it will switch between the bold and normal.
import React, { useState, useMemo } from 'react';
const BoldButon = () => {
const [boldA, setBoldA] = useState(false)
const [boldB, setBoldB] = useState(false)
const toggleBold = (buttonType) => () => {
if(buttonType === 'firstButton') {
setBoldA(prevState => !prevState)
} else {
setBoldB(prevState => !prevState)
}
}
const buttonStyleA = useMemo(() => {
return {fontWeight: boldA ? 'bold' : 'normal'}
}, [boldA])
const buttonStyleB = useMemo(() => {
return {fontWeight: boldB ? 'bold' : 'normal'}
}, [boldB])
return (
<div>
<button style={buttonStyleA} onClick={toggleBold('firstButton')}>
A
</button>
<button style={buttonStyleB} onClick={toggleBold('secondButton')}>
B
</button>
</div>
)
}
export default BoldButon;

React-bootstrap cards not wrapping

My Code is:
import React, { useEffect, useState } from "react";
import styles from "./Cards.module.css";
import { CardDeck, Card } from "react-bootstrap";
const Cards = ({ animeArray }) => {
const [aanimeArray, setAnimeArray] = useState([]);
useEffect(() => {
setAnimeArray(animeArray);
}, [animeArray]);
if (!aanimeArray) {
return;
}
console.log("Anime Array", aanimeArray);
return (
<div className={styles.container}>
{aanimeArray === [] ? (
<h1>Search</h1>
) : (
<CardDeck>
{aanimeArray.map((anime) => {
return (
<Card>
<Card.Img variant = "top" src={anime.image_url} />
<Card.Body>
<Card.Title>{anime.title}</Card.Title>
</Card.Body>
<Card.Footer>
<small className="text-muted">{anime.rated}</small>
</Card.Footer>
</Card>
);
})}
</CardDeck>
)}
</div>
);
};
export default Cards;
I am not using any custom styling whatsoever.
The result of the above mentioned code is as seen on this image:
Image of the issue
You have to make the effort of making them wrap. In fact, as seen on the documentation, majority of the developers' examples includes the CSS property width with a value of 18rem.
Here is an example by leveraging minWidth:
const sampleStyle = {
minWidth: "20%",
flexGrow: 0
};
<Card style={sampleStyle}>
First thing.
aanimeArray === []
won't work since you are comparing an array with another array.
Best way in Javascript for this is to check the length of the array.
aanimeArray.length === 0
means it is an empty array.
About the styling I think you need to show us the CSS code as well. I'm not sure what CardDeck component does...

Call to setState in onDragStart causes DOM node to be deleted?

I use a list of styled components for displaying some info. I want this info to be sortable. The real problem I'm trying to solve is actually way more complex than what I'm demonstrating here. So any odd design choices are very specific to what I'm trying to do. I'm just mentioning it because the code I'm showing will be very simplified but it will also show some of these at first glance odd design choices.
I've read this article: https://medium.com/the-andela-way/react-drag-and-drop-7411d14894b9
Temitope Emmanuel (the author) did what I'm trying to achieve but with just a plain div. I don't know whether he tested all of what he proposes in his article.
Off to some code:
import React, { Component, Fragment } from 'react';
import styled from 'styled-components';
export default class SomeList extends Component {
constructor(props) {
super(props);
// in real problem all of these are props
// pulled off the state of a parent
this.state = {
dragging: false,
listOfChildrenInOrder: ['1', '2', '3'],
itemComponent: styled.div`
border: 1px solid black;
`,
};
}
render() {
const {
dragging,
listOfChildrenInOrder,
itemComponent: ItemComponent,
} = this.state;
const {
children,
} = this.props;
const Container = styled.div`
display: grid;
grid-template-rows: max-content;
grid-template-columns: repeat(${listOfChildrenInOrder.length}, max-content) 1fr;
`;
const Droppable = styled.div`
&:hover {
background-color: rgba(0,0,0,0.4);
}
`;
return (
<Container>
<Fragment>
{listOfChildrenInOrder.map(((cid, i) => (
<ItemComponent
draggable
key={`ic-${cid}`}
style={{
gridArea: `1 / ${i + 1} / span 1 / span 1`,
}}
onDragStart={(e) => {
this.setState({ dragging: true });
e.dataTransfer.setData('text/plain', `${cid}`);
}}
onDragEnd={() => {
this.setState({ dragging: false });
// doesn't even fire anymore
}}
>
{children.find(c => c.key === cid)}
</ItemComponent>
)))}
</Fragment>
<Fragment>
{dragging && listOfChildrenInOrder.map(((cid, i) => (
<Droppable
key={`d-${cid}`}
style={{
gridArea: `1 / ${i + 1} / span 1 / span 1`,
}}
onDragOver={(e) => {
e.stopPropagation();
e.preventDefault();
}}
onDrop={() => {
// do whatever (out of scope), doesn't get called anyway
}}
>
{children.find(c => c.key === cid)}
</Droppable>
)))}
</Fragment>
</Container>
);
}
}
I'm expecting the reconciler (Fiber) to update the DOM node without straight out replacing it in the middle of a drag operation. I'm using these things to act as highlighters. The real Problem I'm trying to solve actually makes a difference on where exactly stuff gets dropped, so the grid in the real problem is finer, with more droppables and one item component spaning multiple grid columns. Like I said: odd choices, but not without purpose.
Okay, I know now what was causing this whole operation to fail. The reason was dynamically creating new styled components in the render loop. Never do that. Just another rule of thumb learned.

Resources