I have a table with Drag and Drop method and everything works fine except Drop. So when I want to Drop item into the table's cell there's an error which is caused by infinite loop.
i would be grateful If somebody could explain the reason of this error.
import React, {Component} from 'react';
import { DropTarget } from 'react-dnd';
function collect(connect, monitor) {
return {
connectDropTarget: connect.dropTarget,
isOver: monitor.isOver,
item: monitor.getItem,
}
}
class DropCells extends Component {
state = {
tdValue: ""
};
render() {
const { connectDropTarget, isOver, item} = this.props;
const backgroundColor = isOver ? 'lightgreen' : 'white';
const dropped = isOver? this.setState({tdValue: this.props.item}): this.state.tdValue;
return connectDropTarget(
<td className="target" style={{ background: backgroundColor }}>
{dropped}
</td>
);
}
}
export default DropTarget('item', {}, collect)(DropCells);
You have a call to this.setState({tdValue: this.props.item}) inside of your render function. Every time your component re-renders, it will call this.setState which will trigger another render.
Related
When I use react aria's mergeProps to merge button props and then pass to my component it causes it to override (I'm guessing) the initial props, but not all. The background color won't appear but everything else does, and the styling works fine on hover or any secondary "conditional" props.
Code:
export default function Button(props: ButtonProps): ReactElement {
const p = { ...DEFAULT_PROPS, ...props };
const ref = useRef<HTMLButtonElement>(null);
const { buttonProps, isPressed } = useButton(p, ref);
const { hoverProps, isHovered } = useHover({ isDisabled: p.isDisabled });
const behaviorProps = mergeProps(buttonProps, hoverProps);
return (
<button
className={clsx([
'button-main',
{
'is-hovered': isHovered,
'is-pressed': isPressed,
'is-secondary': p.variant === 'secondary',
},
])}
{...behaviorProps}
>
{p.children}
</button>
);
}
I'm new to React and have to work on a specific assignment where all the logic of my app is in a single parent component and the child component only receives a few props. However, in the child there shouldn't be any logic or almost no logic.
I have a grid (parent component) made of 25 cell and each cell (child component ) can be either on or off. Imagine each cell as a light which is on or off.
From my parent component I'm rendering the cell component 25 times. Every time each cell has:
a key
an id
a status (on or off randomly assigned)
a click event
In my child component when the click event is triggered, the child component return to the parent its id and its status(on or off)
What I want to achieve:
In my parent component I want to be able to detect which child has been clicked and only change the status of the clicked child.
What I get so far:
Despite the parent receive the id and the status of the child that has been clicked, when I change the state via setState, all the children are affected.
Here is a snippet of my parent component:
import React, { Component } from 'react';
import GridSquare from './GridSquare';
import { randomlyLit, cellIdentifier } from '../helpers/helperFuns.js';
let nums = []
cellIdentifier(nums, 25)
class GridContainer extends Component {
static defaultProps = {
gridSize: 25
};
constructor(props) {
super();
this.state = {
cellID: nums,
hasWon: false,
lightStatus: Array.from({ length: 25 }, () => randomlyLit()),
};
this.changeValue = this.changeValue.bind(this);
}
changeValue(id, value) {
console.log(id, value);
this.setState(st => ({
// let result = st.cellID.filter(c => c===id)
// if(result){
// st.value = !value;
// }
lightStatus : !value
})
)
}
render() {
return (
<div>
<h1 className="neon">
Light <span className="flux">Out</span>
</h1>
<div className="GridContainer">
{this.state.cellID.map((el, i) =>(
<GridSquare key={this.state.cellID[i]} id={this.state.cellID[i]} lit={this.state.lightStatus[i]} click={this.changeValue}/>
))}
</div>
</div>
);
}
}
export default GridContainer;
Here is a snippet of my child component:
import React, {Component} from 'react';
class GridSquare extends Component {
constructor(props){
super(props);
this.handleClick= this.handleClick.bind(this)
}
handleClick(){
this.props.click( this.props.id, this.props.lit);
}
render() {
const squareClasses = {
'GridSquareOn': this.props.lit === true,
'GridSquareOff': this.props.lit === false,
'GridSquare': true,
}
function classNames(squareClasses) {
return Object.entries(squareClasses)
.filter(([key, value]) => value)
.map(([key, value]) => key)
.join(' ');
}
const myClassName = classNames(squareClasses)
return(
<div className={myClassName} onClick={this.handleClick}>
</div>
)
}
}
export default GridSquare;
My app.js only renders the parent component and nothing else:
import GridContainer from './components/GridContainer.jsx'
import './style/App.css';
import './style/GridContainer.css';
import './style/GridSquare.css';
function App() {
return (
<div className="App">
<GridContainer />
</div>
);
}
export default App;
Thank you in advance for any help!
changeValue(id, value) {
console.log(id, value);
this.setState(st => ({
// let result = st.cellID.filter(c => c===id)
// if(result){
// st.value = !value;
// }
lightStatus : !value
})
Your change value function is updating the state with the wrong value.
Initial value of your lightStatus is an array of booleans ( assuming randomlyLit function will return a boolean.
When you click on one of the cells, the lightStatus gets updated with the value of false instead of an array.
To fix this, search through the entire lightStatus array by index for which the cell was clicked and update the boolean at that particular index using Array.slice.
How to Optimise
Instead of traversing the whole array every time to update the lightStatus of the cell. You can save the the value in an Object.
What if I could update the status in changeValue like this ?
this.setState((st) => {
return {
...st,
lightStatus: { ...st.lightStatus, [id]: value } // Direct update without traversing through array
}
});
lightStatus can be used to form a "mapping" between cell ids in cellID and their corresponding status booleans.
I have a problem to reinitialize the ion-phaser function component in a parent component. It works fine by reinitilization just as a class component. Bellow are the two examples to displays which works and which not.
Here is my parent render function:
render() {
return(
<>
{this.state.visible && <IonComponent />}
</>
)
}
Here is the Ion-Phaser function component (this doesn't work):
let game = { ..here comes the Phaser game logic }
const IonComponent = () => {
const [initialize, setInitialize] = useState(false);
useEffect(() => {
if (game?.instance === undefined) {
setInitialize(true);
}
}, [initialize]);
return (
<>
{ initialize && <IonPhaser game={game} initialize={initialize} />}
</>
)
}
export default IonComponent;
Here is the Ion-Phaser class component (this works):
class IonComponent extends React.Component {
state = {
initialize: true,
game: { ..here comes the Phaser game logic }
}
render() {
const { initialize, game } = this.state
return (
<IonPhaser game={game} initialize={initialize} />
)
}
}
export default IonComponent;
When I switch in the parent component the state.visible at the first render to true, it initiate the child IonPhaser component without any problems. But after the state.visible switch once to false and then again back to true, the function component will not reinitialize and it removes the canvas from the dom. The class component however works fine.
Is this a persistent bug in Ion-Phaser by function component or am I doing something wrong?
Make sure you're keeping track of the state of your game using React's useState() hook or something similar.
For what it's worth, I also ran into some issues while trying to get the IonPhaser package working. To get around these issues, I've published an alternative way to integrate Phaser and React here that (I think) is more straightforward.
It works mostly the same way, but it doesn't come with as much overhead as the IonPhaser package.
npm i phaser-react-tools
And then you can import the GameComponent into your App.js file like so:
import { GameComponent } from 'phaser-react-tools'
export default function App() {
return (
<GameComponent
config={{
backgroundColor: '000000',
height: 300,
width: 400,
scene: {
preload: function () {
console.log('preload')
},
create: function () {
console.log('create')
}
}
}}
>
{/* YOUR GAME UI GOES HERE */}
</GameComponent>
)
}
It also comes with hooks that let you emit and subscribe to game events directly from your React components. Hope this helps, and please get in touch if you have feedback.
I have a section with a fixed height. I don't know when the component mounts (first renders) whether the content coming in will fit or not. If it does NOT fit, then I need to render a 'Read More' button.
It looks like this:
I wrote this originally as a Class component using the lifecycle methods DidMount/DidUpdate:
Class Component
import React, { createRef } from "react"
import styled from "#emotion/styled"
import Section from "../Section"
import ButtonReadMore from "./ButtonReadMore"
import Paragraphs from "./Paragraphs"
const StyledHeightContainer = styled.div`
max-height: 150px;
overflow: hidden;
`
class ParagraphList extends React.Component {
state = {
overflowActive: false,
}
wrapper = createRef() // so we can get a ref to the height container
isOverflowing(el) {
if (el) return el.offsetHeight < el.scrollHeight
}
componentDidMount() {
this.setState({ overflowActive: this.isOverflowing(this.wrapper.current) })
}
componentDidUpdate() {
if (this.wrapper.current && !this.state.overflowActive) {
this.setState({
overflowActive: this.isOverflowing(this.wrapper.current),
})
}
}
handleClick() {
this.setState({ overflowActive: false })
}
render() {
const { moreButtonText, titleText, paragraphs, theme } = this.props
return (
<>
<Section overflowActive={this.state.overflowActive}>
{this.state.overflowActive || !this.wrapper.current ? (
<StyledHeightContainer ref={this.wrapper}>
<Paragraphs paragraphs={paragraphs} />
</StyledHeightContainer>
) : (
<Paragraphs paragraphs={paragraphs} />
)}
</Section>
{overflowActive ?
<ButtonReadMore
onClicked={handleClick.bind(this)}
moreButtonText={moreButtonText}
theme={theme}
/>
: null}
</>
)
}
}
export default ParagraphList
My best way to explain the flow:
When the component mounts, the flag is false and we have no reference to the div so the StyledHeightContainer will try to render and thus provide a ref to it
In componentDidMount -> try to set the overflow flag (which will be false because at this point we do not yet have rendering completed so the ref will be null). But by setting the flag anyway, we queue an additional render pass
1st INITIAL rendering completes -> we have a ref to the div now
The 2nd (queued) render occurs, firing the componentDidUpdate -> we can calculate the overflow and set the flag to true when the content overflows
When the user clicks the button -> set the flag to false, which will trigger a re-render and hence the StyledHeightContainer will be removed from the DOM.
Functional Component With Hooks
Sandbox of the code
When I re-wrote this as a functional component using Hooks, I ended up with this:
import React, { createRef, useEffect, useState } from "react"
import styled from "#emotion/styled"
import Section from "../Section"
import ButtonReadMore from "./ButtonReadMore"
import Paragraphs from "./Paragraphs"
const StyledHeightContainer = styled.div`
max-height: 150px;
overflow: hidden;
`
const ParagraphList = ({ moreButtonText, titleText, paragraphs, theme }) => {
const [overflowActive, setOverflowActive] = useState(false)
const [userClicked, setUserClicked] = useState(false)
const wrapper = createRef(false) // so we can get a ref to the height container
const isOverflowing = el => {
if (el) return el.offsetHeight < el.scrollHeight
}
useEffect(() => {
if (!userClicked && !overflowActive && wrapper.current) {
setOverflowActive(isOverflowing(wrapper.current))
}
}, [userClicked]) // note: we only care about state change if user clicks 'Read More' button
const handleClick = () => {
setOverflowActive(false)
setUserClicked(true)
}
return (
<>
<Section theme={theme} overflowActive={overflowActive}>
{!userClicked && (overflowActive || !wrapper.current) ? (
<StyledHeightContainer ref={wrapper}>
<Paragraphs paragraphs={paragraphs} />
</StyledHeightContainer>
) : (
<Paragraphs paragraphs={paragraphs} />
)}
</Section>
{overflowActive ?
<ButtonReadMore
onClicked={handleClick.bind(null)}
moreButtonText={moreButtonText}
theme={theme}
/>
: null}
</>
)
}
export default ParagraphList
I was surprised that I needed to add another state (userClicked), which is how I force the 2nd render to occur (ie. the equivalent to the componentDidUpdate in the class solution).
Is this correct or can someone see a more concise way to write the 2nd solution?
NOTE
One of the reasons I ask is because in the console I get this warning:
48:6 warning React Hook useEffect has missing dependencies:
'overflowActive' and 'wrapper'. Either include them or remove the
dependency array react-hooks/exhaustive-deps
and I don't THINK I want to add them to the dependency array, as I don't want to trigger rendering when they change...?
I really enjoyed while solving the query.
Here is the implementation: https://codesandbox.io/s/react-using-hooks-in-section-component-5gibi?file=/src/ParagraphList.js
First of all, I was thinking of
useEffect(() => {
setOverflowActive(isOverflowing(wrapper.current));
}, [wrapper]);
But if we do this, it will again call the useEffect as when we'll click on the Read more button. Because it was comparing the reference of the wrapper and not it's value.
So, to avoid the reference comparison we have to use the useCallback hook.
const isOverflowingNode = node => {
return node.offsetHeight < node.scrollHeight;
};
const wrapper = useCallback(node => {
if (node !== null) {
setOverflowActive(isOverflowingNode(node));
}
}, []);
I came across the beautiful discussion: https://github.com/facebook/react/issues/14387
For more information:
https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node
Thanks for the question :)
You could add an extra useEffect(() => (...),[]) that acts like componentDidMount(). And another useEffect(() => (...)) that acts like componentDidUpdate(). Then you should be able to get rid of userClicked.
This is a good link on how the lifestyle methods work with hooks. https://dev.to/trentyang/replace-lifecycle-with-hooks-in-react-3d4n
useEffect(() => {
setOverflowActive(isOverflowing(wrapper.current));
}, []);
useEffect(() => {
if (!overflowActive && wrapper.current) {
setOverflowActive(isOverflowing(wrapper.current))
}
});
The second one might need to be useLayoutEffect if you are wanting the update to happen after the layout.
I have a component that displays search data returned from the Spotify API. However, every time I update the state the UI flickers:
Input:
<DebounceInput
debounceTimeout={300}
onChange={handleChange}
/>
Hook:
const [searchResults, setSearchResults] = useState(null)
API call w/ Apollo:
const searchSpotify = async (query) => {
const result = await props.client.query({
query: SearchTracks,
variables: {
query
}
})
const tracks = result.data.searchedTracks
setSearchResults(tracks)
}
Render:
{searchResults &&
<div className="search-results">
{searchResults.map((song) => (
<SongInfo key={song.id} {...song} />
))}
</div>
}
I noticed it only happens on the first load. For example, if I were to type the query again it shows without flickering. Is there a better way to implement this so the UI doesn't flicker?
Below are the frames that cause the flicker. What I think is happening is it takes some time for the images to load. While they are loading the items have reduced height. You should make sure SongInfo layout does not depend on whether the image has been loaded or not.
Images not loaded - items are collapsed:
Images were loaded:
I think whats happening is that you are executing a search query on every key stroke which is causing the weird behavior.
Use lodash debounce to avoid doing a search on every key stroke.
That should address the flickering. (Also, adding a loading state will help)
Sample debounce component
import React, {Component} from 'react'
import { debounce } from 'lodash'
class TableSearch extends Component {
//********************************************/
constructor(props){
super(props)
this.state = {
value: props.value
}
this.changeSearch = debounce(this.props.changeSearch, 250)
}
//********************************************/
handleChange = (e) => {
const val = e.target.value
this.setState({ value: val }, () => {
this.changeSearch(val)
})
}
//********************************************/
render() {
return (
<input
onChange = {this.handleChange}
value = {this.props.value}
/>
)
}
//********************************************/
}