How can update the DOM if the state changes? - javascript

I've created the box container dynamically according to the input changes.
If I entered 1, it will create one box
If I change the input lets say 2, its create 3 box but it should create 2
import React from 'react';
import './style.css';
export default function App() {
const [value, setValue] = React.useState();
const boxRef = React.useRef();
function createBox() {
const div = document.createElement('div');
div.classList.add('mystyle');
div.style.backgroundColor = 'white';
div.addEventListener('click', () => {
let boxColor = div.style.backgroundColor;
if (boxColor === 'white') {
div.style.backgroundColor = 'red';
} else {
div.style.backgroundColor = 'white';
}
});
return div;
}
React.useEffect(() => {
for (let i = 0; i < value; i++) {
const boxElement = createBox();
boxRef.current.appendChild(boxElement);
}
}, [value]);
function handleBoxCreate({ target: { value } }) {
setValue(value);
}
return (
<div>
<h1>BOX CREATE</h1>
<input type="number" name="boxInput" onChange={handleBoxCreate} />
<div ref={boxRef} />
</div>
);
}
/* style.css */
.mystyle {
width: 30px;
height: 30px;
border: 2px solid black;
display: inline-block;
padding: 2px;
margin-right: 5px;
}
Do I need to cleanup the dom. if so how to do it?. or is there any better way to implement the same.
Please help. ty:)

You should avoid doing direct manipulations on the DOM. Instead create a "Box" react component and render it based on the amount of your value state.
import React from "react";
import "./styles.css";
const Box = () => {
const [color, setColor] = React.useState("white");
const onClick = () => {
if (color === "white") {
setColor("red");
} else {
setColor("white");
}
};
return (
<div
className="mystyle"
style={{ backgroundColor: color }}
onClick={onClick}
/>
);
};
export default function App() {
const [value, setValue] = React.useState(0);
function handleBoxCreate({ target: { value } }) {
setValue(Number(value));
}
return (
<div>
<h1>BOX CREATE</h1>
<input type="number" name="boxInput" onChange={handleBoxCreate} />
{[...Array(value)].map((e, i) => (
<Box key={i} />
))}
</div>
);
}

Related

How to reparent Node in React

I want to change the parent of a Node in a way that
<div>
<children/>
</div>
becomes
<div>
<NewParent>
<children/>
</NewParent>
</div>
I need this to put a mui Tooltip above a component that overflows with ellipsis.
I implemented a small algorithm to find the needed element but when I try to use portals for this case this happens. enter image description here
My NewParent becomes the sibbling of the old parent.
Later I learned that usePortal brings the children to the parent and doesn't wrap the parent to the children so my question is what can I do to wrap a new parent to the node and make the old parent be the grandfather as per my first example
Current component
import React, { useRef, useEffect, useState } from 'react';
import { Tooltip } from '#mui/material';
import { Theme } from '#mui/material';
import { makeStyles } from '#mui/styles'
import { GridCellProps, GridCell } from '#mui/x-data-grid';
import { createPortal } from 'react-dom';
const useStyles = makeStyles<Theme>(() => ({
overflowEllipsis: {
width: '100%',
},
}))
const OverflowTip = React.forwardRef(({ children, ...props }: GridCellProps) => {
const [ portaled, setPortaled ] = useState(false)
const textElementRef = useRef<HTMLDivElement>();
const TooltipRef = useRef<HTMLDivElement>();
const classes = useStyles()
const compareSize = () => {
if (!textElementRef.current) {
return
}
const compare =
textElementRef.current.scrollWidth > textElementRef.current.clientWidth;
setHover(compare);
};
const findLowestChildren = (currentElement) => {
if (!currentElement) {
return
}
if (currentElement.children.length === 0) {
console.log(currentElement);
console.log(TooltipRef);
setPortaled(true)
createPortal(currentElement, TooltipRef.current)
currentElement.className += ` ${classes.overflowEllipsis}`
}
const arr = [].slice.call(currentElement.children);
return arr.forEach((ch) => {
if (ch.tagName === 'DIV' || ch.tagName === 'P' || ch.tagName === 'SPAN') {
return findLowestChildren(ch)
}
});
}
// compare once and add resize listener on "componentDidMount"
useEffect(() => {
compareSize();
window.addEventListener('resize', compareSize);
if (!portaled) {
findLowestChildren(textElementRef.current)
}
}, []);
// remove resize listener again on "componentWillUnmount"
useEffect(() => () => {
window.removeEventListener('resize', compareSize);
}, []);
// Define state and function to update the value
const [ hoverStatus, setHover ] = useState(false);
// console.log(props);
return (
<div ref={textElementRef}>
<GridCell
{...props}>
{children}
</GridCell>
<div ref={TooltipRef} className="wwwwwwww"><Tooltip title="QWEW"><></></Tooltip></div>
</div>
);
// return (
// <Tooltip
// title={children}
// disableHoverListener={!hoverStatus}
// >
// <BoxContainer
// ref={textElementRef}
// style={{
// whiteSpace: 'nowrap',
// overflow: 'hidden',
// textOverflow: 'ellipsis',
// }}>
// <GridCell
// {...props}
// >
// {children}
// </GridCell>
// </BoxContainer>
// </Tooltip>
// );
});
export default OverflowTip;

How to trigger a re-rendering of my Icon Component in React?

SOLUTION
Remove the style={{ fill: 'green' }} from the component and add this to the css file:
/*index.css*/
/* INFO: the sidebarleft-button contains a child that is an <svg> */
.sidebarleft-button-active > * {
fill: rgb(192, 192, 192);
}
.sidebarleft-button:hover > * {
fill: rgb(192, 192, 192);
}
PROBLEM(That is now solved)
I am working on a SideBar for my Application. The Sidebar contains Buttons. Each Button contains an Icon. I want to change the color of the icon if I hover with my mouse over the button. I tried many things and I wonder why no re-rendering is triggered because the icon changed and a change means re-rendering. I can see that the style.fill gets changed to blue in the inspector window of my browser.
I show you my code. The comments inside the code may help you.
//sideBarLeft.jsx
import React, { useState } from 'react';
import SideBarButton from './sideBarButton';
import { DIAGRAMTYPE } from '../diagramComponent/diagramUtils';
import { ReactComponent as BarChartIcon } from '../../icon/barChart.svg';
const SideBarLeft = (props) => {
const [btn0Active, setBtn0Active] = useState(true);
const [btn1Active, setBtn1Active] = useState(false);
const deactivateOtherButtonsAndActiveThisButton = (
idOfButtonThatWasClicked
) => {
switch (idOfButtonThatWasClicked) {
case 0:
setBtn1Active(false);
setBtn0Active(true);
break;
case 1:
setBtn0Active(false);
setBtn1Active(true);
break;
default:
setBtn0Active(false);
setBtn1Active(false);
}
};
return (
<div className={'sidebarleft'}>
<SideBarButton
active={btn0Active}
id={0}
deactivateOtherButtonsAndActiveThisButton={
deactivateOtherButtonsAndActiveThisButton
}
icon={<BarChartIcon style={{ fill: 'green' }} />}
diagramType={DIAGRAMTYPE.barChartPos}
/>
<SideBarButton
active={btn1Active}
id={1}
deactivateOtherButtonsAndActiveThisButton={
deactivateOtherButtonsAndActiveThisButton
}
icon={<BarChartIcon style={{ fill: 'green' }} />}
diagramType={DIAGRAMTYPE.barChartWordOccurence}
/>
</div>
);
};
export default SideBarLeft;
import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionCreators } from '../../state/index';
const SideBarButton = (props) => {
const dispatch = useDispatch();
const { switchToDiagramType } = bindActionCreators(actionCreators, dispatch);
const [cssClass, setCssClass] = useState('sidebarleft-button');
const onClickFn = () => {
props.deactivateOtherButtonsAndActiveThisButton(props.id);
switchToDiagramType(props.diagramType);
};
const [icon, setIcon] = useState(props.icon);
const buttonHandler = () => {};
const onMouseEnter = (component) => {
console.log('in onMouseEnter()');
const classOfComponent = component.target.classList.value;
if (classOfComponent === 'sidebarleft-button-active') {
console.log('component is active');
} else if (classOfComponent === 'sidebarleft-button') {
console.log('before');
console.log(props.icon); //even without mousenter, the component is already blue...
let changedIcon = props.icon;
props.icon.props.style.fill = 'blue';
setIcon(changedIcon); //in the inspector
//there is props->style->fill == 'blue'
//why does it not get rendered then in blue???
console.log('after');
console.log(changedIcon);
console.log('component is not active');
// console.log(component);
}
};
console.log('beforebefore');
console.log(icon);
return (
<button
onClick={onClickFn}
className={cssClass}
onMouseEnter={(component) => onMouseEnter(component)}
>
{icon}
</button>
);
};
export default SideBarButton;
Why not a css or sass approach?
.sidebarleft-button-active {
&:hover {
fill: blue;
}
}

React useState lagging by one keystroke

Okay - I'm having an issue with my react app onChange attribute lagging by one keystroke.
I am pretty sure this has to do with this code block running before the state is being updated.
if (hex.length === 3) {
let newColor = hex.join('');
let newColors = [...colors, newColor];
setColors(newColors);
setHex([]);
}
I tried moving the above block to a useEffect hook (as such) so that it would run when the value of hex changes to remedy this.
useEffect(() => {
if (hex.length === 3) {
let newColor = hex.join('');
let newColors = [...colors, newColor];
setColors(newColors);
setHex([]);
}
}, hex)
This did not work as expected, and I'm still facing the same issue. The goal is when an input is received, if the length of the total ASCII input is 3 characters or more, it will convert that text ([61, 61, 61] for example) into hex string of 6 characters, which will eventually be converted into a color hex code.
All of my code is as follows.
import TextInput from './components/TextInput';
import Swatch from './components/Swatch';
import './App.css';
function App() {
const [colors, setColors] = useState([]);
const [hex, setHex] = useState([]);
const [text, setText] = useState();
const convertToHex = (e) => {
const inputText = e.target.value;
setText(inputText);
for (let n = 0, l = inputText.length; n < l; n++) {
let newHex = Number(inputText.charCodeAt(n)).toString(16);
let newHexArr = [...hex, newHex];
setHex(newHexArr);
}
if (hex.length === 3) {
let newColor = hex.join('');
let newColors = [...colors, newColor];
setColors(newColors);
setHex([]);
}
};
return (
<div className='App'>
<h1 id='title'>Color Palette Generator</h1>
<TextInput func={convertToHex} />
<Swatch color='#55444' />
</div>
);
}
export default App;
How is the TextInput component managing its state (the input's value)?
I suspect you might have a problem there, as changing that TextInput component with a simple controlled input:
<input type="text" onChange={ handleOnChange } value={ text } />
And using your existing code as handleOnChange works as expected:
const App = () => {
const [colors, setColors] = React.useState([]);
const [hex, setHex] = React.useState([]);
const [text, setText] = React.useState('');
const handleOnChange = (e) => {
const inputText = e.target.value;
// I thought your problem was related to `text`...:
setText(inputText);
for (let n = 0, l = inputText.length; n < l; n++) {
let newHex = Number(inputText.charCodeAt(n)).toString(16);
let newHexArr = [...hex, newHex];
setHex(newHexArr);
}
if (hex.length === 3) {
// But looking at the comments it looks like it's related to `colors`, so as
// pointed out already you should be using the functional version of `setState`
// to make sure you are using the most recent value of `colors` when updating
// them here:
setColors(prevColors => [...prevColors, hex.join('')]);
setHex([]);
}
};
return (
<div className="app">
<h1 className="title">Color Palette Generator</h1>
<input className="input" type="text" onChange={ handleOnChange } value={ text } />
<pre>{ hex.join('') }</pre>
<pre>{ colors.join(', ') }</pre>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#app'));
body {
font-family: monospace;
margin: 0;
}
.app {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
.title {
display: flex;
margin: 32px 0;
}
.input {
margin: 0 4px;
padding: 8px;
border: 2px solid black;
background: transparent;
border-radius: 2px;
}
.as-console-wrapper {
max-height: 45px !important;
}
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
In any case, there are a few other things that could be improved on that code, such as using a single useState, using useCallback, adding a separate <button> to push colors to the state or actually doing the RGB to HEX conversion:
const App = () => {
const [state, setState] = React.useState({
value: '',
hex: '',
colors: [],
});
const handleInputChange = React.useCallback((e) => {
const nextValue = e.target.value || '';
const nextHex = nextValue
.trim()
.replace(/[^0-9,]/g, '')
.split(',')
.map(x => `0${ parseInt(x).toString(16) }`.slice(-2))
.join('')
.toUpperCase();
setState((prevState) => ({
value: nextValue,
hex: nextHex,
colors: prevState.colors,
}));
}, []);
const handleButtonClick = React.useCallback(() => {
setState((prevState) => {
return prevState.hex.length === 3 || prevState.hex.length === 6 ? {
value: '',
hex: '',
colors: [...prevState.colors, prevState.hex],
} : prevState;
});
}, []);
return (
<div className="app">
<h1 className="title">Color Palette Generator</h1>
<input
type="text"
className="input"
value={ state.value }
onChange={ handleInputChange } />
<input
type="text"
className="input"
value={ `#${ state.hex }` }
readOnly />
<button className="button" onClick={ handleButtonClick }>
Add color
</button>
<pre className="input">{ state.colors.join('\n') }</pre>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#app'));
body {
font-family: monospace;
margin: 0;
}
.app {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
.title {
display: flex;
margin: 32px 0;
}
.input,
.button {
margin: 8px 0 0;
padding: 8px;
border: 2px solid black;
background: transparent;
border-radius: 2px;
width: 50vw;
box-sizing: border-box;
}
.button {
cursor: pointer;
}
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>

react-select input closes before I can (mouseclick) select an option

EDIT: this problem happens in Firefox (Ubuntu 16), but using Chrome I don't have the problem.
Using React.js, and react-select, I have the following situation:
When I click on the select input, the dropdown with options shows, but it closes almost immediately.
Desired behavior: keep it open until I select an option.
Does anyone know why this is happening?
Here is my code (some of it at least).
component containing the select input:
import React from "react";
import { sec } from "../style/Colors";
import Select from "react-select";
const TagSelectForm = ({ onTagSelectChange, options }) => {
return (
<div className="tagselect-main-container">
<Select isMulti={true} onChange={onTagSelectChange} options={options} />
</div>
);
};
export default TagSelectForm;
Parent component:
import React, { Component } from "react";
import ContentRequest from "../components/ContentRequest";
import axios from "axios";
import TagSelectForm from "../components/TagSelectForm";
import styled from "styled-components";
class OverviewPage extends Component {
state = {
contentRequests: [],
contentRequestTags: [],
filterTags: []
};
async componentDidMount() {
const { data: JSON_string } = await axios.get(
"http://localhost:8000/request"
);
const { requests, tags } = JSON.parse(JSON_string);
this.setState({ contentRequests: requests, contentRequestTags: tags });
}
filterByTags = () => {
const { contentRequests } = this.state;
const filteredRequests = contentRequests.filter(request =>
this.testContainsAFilterTag(request.tags)
);
return filteredRequests;
};
handleAddFilterTag = tag => {
const filterTags = [...this.state.filterTags, tag];
this.setState({ filterTags });
};
handleTagSelectChange = selectedTagsList => {
this.setState({ filterTags: selectedTagsList });
};
handleRemoveFilterTag = tagID => {
const filterTags = this.state.filterTags.filter(tag => tag.id !== tagID);
console.log("filterTags", filterTags);
this.setState({ filterTags });
};
setOverViewpageState = (stateName, stateValue) => {
this.setState({ [stateName]: stateValue });
};
testContainsAFilterTag = tags => {
const { filterTags } = this.state;
const filterTagIDs = filterTags.map(tag => tag.value);
return tags.some(tag => filterTagIDs.includes(tag.id));
};
renderRequests = () => {
let { contentRequests } = this.state;
const { filterTags } = this.state;
const { loginToken, userID } = this.props;
if (filterTags.length > 0) {
contentRequests = this.filterByTags();
}
return (
<RequestList>
{contentRequests.map(request => (
<ContentRequest
contentRequests={contentRequests}
key={request.id}
loginToken={loginToken}
request={request}
setOverViewpageState={this.setOverViewpageState}
userID={userID}
/>
))}
</RequestList>
);
};
render() {
const { contentRequestTags, filterTags } = this.state;
return (
<MainContainer>
<PageTitle>Content Request Overview</PageTitle>
<TagSelectForm
onTagSelectChange={this.handleTagSelectChange}
options={contentRequestTags}
/>
{this.renderRequests()}
</MainContainer>
);
}
}
export default OverviewPage;
const MainContainer = styled.div`
box-sizing: border-box;
display; flex;
flex-direction: column;
max-width: 768px;
margin: 0 auto;
padding: 0.5rem;
`;
const PageTitle = styled.h1`
font-size: 1.25rem;
`;
const RequestList = styled.ul`
list-style-type: none;
padding: 0;
`;
I have resolved this issue by wrapping react-select in "label" tag, so my code looks like:
<label>
<Select
name="name"
options={optionsArray}
...
/>
</label>

States aren't being updated

I'm currently working on an image uploader component in React. Everything works fine but the deleting method. I've read a couple of articles on how to update arrays/objects and the idea of immutable state. Here's what I've tried:
.filter()
.slice()
.splice() (I doubt this would work as it modifies the original array)
And I always got this error no matter what I tried:
Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
And this is my code:
ImageUploader.js
import React, { Component } from 'react';
import styled from 'styled-components';
import FileUploadButton from '../FileUploadButton';
import ImagePreviewer from './ImagePreviewer';
import {
Typography,
Button
} from '#material-ui/core';
import theme from '../../../theme';
import uuidv5 from 'uuid/v5';
const StyledPreviewerContainer = styled.div`
display: flex;
margin: ${theme.spacing.unit}px 0;
overflow: hidden;
overflow-x: auto;
`;
export default class ImageUploader extends Component {
state = {
uploadedImages: []
}
updateImages = e => {
const { uploadedImages } = this.state,
files = [...e.target.files],
inexistentImages = files.filter(image => uploadedImages.indexOf(image) === -1);
this.setState(prevState => ({
uploadedImages: [...prevState.uploadedImages, ...inexistentImages]
}));
this.props.onChange(e);
}
removeImages = image => {
const { uploadedImages } = this.state,
imageIndex = uploadedImages.indexOf(image);
this.setState(prevState => ({
uploadedImages: prevState.uploadedImages.filter((image, index) => index !== imageIndex)
}));
};
render() {
const {
className,
label,
id,
multiple,
name,
onBlur
} = this.props, {
uploadedImages
} = this.state;
return (
<div className={className}>
<Typography>
{label}
</Typography>
<StyledPreviewerContainer>
{uploadedImages.map(image =>
<ImagePreviewer
src={URL.createObjectURL(image)}
image={image}
removeImages={this.removeImages}
key={uuidv5(image.name, uuidv5.URL)}
/>
)}
</StyledPreviewerContainer>
<FileUploadButton
id={id}
multiple={multiple}
name={name}
onChange={this.updateImages}
onBlur={onBlur}
/>
<Button>
Delete all
</Button>
</div>
);
}
}
ImagePreviewer.js
import React, { Component } from 'react';
import styled from 'styled-components';
import AnimatedImageActions from './AnimatedImageActions';
import { ClickAwayListener } from '#material-ui/core';
import theme from '../../../theme';
const StyledImagePreviewer = styled.div`
height: 128px;
position: relative;
user-select: none;
cursor: pointer;
&:not(:last-child) {
margin-right: ${theme.spacing.unit * 2}px;
}
`;
const StyledImage = styled.img`
height: 100%;
`;
export default class ImagePreviewer extends Component {
state = {
actionsOpened: false
};
openActions = () => {
this.setState({
actionsOpened: true
});
};
closeActions = () => {
this.setState({
actionsOpened: false
});
};
render() {
const {
actionsOpened
} = this.state,
{
src,
image,
removeImages
} = this.props;
return (
<ClickAwayListener onClickAway={this.closeActions}>
<StyledImagePreviewer onClick={this.openActions}>
<StyledImage src={src} />
<AnimatedImageActions
actionsOpened={actionsOpened}
image={image}
removeImages={removeImages}
/>
</StyledImagePreviewer>
</ClickAwayListener>
);
}
}
AnimatedImageActions.js
import React from 'react';
import styled from 'styled-components';
import { Button } from '#material-ui/core';
import { Delete as DeleteIcon } from '#material-ui/icons';
import { fade } from '#material-ui/core/styles/colorManipulator';
import theme from '../../../theme';
import {
Motion,
spring
} from 'react-motion';
const StyledImageActions = styled.div`
position: absolute;
top: 0;
left: 0;
color: ${theme.palette.common.white};
background-color: ${fade(theme.palette.common.black, 0.4)};
width: 100%;
height: 100%;
display: flex;
`;
const StyledImageActionsInner = styled.div`
margin: auto;
`;
const StyledDeleteIcon = styled(DeleteIcon)`
margin-right: ${theme.spacing.unit}px;
`;
const AnimatedImageActions = ({ actionsOpened, removeImages, image }) =>
<Motion
defaultStyle={{
scale: 0
}}
style={{
scale: spring(actionsOpened ? 1 : 0, {
stiffness: 250
})
}}
>
{({ scale }) =>
<StyledImageActions style={{
transform: `scale(${scale})`
}}>
<StyledImageActionsInner>
<Button
color="inherit"
onClick={removeImages(image)}
>
<StyledDeleteIcon />
Delete
</Button>
</StyledImageActionsInner>
</StyledImageActions>
}
</Motion>
;
export default AnimatedImageActions
Any help would be greatly appreciated!
Could it be that onClick={removeImages(image)} should be onClick={()=>removeImages(image)}?
Otherwise, removeImages is calling setState in AnimatedImageActions's render pass.

Categories