React component not passing value to correct component - reactjs

I have a simple React app that uses react-dnd to build a grid of draggable squares that have an initial color and text value and two components to change the color and change the text.
The color change (via ColorPicker2, using react-color library) works okay. The text change (using TextInput from #carbon/react) doesn't work as desired.
I thought I was applying the same logic with both components, but whilst the color-picker updates the color and retains that color when the square is moved, the text seems to render inside the TextInput and not the square itself and I can't figure out the logical difference.
The Code Sandbox is here: https://codesandbox.io/s/wild-waterfall-z8s1de?file=/src/App.js
This is the current code:
ColorPicker2.js
import "./styles.css";
import React from "react";
import { BlockPicker } from "react-color";
const presetColors = ["#9E9E9E", "#4CAF50", "#FFEB3B", "#F44336", "#2196F3"];
const ColorPicker2 = (props) => {
const handleChangeComplete = (color) => {
if (props.updateColor) {
props.updateColor(color.hex);
}
};
return (
<div className="palette">
<BlockPicker
className="palette"
colors={presetColors}
onChangeComplete={handleChangeComplete}
presetColors={Object.values(presetColors)}
color={props.currentColor}
/>
</div>
);
};
export default ColorPicker2;
App.js
import "./styles.css";
import React, { useState } from "react";
import { DndProvider, useDrag, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import edit from "./edit.svg";
import palette from "./palette.svg";
import ColorPicker2 from "./ColorPicker2";
import { TextInput } from "#carbon/react";
const DndWrapper = (props) => {
return <DndProvider backend={HTML5Backend}>{props.children}</DndProvider>;
};
const DraggableSquare = ({ index, text, color, moveSquare }) => {
const [{ isDragging }, drag] = useDrag({
type: "square",
item: { index },
collect: (monitor) => ({
isDragging: monitor.isDragging()
})
});
const [isHovered, setIsHovered] = useState(false);
const [, drop2] = useDrop({
accept: "square",
drop: (item, monitor) => {
const didDrop = monitor.didDrop();
if (!didDrop) {
moveSquare(item.index, index);
}
},
hover: (item, monitor) => {
setIsHovered(monitor.isOver());
},
collect: (monitor) => {
setIsHovered(monitor.isOver());
}
});
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
const [isTextInputOpen, setIsTextInputOpen] = useState(false);
const [newText, setNewText] = useState(text);
const opacity = isDragging ? 0.5 : 1;
return (
<div className="square-div" ref={drop2}>
<div
className="grey-square"
ref={drag}
style={{
opacity,
backgroundColor: color,
width: "200px",
height: "200px",
textAlign: "center",
paddingTop: "30px",
position: "relative",
border: isHovered ? "3px solid blue" : "none",
borderRadius: "5px",
}}
onMouseOver={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<img
src={edit}
onClick={() => {
setIsTextInputOpen(!isTextInputOpen);
if (!isTextInputOpen) {
moveSquare(index, newText, color, undefined);
}
}}
style={{
width: "15px",
height: "15px",
position: "absolute",
right: "5px",
top: "5px"
}}
alt="edit icon"
/>
{isTextInputOpen && (
<TextInput
id="newtext"
labelText=""
value={newText}
onChange={(e) => setNewText(e.target.value)}
/>
)}
<img
src={palette}
onClick={() => setIsPaletteOpen(!isPaletteOpen)}
style={{
width: "15px",
height: "15px",
position: "absolute",
right: "25px",
top: "5px"
}}
alt="palette icon"
/>
{isPaletteOpen && (
<ColorPicker2
className="palette"
currentColor={color}
updateColor={(newColor) =>
moveSquare(index, index, newText, newColor)
}
/>
)}
</div>
</div>
);
};
const Grid = () => {
const [grid, setGrid] = useState([
{ text: "1", color: "grey" },
{ text: "2", color: "grey" },
{ text: "3", color: "grey" },
{ text: "4", color: "grey" },
{ text: "5", color: "grey" },
{ text: "6", color: "grey" },
{ text: "7", color: "grey" },
{ text: "8", color: "grey" },
{ text: "9", color: "grey" },
{ text: "10", color: "grey" },
{ text: "11", color: "grey" },
{ text: "12", color: "grey" },
{ text: "13", color: "grey" },
{ text: "14", color: "grey" },
{ text: "15", color: "grey" }
]);
const moveSquare = (fromIndex, toIndex, newText, newColor) => {
setGrid((grid) => {
const newGrid = [...grid];
const item = newGrid[fromIndex];
newGrid.splice(fromIndex, 1);
newGrid.splice(toIndex, 0, {
text: newText || item.text,
color: newColor || item.color
});
return newGrid;
});
};
return (
<>
<DndWrapper>
<div
className="grid"
style={{
display: "grid",
gridTemplateColumns: "repeat(5, 190px)",
gridGap: "15px",
gridColumnGap: "20px",
gridRowGap: "10px",
position: "absolute"
}}
>
{grid.map((square, index) => (
<DraggableSquare
key={index}
index={index}
text={square.text}
color={square.color}
moveSquare={moveSquare}
//grid={grid}
//setGrid={setGrid}
/>
))}
</div>
</DndWrapper>
</>
);
};
export default Grid;
Any thoughts from fresh eyes would be helpful.

I think this is just a simple issue with using index as the key while mapping. Adjusting your code pen to have a unique key fixed it for me but the input text is not being saved anywhere so returned to the default text={square.text} when moved as expected.
Unique Id in objects:
const [grid, setGrid] = useState([
{ text: "1", color: "grey", id: crypto.randomUUID() },
{ text: "2", color: "grey", id: crypto.randomUUID() },
{ text: "3", color: "grey", id: crypto.randomUUID() },...])
Adding key to mapped object:
{grid.map((square, index) => (
<DraggableSquare
key={square.id}
index={index}
text={square.text}
color={square.color}
moveSquare={moveSquare}
//grid={grid}
//setGrid={setGrid}
/>}

Related

react The swipe animation will be performed twice

When you press the Circle button, the Box moves to the right and disappears from the screen.
Additional information (FW/tool version, etc.)
react
scss
Typescript
framer-motion
import "./style.scss";
import React, { FunctionComponent, useState } from "react";
import { useMotionValue, useTransform } from "framer-motion";
import { SwipeBox } from "./SwipeBox";
const App: FunctionComponent = () => {
const [cards, setCards] = useState([
const onClic = () => {
animateCardSwipe({ x: -1400, y: 0 });
}; <div
style={{
width: "400px",
height: "300px",
background: `${card.background}`
}}
>
) : (
</div>
);
};
export default App;
The problem is that the animation is happening on mount and you're updating state twice inside the animateCardSwipe function:
const animateCardSwipe = (animation: { x: number, y: number }) => {
setAnime({ ...anime, animation });
setTimeout(() => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
}, 200);
};
I personally like to use a more imperative approach here for starting the animation using animate:
const animateCardSwipe = (animation: { x: number, y: number }) => {
animate(x, animation.x, {
duration: 1,
onComplete: () => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
},
});
};
This implementation also waits for the animation to complete before rearranging the cards.
Full example:
import React, { FunctionComponent, useState } from "react";
import {
animate,
useAnimation,
useMotionValue,
useTransform,
MotionValue,
motion,
} from "framer-motion";
interface Props {
animate?: { x: number; y: number };
style: {
x?: MotionValue;
y?: MotionValue;
zIndex: number;
rotate?: MotionValue;
};
card: { text: string; background: string };
}
const SwipeBox: React.FunctionComponent<Props> = (props) => {
return (
<motion.div
animate={props.animate}
className="card"
style={{
...props.style,
background: "white",
borderRadius: "8px",
display: "grid",
top: 0,
left: 0,
placeItems: "center center",
position: "absolute",
width: "100%",
}}
transition={{ duration: 1 }}
>
{props.children}
</motion.div>
);
};
const App: FunctionComponent = () => {
const controls = useAnimation();
console.log(controls);
const [cards, setCards] = useState([
{ text: "Up or down", background: "red" },
{ text: "Left or right", background: "green" },
{ text: "Swipe me!", background: "gray" },
{ text: "Swipe me!", background: "purple" },
{ text: "Swipe me!", background: "yellow" },
]);
const x = useMotionValue(0);
const y = useMotionValue(0);
const animateCardSwipe = (animation: { x: number; y: number }) => {
animate(x, animation.x, {
duration: 1,
onComplete: () => {
x.set(0);
y.set(0);
setCards([...cards.slice(0, cards.length - 1)]);
},
});
};
const [anime, setAnime] = useState<{ animation: { x: number; y: number } }>({
animation: { x: 0, y: 0 },
});
const rotate = useTransform(x, [-950, 950], [-30, 30]);
const onClickLeft = () => {
animateCardSwipe({ x: 1400, y: 0 });
};
const onClickRight = () => {
animateCardSwipe({ x: -1400, y: 0 });
};
return (
<div style={{ display: "flex", flexDirection: "column" }}>
<div>
{cards.map((card, index) =>
index === cards.length - 1 ? (
<SwipeBox
animate={anime.animation}
card={card}
key={index}
style={{ x, y, zIndex: index, rotate: rotate }}
>
<div
style={{
width: "400px",
height: "300px",
background: `${card.background}`,
}}
>
{card.text}
</div>
</SwipeBox>
) : (
<SwipeBox
card={card}
key={index}
style={{
zIndex: index,
}}
>
<div
style={{
width: "400px",
height: "300px",
background: `${card.background}`,
}}
>
{card.text}
</div>
</SwipeBox>
)
)}
</div>
<div style={{ zIndex: 999 }}>
<button onClick={onClickRight}>✖️</button>
<button onClick={onClickLeft}>○</button>
</div>
</div>
);
};
export default App;
Codesandbox Demo

Material Table rowStyle background color change based on cell value in

I am trying to have the color of the row change colors based on the priority number. For example, if the priority is 4 the color of the entire row should be blue. The problem is that I'm unsure of how to achieve this. I know Material Table assigns ID's to the rows, but I have no way of accessing them. Below is what I have come up with so far. I need options[backgroundColor] to be set based on the priority number. I have a codesandbox here as well https://codesandbox.io/s/notifications-material-ui-spq45
import React from "react";
import { Container } from "react-bootstrap";
import MaterialTable from "material-table";
import FilterListIcon from "#material-ui/icons/FilterList";
import SearchIcon from "#material-ui/icons/Search";
import FirstPage from "#material-ui/icons/FirstPage";
import LastPage from "#material-ui/icons/LastPage";
import ChevronLeft from "#material-ui/icons/ChevronLeft";
import ChevronRight from "#material-ui/icons/ChevronRight";
import CloseIcon from "#material-ui/icons/Close";
function App() {
//loop through the array of priority numbers, and display color based on number
function colors(num) {
for(let i = 0; i < num.length; i++) {
if (num[i] === 4) {
options.rowStyle.backgroundColor = "blue";
}
if (num[i] === 3) {
options.rowStyle.backgroundColor = "red";
}
console.log(num[i]);
}
}
let data = [
{
date: "06-29-2021",
subject: "CANBUS LOSS, Automatic Reboot!",
message:
"SCMs CANBUS LOSS for more than 15 min, Automatic System Reboot!",
category: "System",
priority: 4,
error: "SYS0080"
},
{
date: "06-28-2021",
subject: "Reboot!",
message: "Automatic System Reboot!",
category: "Alarm",
priority: 3,
error: "SYS0090"
},
{
date: "06-25-2021",
subject: "Testing!",
message: "Generator not running!",
category: "Generator",
priority: 2,
error: "SYS0050"
}
];
let columns = [
{ title: "Date", field: "date" },
{ title: "Subject", field: "subject" },
{ title: "Message", field: "message" },
{ title: "Category", field: "category" },
{ title: "Priority Level", field: "priority" },
{ title: "Error Code", field: "error" }
];
let options = {
filtering: false,
sorting: true,
rowStyle: {
fontFamily: "Mulish-Regular",
backgroundColor: ""
},
headerStyle: {
fontFamily: "Mulish-Regular",
fontSize: "1.1em",
fontWeight: "600",
backgroundColor: "#D1D1D8"
},
searchFieldStyle: {
fontFamily: "Mulish-Regular"
}
};
// Loop through all the data and find the priority number, and put it in an array
let map = data.map((x) => x.priority);
console.log(map);
colors(map);
return (
<>
<Container>
<MaterialTable
title=""
columns={columns}
data={data}
options={options}
icons={{
Filter: (props) => <FilterListIcon style={{ fill: "#2D3155 " }} />,
Search: (props) => <SearchIcon style={{ fill: "#2D3155 " }} />,
FirstPage: (props) => <FirstPage style={{ fill: "#2D3155 " }} />,
LastPage: (props) => <LastPage style={{ fill: "#2D3155 " }} />,
NextPage: (props) => <ChevronRight style={{ fill: "#2D3155 " }} />,
PreviousPage: (props) => (
<ChevronLeft style={{ fill: "#2D3155 " }} />
),
SortArrow: (props) => (
<FilterListIcon
style={{ fill: "#2D3155 ", fontSize: "1.4em", margin: ".4em" }}
/>
),
ResetSearch: (props) => <CloseIcon style={{ fill: "#2D3155 " }} />
}}
/>
</Container>
</>
);
}
export default App;
As you can read in the docs, rowStyle accepts an object or a function.
Hence, you can set rowStyle using a function that receives rowData as parameter, an example:
const rowBackgroundColors = {
"2": "yellow", // just for example, remove it if you don't need
"3": "orange",
"4": "red",
};
const options = {
// ...
rowStyle: (rowData) => {
return {
fontFamily: "Mulish-Regular",
backgroundColor: rowBackgroundColors[rowData.priority] ?? "#fff",
};
},
// ...
};

How to create dropdown list with description in React

I am trying to create a dropdown list with description in React. For reference you can see the image below:
Is there any other way using bootstrap or Material-UI so I can achieve this?
I am using react-select npm package for dropdown list. you can find live link and code below:
https://codesandbox.io/embed/react-select-selected-option-background-color-forked-jpu99?fontsize=14&hidenavigation=1&theme=dark
const colourOptions = [
{ value: "red", label: "Red" ,description:"Test description for red"},
{ value: "green", label: "Green", description:"Test description for green" },
{ value: "blue", label: "Blue", description:"Test description for blue" }
];
//for styling
const colourStyles = {
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
// const color = chroma(data.color);
console.log({ data, isDisabled, isFocused, isSelected });
return {
...styles,
backgroundColor: isFocused ? "#00A3BE" : "#191D2F",
font: "Segoe UI",
color: "#F9FAFC"
};
}
};
export default () => (
<Select
defaultValue={colourOptions[1]}
label="Single select"
options={colourOptions}
styles={colourStyles}
/>
);
You can override the Option component and provide your own Option that can display both title and description:
import Select, { components } from "react-select";
const colourStyles = {
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
return {
...styles,
backgroundColor: isFocused ? "#00A3BE" : "",
color: isFocused ? "#F9FAFC" : "#191D2F",
display: "flex",
paddingLeft: 0,
"& .left": {
display: "flex",
justifyContent: "center",
width: 60,
marginTop: 3
},
"& .right": {
width: "100%"
},
"& .right > .title": {
display: "block",
margin: "5px 0"
}
};
}
};
const Option = (props) => {
return (
<components.Option {...props}>
<div className="left">{props.isSelected ? "✔" : ""}</div>
<div className="right">
<strong className="title">{props.data.label}</strong>
<div>{props.data.description}</div>
</div>
</components.Option>
);
};
export default () => (
<Select
defaultValue={colourOptions[1]}
label="Single select"
options={colourOptions}
styles={colourStyles}
components={{ Option }}
/>
);
Live Demo

Click handler issue in React

I have written useState. The console log works, but even though the filter is correct, it doesn't change after the click. May I attach any clickHandler as a constant to an icon or div or section?
The <Task /> is added in the main container.
import React, { useState } from "react";
import { FaTimes } from "react-icons/fa";
const Task = () => {
const deleteTask = (id) => {
setTasks(tasks.filter((task) => task.id !== id));
console.log("deleted");
};
const [tasks, setTasks] = useState([
{ id: 1, text: "Football", reminder: true, day: " -March 22th at 20:30" },
{
id: 2,
text: "Lunch Meeting",
day: " -May 4th at 16:45",
reminder: true,
},
{
id: 3,
text: "Holliday in Bahamas",
reminder: true,
day: " -July 25th at 00:00",
},
]);
return (
<div>
{tasks.map((task) => (
<h3
key={task.id}
style={{
margin: "20px",
backgroundColor: "#cee1f4",
justifyContent: "center",
}}
>
{task.text}
<FaTimes
onClick={deleteTask}
style={{
color: " red",
paddingLeft: "370px",
position: "absolute",
display: "flex",
margin: "-8px 57px",
cursor: "pointer",
}}
/>
<p>{task.day}</p>
</h3>
))}
</div>
);
};
export { Task };
In <FaTimes> component, your onClick does not pass an ID, as is required by that function's parameter input.
This should work:
<FaTimes
onClick={() => deleteTask(task.id)}
style={{
color: " red",
paddingLeft: "370px",
position: "absolute",
display: "flex",
margin: "-8px 57px",
cursor: "pointer",
}}
/>

react-autosuggest how to style input and autosuggestion when using along with Material-ui

I am using react-autosuggest in my Material-UI component to get suggestions when user types. And just not able to style the input field and the suggestions text.
Probably I am missing something basic here, and any guidance will be immensly helpful. The official dox of react-autosuggest is here for using the theme technique that uses react-themeable. But I could not implement that in my Material-UI component.
The below is my code that I am trying with.
import React, { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { makeStyles } from '#material-ui/core/styles'
import Autosuggest from 'react-autosuggest';
import { defaultTheme } from 'react-autosuggest/dist/theme';
const useStyles = makeStyles(theme => ({
container: {
margin: 'auto',
backgroundColor: theme.background.default,
},
innerTableContainer: {
height: 'calc(100vh - 190px)',
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.background.paper,
},
react_autosuggest__container: {
"position": "relative",
"width": "440px",
},
react_autosuggest__input: {
"width": "240px",
"height": "30px",
"padding": "10px 20px",
"fontFamily": "Helvetica, sans-serif",
"fontWeight": "300",
"fontSize": "16px",
"border": "1px solid #aaa",
"borderRadius": "4px"
},
react_autosuggest__input__focused: {
"outline": "none"
},
react_autosuggest__input__open: {
"borderBottomLeftRadius": "0",
"borderBottomRightRadius": "0"
},
react_autosuggest__suggestions_container__open: {
"display": "block",
"position": "absolute",
"top": "51px",
"width": "280px",
"border": "1px solid #aaa",
"backgroundColor": "#fff",
"fontFamily": "Helvetica, sans-serif",
"fontWeight": "300",
"fontSize": "16px",
"borderBottomLeftRadius": "4px",
"borderBottomRightRadius": "4px",
"zIndex": "2"
},
react_autosuggest__suggestions_list: {
"margin": "0",
"padding": "0",
"listStyleType": "none"
},
react_autosuggest__suggestion: {
"cursor": "pointer",
"padding": "10px 20px"
},
react_autosuggest__suggestion__highlighted: {
"backgroundColor": "#ddd"
}
}))
const GithubMostPopularList = () => {
const classes = useStyles()
const [value, setValue] = useState('')
const [suggestions, setSuggestions] = useState([])
const onChange = (event, { newValue, method }) => {
setValue(newValue)
};
const onSuggestionsFetchRequested = ({ value }) => {
setSuggestions(getSuggestions(value))
};
const onSuggestionsClearRequested = () => {
setSuggestions([])
};
const inputProps = {
placeholder: "Start typing your city name",
value,
onChange: onChange,
};
return (
<div className={classes.container}>
<div className={classes.react_autosuggest__container} >
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={onSuggestionsFetchRequested}
onSuggestionsClearRequested={onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
</div>
)}
</div>
)
}
export default GithubMostPopularList
I have also tried this solution given in one of Github issue
<Autosuggest
//misc extra props I've cut out for brevity
theme={{
...defaultTheme,
...{
container: {
...defaultTheme.container,
display: 'visible',
width: '340px',
},
//more overrides
}
}}
/>
But in this case the component is not compiling at all.
Answering my own question.
I was able to solve it as below, the useStyles = makeStyles() portion remains the same and the below is how to change the defulat theme of react-autosuggest.
import { defaultTheme } from 'react-autosuggest/dist/theme';
....
....
const GithubMostPopularList = () => {
.....
.....
return (
<div className={classes.container}>
{console.log('GITHUB USER ', JSON.stringify(globalStore.githubUser))}
<div className={classes.tableAndFabContainer}>
{globalStore.loading ? (
<div className={classes.spinner}>
<LoadingSpinner />
</div>
) : (
<div className={classes.table}>
{console.log('VALUE IS ', value)}
<div className={classes.inputandButtonContainer} >
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={onSuggestionsFetchRequested}
onSuggestionsClearRequested={onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
theme={{
...defaultTheme,
container: classes.react_autosuggest__container,
input: classes.react_autosuggest__input,
inputOpen: classes.react_autosuggest__input__open,
inputFocused: classes.react_autosuggest__input__focused,
suggestionsContainer: classes.react_autosuggest__suggestions_container,
suggestionsContainerOpen: classes.react_autosuggest__suggestions_container__open,
suggestionsList: classes.react_autosuggest__suggestions_list,
suggestion: classes.react_autosuggest__suggestion,
suggestionHighlighted: classes.react_autosuggest__suggestion__highlighted,
}
}
/>
</div>
</div>
)}
</div>
</div>
)
}
export default GithubMostPopularList
Not sure about what you did. This is what I did, and it works well :
One component with the definition of your styling, and the Autosuggest component you render :
import { makeStyles } from '#material-ui/styles';
const useStyles = makeStyles({
container: {
position: "relative",
},
input: {
width: "240px",
height: "30px",
width: '80%',
padding: "10px 20px",
fontFamily: "Helvetica, sans-serif",
fontWeight: "bold",
fontSize: "16px",
border: "1px solid #aaa",
borderRadius: "4px"
},
inputFocused: {
outlineStyle: "none"
}
// add other styling here...
});
const MyAutosuggest = (props) => {
const inputProps = {
placeholder: 'Type a programming language',
value: props.value,
onChange: props.onChange
};
const theme = useStyles();
return(
<Autosuggest
suggestions={props.suggestions}
onSuggestionsFetchRequested={props.onSuggestionsFetchRequested}
onSuggestionsClearRequested={props.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
theme={theme}
/>
)
}
I import MyAutosuggest in the component where I implement autosuggest :
import MyAutosuggest from './Autosuggest';
<MyAutosuggest
value={this.state.value}
onChange={this.onChange}
suggestions={this.state.suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
/>

Resources