Bug
When a MobX React observer decorated draggable component has a state change (either through an interface interaction or directly changing the values in the observable store, and MobX issues a rerender of all observer components that care about the observable data, the elements that have changed should rerender.
The element does not rerender until it is clicked (as if to start a drag)
Create an observer decorated component that accepts an observable object as a prop. Place the component in a draggable area. Change an attribute of the object (this should cause MobX to rerender the component) and verify that autorun runs.
Note that the component was not rerendered.
Click in an area of the draggable element that would cause Draggable to capture the event. Note that the element has rerendered.
https://www.webpackbin.com/bins/-KxiZyKnJzY-13XSEhqT
You can drag and drop the elements, and click on the checkbox. However, the value does not change until you click into an area outside of the checkbox.
I assisted @hoenth in creating the Demo and can confirm this is an issue.
It looks like something is blocking the render to children. The only way this can happen if a parent renders is if shouldComponentUpdate returns false. We have tests to ensure that the components render if their parents do. My suspicion is that the parent itself is not rendering. I can take a look in a few days but that information might be helpful
When I click the input the parent element is not rendered
https://www.webpackbin.com/bins/-KxlrpZw0lIcpkqKc4m6
I suspect your problem is with this line:
this.props.item.content = !this.props.item.content;
Changing props like this will not trigger a re-render in react. You cannot update your own props directly. I cannot see anything in the mobx-react docs that would allow this syntax to work
More info: https://stackoverflow.com/a/26089687/1374236
Please reopen this if needed
@alexreardon Thanks for taking a look at this. I have updated the webpackbin to demonstrate that the list is being rerendered. I am rendering two lists. In the first, each element is wrapped in draggable. In the second, the element is not wrapped in draggable.
When you click the checkbox in an element in the first list, you will notice that the content gets updated (rerendered) in the second list, but not in the first.
https://www.webpackbin.com/bins/-KxmVueDfBxRAnRjUX9K
It doesn't appear that I am able to re-open this issue.
I think the problem here is that the observable prop isn't inside an observer. I.e. what you render here:
{(provided, snapshot) => (
<div>
<div
ref={provided.innerRef}
style={getItemStyle(
provided.draggableStyle,
snapshot.isDragging
)}
{...provided.dragHandleProps}
>
{this.props.item.content.toString()}
<input
type="checkbox"
onClick={e => {
e.stopPropagation();
this.props.item.content = !this.props.item.content;
}}/>
</div>
{provided.placeholder}
</div>
)}
must also be wrapped in an observer.
@tobiasandersen Thanks for your thoughts. Do you mean:
observer({(provided, snapshot) => (
<div>
<div
ref={provided.innerRef}
style={getItemStyle(
provided.draggableStyle,
snapshot.isDragging
)}
{...provided.dragHandleProps}
>
{this.props.item.content.toString()}
<input
type="checkbox"
onClick={e => {
e.stopPropagation();
this.props.item.content = !this.props.item.content;
}}/>
</div>
{provided.placeholder}
</div>
)}
)
No:({(provided, snapshot) => /* This must be inside an observer */ }
@tobiasandersen That worked. I pulled the internals into a new component, wrapped that component in an observer and passed provided and snapshot as props.
Thanks for your help!
Glad we got to the bottom of this! Thanks everyone
@tobiasandersen That worked. I pulled the internals into a new component, wrapped that component in an observer and passed provided and snapshot as props.
Thanks for your help!
Hey do you have an example of this code? Facing the same quirk...
I ran into the same issue.
@alexreardon MobX is getting more and more popular. Maybe there should be a more obvious / elegant solution to this?
@84pennies this is what worked for me:
export const ItemsList = observer(({provided, snapshot}) => {
return store.itemsStore.items.map((item, index) => (
<Draggable key={'' + item.id} draggableId={'' + item.id} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)}
>
<Item key={item.id} item={item} />
</div>
)}
</Draggable>
))
})
// Normally you would want to split things out into separate components.
// But in this example everything is just done in one place for simplicity
export const ItemsWrapper = (() =>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={getListStyle(snapshot.isDraggingOver)}
>
<ItemsList provided={provided} snapshot={snapshot} />
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
)
(The starting point for my code is the simple vertical list example at https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/about/examples.md#basic-samples)
I guess the need to do it this way is explained by:
https://github.com/mobxjs/mobx/blob/gh-pages/docs/best/react.md#mobx-only-tracks-data-accessed-for-observer-components-if-they-are-directly-accessed-by-render
tobiasandersen's solution works in 2020 when it comes to functional components as well.
tobiasandersen's solution works in 2020 when it comes to functional components as well.
Could you provide example for functional component please
well, according to tobiasandersen's solution 锛孖 modify the code and it work in my project, (this is a simple example code and i hide some business code :))
```
{(provided: DroppableProvided) => (
// next
export const PanelWrap = observer(() => {
const { componentQueue } = useStore() // data from mobx store
return (
<div>
{componentQueue.map((Comp: IComp, index: number) => {
const Component = Comp.component;
return (
<Draggable draggableId={`draggable-${index}`} key={index} index={index}>
{(provided) => (
<PanelItem
Comp={Comp}
provided = {provided}
Component={Component}
index={index}
/>
)}
</Draggable>
)
})}
</div>
)
})
export const PanelItem = observer(() => {
return (
Most helpful comment
No:
({(provided, snapshot) => /* This must be inside an observer */ }