Issue refreshing child components in React after implementing React DnD - reactjs

I'm trying to implement a sortable list of sortable lists in React, using React DnD. Prior to implementing the drag and drop side of things, all was working well.
I have a container component, which renders this:
<DndProvider backend={Backend}>
{this.state.classifications.map((classification, index) =>
<Classification id={classification.id} classification={classification} reportTemplate={this} key={classification.id} index={index} />
)}
</DndProvider>
The Classification extends Component, constructed like this:
constructor(props) {
super(props);
this.state = {
isEditing: false,
classification: props.classification
};
}
... and renders this (condensed for brevity):
<div className="panel-body">
<DndProvider backend={Backend}>
{this.state.classification.labels.map((label, index) =>
<Label id={label.id} label={label} reportTemplate={this.props.reportTemplate} key={label.id} index={index} />
)}
</DndProvider>
</div>
In turn, the Label also extends component, constructed like this:
constructor(props) {
super(props);
this.state = {
isEditing: false,
label: props.label
};
}
... and renders like this (again condensed for brevity):
return (
<div className={"panel panel-default panel-label " + (isDragging ? "dragging " : "") + (isOver ? " over" : "")}>
<div className="panel-heading" role="tab" id={"label-" + this.state.label.id}>
<div className="panel-title">
<div className="row">
<div className="col-xs-6 label-details">
{this.state.isEditing
? <input type="text" className="form-control" value={this.state.label.value} onChange={e => this.props.reportTemplate.onLabelValueChange(e, this.state.label.classificationId, this.state.label.id, 'value')} />
: <p className="form-control-static">{this.state.label.value}</p>
}
<div className="subuser-container">...</div>
</div>
</div>
</div>
</div>
);
All of this works well - when the user makes a change from the Label child component, it gets updated in the root component and everything syncs up and refreshes.
However, when implementing React DnD, both the Classification and Label components have been wrapped in Drag and Drop decorators, to provide sorting. The sorting via drag and drop works perfectly. However: this has caused the updating of elements to stop working (i.e., when a change is made from the Label, the update is fed through to the root Component correctly, but it doesn't then refresh down the tree).
Both the classification and label dnd implementations are like this in the render method:
return connectDropTarget(connectDragSource(...));
... and this when exporting the component:
export default DropTarget('classification', classificationTarget, classificationDropCollect)(DragSource('classification', classificationSource, classificationDragCollect)(Classification));
Interestingly, when a label is edited, the refresh does then occur when the user drags and drops the component. So its like the drag and drop will trigger a component refresh, but not the other onChange functions.
That was a long question, apologies. I'm almost certain someone else will have experienced this issue, so any pointers gratefully appreciated.

Ok so i've basically answered my own question, but many thanks to those who posted comments on here to help narrow it down.
The answer was that my state object is complex and deep, and the component/decorator implementation of React DnD seems to have an issue with that. My assumption is that there is some behaviour in the decorator's shouldComponentUpdate that is blocking the components refresh when a deep property is update. React DnD's own documentation refers to the decorators as "legacy", which is fair enough.
I updated our components to use hooks instead of decorators, and it all sprang to life. So the answer is this, if your DnD implementation is deep and complex, use hooks.

Here is an example of your code without DnD that won't work either because the prop is copied to state in the constructor and on concurrent renders the prop is never used again, only the state.
In Child you can see that it will render but counter never changes, in CorrectChild the counter will change because it's just using props.counter.
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {//copied only once in constructor
counterFromParent: props.counter,
};
}
rendered=0;
render() {
this.rendered++;
return (
<div>
<h3>in broken Child rendered {this.rendered} times</h3>
<button onClick={this.props.up}>UP</button>
<pre>
{JSON.stringify(this.state, undefined, 2)}
</pre>
</div>
);
}
}
class CorrectChild extends React.Component {
render() {
//not copying props.count, just using it
return (
<div>
<h3>in correct Child</h3>
<button onClick={this.props.up}>UP</button>
<pre>
{JSON.stringify(this.props, undefined, 2)}
</pre>
</div>
);
}
}
function App() {
const [state, setState] = React.useState({ counter: 1 });
const up = React.useCallback(
() =>
setState((state) => ({
...state,
counter: state.counter + 1,
})),
[]
);
return (
<div>
<h3>state in App:</h3>
<pre>{JSON.stringify(state, undefined, 2)}</pre>
<Child counter={state.counter} up={up} />
<CorrectChild counter={state.counter} up={up} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
If you still experience that without DnD your code "works" then please provide a Minimal, Reproducible Example of it "not working".

Related

React - Carbon Design System - Transition in right sidebar not working

I'm trying to use Carbon Design System for a project, and I need some help with the UI Shell.
The transition of the right sidebar is not working properly.
Here is the working example, see how smooth it opens and closes (see live example):
Here is the example with issues. There's no transition (see live example):
I think it's related to how High Order Components work. Here's my theory:
HeaderPanel is being rendered inside the HeaderContainer, which is a High Order Component.
When the state variable switchCollapsed changes, the HeaderContainer is rendered entirely again.
So this would explain why there's no transition.
I'm new to React, and this theory is based on my React knowledge until now. Please correct me if I'm wrong
Here's a piece of the code of the second example (simplified for the brevity of this post). See the entire code here
class App extends React.Component {
constructor(props) {
super(props);
this.toggleSwitch = this.toggleSwitch.bind(this);
this.state = { switchCollapsed: true };
}
toggleSwitch() {
this.setState(state => ({
switchCollapsed: !state.switchCollapsed
}));
}
render() {
return (
<div className="container">
<HeaderContainer
render={({ isSideNavExpanded, onClickSideNavExpand }) => (
<>
<Header aria-label="IBM Platform Name">
<HeaderMenuButton
onClick={onClickSideNavExpand}
isActive={isSideNavExpanded}
/>
<HeaderGlobalBar>
<HeaderGlobalAction onClick={this.toggleSwitch}>
<UserAvatar20 />
</HeaderGlobalAction>
</HeaderGlobalBar>
<HeaderPanel
aria-label="Header Panel"
expanded={!this.state.switchCollapsed}
/>
<SideNav
aria-label="Side navigation"
expanded={isSideNavExpanded}
>
<SideNavItems>
</SideNavItems>
</SideNav>
</Header>
<StoryContent />
</>
)}
/>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Moreover, if I change the property on React Dev Tools, the transition work!

How to hide / show the content of a component that is reused by different views in ReactJS

I have two views that call a particular component and this makes it difficult for me to hide / show when the component is needed. In this way, I have the following:
I tried to make a display: none to overide the styles but it is a problem. This is because the views when using the same component, the classes of the CSS are called the same and when I make the display: none, I get it not to show in "News" but in "Home" it also affects and it is not shown to me.
I tried it:
Component Section:
<div className="section_news">
{orden.map(this.renderSection)}
<ViewAllNews mostrarCategorias={false} categoriaId={newsId} />
If I remove the title like this, I'll leave the view "Home" and "News", and that's not what I intend to do.
Next, I leave the original piece of code:
View named "Home":
<div className="contentbar">
<Section title="Ult. News" />
<Section
notices_id={19}
orden={[[0, 3], [3, 1], [4, 2]]}
/>
</div>
**View named "News": **
<Section
notices_id={data.post.category.id}
orden={[[0, 2], [2, 3]]}
/>
Component Section:
<div className="section_news">
<Titles title={title || (category && category.title)} />
{orden.map(this.renderSection)}
<ViewAllNews categoriaId={newsId} />
Component ViewAllNews:
return (
<Link to={`/news/c/${categoryId}/`}>
<div className="parentLineViewAll">
<div className="LineViewAll" />
<div className="line" />
<div className="Views">View All</div>
<div className="arrowMore">
<FontAwesomeIcon icon="chevron-circle-right" />
</div>
</div>
</Link>
);
};
As you can see, the view of "Home" and "Views" make use of the same component.
What I really need is to hide the component named for the "News" view and also hide the "ViewAllNews" component only for the "News" view.
Thanks for your help!
The easiest way to hide / show react components is to use conditional rendering
Here is a basic example:
class MyComponent Extends React.Component {
constructor(props){
super(props)
this.state = {
isVisible: false, // <-- add a value to state so we can track the components visibility
}
}
show = () => {
this.setState({ isVisible: false })
}
hide = () => {
this.setState({ isVisible: true })
}
render(){
if(this.state.isVisible){
return (
/* your component code */
)
}
}
}
If you want to toggle the component from inside a parent component then you can control this with a ref:
class Parent Extends React.Component {
constructor(props){
this.myRef = React.createRef()
}
render(){
return <MyComponent ref={this.myRef}>
}
}
Once the component mounts, you can check if the ref has been set and call the method anywhere in your code:
if(this.myRef.current){
this.myRef.current.show() // or .hide()
}
You can also control the components visibility via props:
function MyComponent({ isVisible }){
if(isVisible){
return (
/* your component code */
)
}
}
Hope that helps, let me know if you have any questions!
a way to implement this componentWillUnmount() and componentWillMount() ,
like to this example , hope help you this.

Props not updated

I'm a new user of React and I try to dispatch a modification from my redux store into my components through a container component and props.
My problem is at the end, the data isn't updated. I tested and I figured out that in a Board component, I got the correct edited state (I edit a module's name in this.state.mlodules[1].name) but this value isn't sent in the Bloc component. Here is the render function of my Board component:
render() {
const modules = this.state.modules.map((module) => (
<Draggable key={module._id} x={module.position.x} y={module.position.y} inside={this.state.inside}>
<Bloc module={module} editModule={this.props.onModuleEdited(module._id)}/>
</Draggable>
));
return (
<div className="board"
onMouseLeave={this.mouseLeave}
onMouseEnter={this.mouseEnter}>
{modules}
</div>
);
}
And here is the render function of my Bloc component (I'm using a BlueprintJS editable text):
render() {
return (
<div className="pt-card pt-elevation-3 bloc">
<span onMouseDown={this.preventDrag}>
<EditableText
className="name"
defaultValue={this.props.module.name}
onChange={this.nameChanged}
/>
</span>
</div>
);
}
Any ideas ?
Thanks !
as i mentioned in my comment, you are assigning a defaultValue and not assigning a value prop.
according to their source code on line #77 you can see that there's a value prop.
Edit: As you can see in the docs, defaultValue is uncontrolled input where's value is a controlled input
I think, issue is defaultText. defaultText will assign the default text only on initial rendering it will not update the value. So instead of that assign the props value to value property.
Like this:
value = {this.props.module.name}
Note: But it will make the field read-only, if you don't update the props value (state of parent component) in onChange method.
Check this example, when you click on text 'Click Me' it will update the state value but text of input field will not change:
class App extends React.Component{
constructor(){
super();
this.state = {a:'hello'}
}
click(){
this.setState({a: 'world'},() => {
console.log('updated value: ', this.state.a)
})
}
render(){
return(
<div>
<Child value={this.state.a}/>
<p onClick={() => this.click()}>Click Me</p>
</div>
)
}
}
class Child extends React.Component{
render(){
console.log('this.props.value', this.props.value)
return(
<input defaultValue={this.props.value}/>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>

Unnecessary DOM update for dynamic React elements

I have a React project that generates some DOM elements "dynamically" within JSX:
<div className='ui form'>
<h2 className="header">{subtype}</h2>
{
subtypes[subtype].fields.map((field) =>
<div className='field' key={field.name}>
<label>{field.label}</label>
<input name={field.name}
value={entity[field.name]}
onChange={onInputChange}/>
</div>
)
}
</div>
For a specific component, the generated input fields don't ever change during the life of the application (only their props change), so it is just a way to generate forms that are actually static.
So it is exactly equivalent to this "static" JSX:
<div className='ui form'>
<h2 className="header">{subtype}</h2>
<div className='field' key='field1'>
<label>Field 1</label>
<input name='field1'
value={entity['field1']}
onChange={onInputChange}/>
</div>
<div className='field' key='field2'>
<label>Field 2</label>
<input name='field2'
value={entity['field2']}
onChange={onInputChange}/>
</div>
</div>
If I used the first code snippet, then the HTML DOM elements get recreated on every change to state / props. If I use the second snippet, then the HTML appears to be unchanged and only the field values are updated (React can detect in the second instance that the virtual DOM elements are still the same, but not in the first instance)
Is there a way for me to create the "dynamic" virtual DOM in the first code example in a way that it can be cached and reused so that React sees it as being the same on each render?
Many thanks
Where is subtypes coming from? From what I understand you are receiving this in the component's props. If that is the case, you need to store this variable in this component's state. Then, you need to update it's state in it's componentWillReceiveProps lifecycle function.
The thing is, your component will only re-render when it's setState function is called. Hence, the components will not re-render when it's props change (after it has already been mounted).
class SimpleCom extends React.Component {
constructor(props) {
super(props);
this.state = {
subtypes: props.subtypes
}
}
componentWillReceiveProps(props) {
this.setState({
subtypes: props.subtypes
});
}
render() {
const subtypes = this.state.subtypes;
return (
<div className='ui form'>
<h2 className="header">{subtype}</h2>
{
subtypes[subtype].fields.map((field) =>
<div className='field' key={field.name}>
<label>{field.label}</label>
<input name={field.name}
value={entity[field.name]}
onChange={onInputChange}/>
</div>
)
}
</div>
);
}
}

React access Dom Nodes from this.props.children

Let's say I have a Card that contains a login Form
<Card>
<LoginForm/>
</Card>
How do I access the nodes from the Form within the Card render function?
<Form >
<input type="text" name="email"/>
<input type="password" name="password"/>
<input type="submit"/>
</Form>
Because what i´d like to do is to render the submitbutton not within the props.children context but render it wrapped outside of the given child!
render () {
return (
<div className="card">
<div className="inner">
{/* render Children */}
{this.props.children != undefined ?
<div className="childrenWrapper">
{this.props.children}
</div>
: ""
}
</div>
{/* render submit from login form here, not above */
</div>)
There are some components which actually do what I want. For example the Tabs component from react-toolbox. They somehow manage to render what's within the Tab (children) somewhere else
Just for instance
<Tabs index={this.state.inverseIndex} onChange={this.handleInverseTabChange} inverse>
<Tab label='First'><small>First Content</small></Tab>
<Tab label='Second'><small>Second Content</small></Tab>
<Tab label='Third'><small>Third Content</small></Tab>
<Tab label='Disabled' disabled><small>Disabled Content</small></Tab>
</Tabs>
Which will lead to the following html
As you can see the children from the tab where rendered within their own section
I do not want to change anything on the Form to solve this problem, I would like to pass the Form into the Card and let the Card decide how the Form will be rendered within the card render function.
Since I'm trying to implement the Google Material Design Card component and just use it as a template there are more elements coming which will need to be split up and placed at the positions I want them to be. The thing is I could actually place the relevant HTML around the Form to get it as the Card I want it to be, but then I wouldn't need the component at all.
There are some decent answers here, but none of them directly answer your question. Therefore, even though you should refactor your code (as elucidated below), I am going to provide you a working solution:
class Card extends React.Component {
constructor() {
super();
this.state = {};
}
render() {
console.log(typeof this.props.children)
return (
<div>
{typeof this.props.children === 'object'
? React.cloneElement(this.props.children, { ref: (n) => this.form = n })
: null}
<button onClick={(e) => console.log(this.form.data)}>submit</button>
</div>
);
}
}
class Form extends React.Component {
constructor() {
super();
this.onChange = this.onChange.bind(this);
this.state = {};
}
onChange(e) {
this.data = e.target.value;
}
render() {
return (
<form>
<input type="text" onChange={this.onChange} />
</form>
);
}
}
ReactDOM.render(
<Card><Form /></Card>,
document.getElementById('container')
);
https://jsbin.com/fohehogozo/edit?js,console,output
By setting a property on the instance, you can then access that property from children by using a ref. I checked for typeof === object here, because there was only one child.
WARNING: this code is NOT PRODUCTION READY. Do not ever run this in production. The code I have demonstrated is a terrible hack, and you should never try this at home.
If you are trying to submit a form, maybe look at passing down an onChange event and storing the value (based on the name of the field) in the state of the Card. Then attach the onChange event on the inputs so as soon as they're updated, the data will be passed back up to the container for you to submit.
If you would like to split up the childrens passed, you can simply filter the children array to split up the children, however your childrens seem to be nested.
Why dont you let the cards children handle the separation between your inner container and other content?
I think restructuring in this case is more suitable than modifying the passed children property.
Also, pulling the submit button out of the actual form tags, would break your form as it would no longer submit without some custom connection between the button and the actual form.
Don't try to manipulate the DOM; it's generally an anti-pattern in React (though there are a few valid use cases). In your case, rather than literally trying to move the elements, I'd simply hide the button in the form and add it to the parent.
Assuming you have access to the internals of <LoginForm>, you can add a prop to hide the button:
const button =
<div class="flatbuttonWrapper">
<input type="submit"/>
</div>;
<Form>
<input type="text" name="email"/>
<input type="password" name="password"/>
{!this.props.hideButton && button}
</Form>
Add the button to the Card component:
render() {
return (
<div className="card">
<div className="inner">
{this.props.children != undefined ?
<div className="childrenWrapper">
{this.props.children}
</div>
: ""
}
</div>
<div class="flatbuttonWrapper">
<input type="submit"/>
</div>
</div>
);
}
Finally, in your parent:
<Card>
<LoginForm hideButton />
</Card>
All that said, it really feels like you need to structure your code better and break some of these components up into smaller, more reusable pieces. For example, the Card component probably shouldn't be affecting the button's style or conditionally rendering children; it should just add a frame around any children. Then you can create a more complex component that composes these simpler sub-components to to whatever you need.

Resources