atlassian / react-beautiful-dnd

Beautiful and accessible drag and drop for lists with React
https://react-beautiful-dnd.netlify.app
Other
33.19k stars 2.53k forks source link

draggable component does not get rerendered until clicked when not using inline function #854

Closed alukach closed 4 years ago

alukach commented 5 years ago

Bug or feature request?

Bug

Similar to #156, however I'm not using MobX.

My state object is updated after move but the change is not represented in the view until I click on an element (i.e. after dropping the moved element, it appears that it has returned to its source location despite the state showing otherwise).

This only occurs when I'm not using an inline function.

Bad (Same result when using a non-arrow function):

export default class TemplateEditor extends React.Component<Props, State> {

  public renderTemplateElements = (provided: DroppableProvided) => (
    <TemplateElementList
      innerRef={provided.innerRef}
      {...provided.droppableProps}
    >
      {this.props.template.layout.map((element, i) => (
        <TemplateElementComponent
          key={i}
          index={i}
          id={i.toString()}
          element={element}
        />
      ))}
      {provided.placeholder}
    </TemplateElementList>
  );

  public render() {
    return (
      <DragDropContext onDragEnd={this.onDragEnd}>
        <Container>
          <div className="m-2">Visual Editor</div>
          <Droppable droppableId="someUniqueId">
            {this.renderTemplateElements}
          </Droppable>
        </Container>
      </DragDropContext>
    );
  }
}

Good:

  public render() {
    return (
      <DragDropContext onDragEnd={this.onDragEnd}>
        <Container>
          <div className="m-2">Visual Editor</div>
          <Droppable droppableId="someUniqueId">
            {(provided: DroppableProvided) => (
              <TemplateElementList
                innerRef={provided.innerRef}
                {...provided.droppableProps}
              >
                {this.props.template.layout.map((element, i) => (
                  <TemplateElementComponent
                    key={i}
                    index={i}
                    id={i.toString()}
                    element={element}
                  />
                ))}
                {provided.placeholder}
              </TemplateElementList>
            )}
          </Droppable>
        </Container>
      </DragDropContext>
    );
  }

What version of React are you using?

16.4.7

What version of react-beautiful-dnd are you running?

7.1.2

garkin commented 5 years ago

I got this issue too. Sad reason is Redux {pure: true} HOC being used: https://github.com/atlassian/react-beautiful-dnd/blob/6f71b45c13aab36807f9da952437a5d63d52d696/src/view/droppable/connected-droppable.js#L140

garkin commented 5 years ago

I think this is a bug. There is no need to check mutations in case we always send mutated children={new lambda()} prop. It's a CPU waste. You should not assume function returns same result if function is the same. Dynamic composition is a whole reason to have a function in the first place. Not to mention about nesting hell and ill patterns with lambdas in render.

Related caution from the official docs: https://reactjs.org/docs/render-props.html#be-careful-when-using-render-props-with-reactpurecomponent

alexreardon commented 5 years ago

Fasinating! We have a test to ensure that parent renders are passed through:

https://github.com/atlassian/react-beautiful-dnd/blob/master/test/unit/view/connected-droppable/child-render-behaviour.spec.js

But the test is not exercising the case where a Droppable function is an instance property and not an inline function. This is worth investigating.

We should be allowing all parent renders through

alexreardon commented 5 years ago

Also, please confirm the issue is still occurring on the latest version. 7.x is two majors behind the current supported version

alexreardon commented 5 years ago

Thanks!

garkin commented 5 years ago

I'm getting it in "react-beautiful-dnd": "^9.0.2"

alexreardon commented 5 years ago

Any thoughts on this @markerikson?

markerikson commented 5 years ago

Mmm... not specifically, other than connect() does default to ensuring that your wrapped component only re-renders when either its data from the Redux store or the props passed to the wrapper component have actually changed.

alexreardon commented 5 years ago

@markerikson It looks like using an arrow function changes the children props (a new reference every time)

At least, that is what I thought until I set areOwnPropsEqual to () => false and every component re-rendered every time...

markerikson commented 5 years ago

So just to check: you're rendering <SomeComponent constantProp={123}>{constantChild}</SomeComponent> ? In that case, yeah, if that component isn't extracting new values in its mapState, then connect will skip re-rendering it. Passing in a new arrow function as that child would be a new prop, so it would re-render.

alukach commented 5 years ago

Btw, I mispoke, I am using v9.0.2

alexreardon commented 5 years ago

@markerikson here is what I am seeing:

this is the behaviour I want, but it is a little strange as I would think that each component's areOwnPropsEqual check would fail. If i set areOwnPropsEqual to () => false then every component re-renders on every update

I am confused by this behaviour

markerikson commented 5 years ago

Hmm. That does sound odd. Do you have a CodeSandbox or other project that repros this issue?

alexreardon commented 5 years ago

I can make one tomorrow On Sun, 28 Oct 2018 at 5:48 am, Mark Erikson notifications@github.com wrote:

Hmm. That does sound odd. Do you have a CodeSandbox or other project that repros this issue?

— You are receiving this because you commented.

Reply to this email directly, view it on GitHub https://github.com/atlassian/react-beautiful-dnd/issues/854#issuecomment-433645668, or mute the thread https://github.com/notifications/unsubscribe-auth/ACFN7eun8bNBac3_MOLyUbsRnoeGDySVks5upKppgaJpZM4XYdUf .

markerikson commented 5 years ago

@alexreardon : Did you ever dig into this issue further?

dandelany commented 5 years ago

Just wanted to ping this issue again, as I ran into it today ("react-beautiful-dnd": "10.0.3") and spent some time confusedly debugging before finding this ticket. Relatively easy to work around it using an arrow function, but it's hard to track down & def could cause confusion for users.

alexreardon commented 5 years ago

For sure, this looks like a deep react-redux issue. I am hoping it will just go away when we move to hooks #871

alexreardon commented 5 years ago

It could be worth calling out in the docs

alexreardon commented 5 years ago

PR's welcome!

markerikson commented 5 years ago

FWIW, I don't think I ever did see any kind of a repro project or test that I could look at with this.

alexreardon commented 5 years ago

I stopped digging after a while. I think I got distracted.

My understanding: if you use a render props function then your children prop is always different, which has some flow-on effects.

alexreardon commented 5 years ago

Can somebody check to see if this issue still exists on 12.0.0-alpha.7?

alexreardon commented 4 years ago

I'll close this for now as I have not heard any updates on it. Please feel free to comment on this and we can reopen it if it is still an issue for people

robbertbrak commented 3 years ago

Still an issue. I ran into this today and had to waste some time debugging this before I found this ticket. How about mentioning this in the "Common setup issues"?

SpringsTea commented 3 years ago

Still an issue on 13...