Next.js: MobX Stores are not Reactive in Next.js v9 when using Hooks

Created on 13 Jul 2019  Â·  21Comments  Â·  Source: vercel/next.js

next.js / mobx bug report

Example name

with-mobx

Describe the bug

MobX stores are not reactive in Next.js v9 when using hooks

"mobx": "^5.11.0",
"mobx-react": "^6.1.1",
"next": "^9.0.1",
"react": "^16.8.6",

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Create a new next.js app - yarn create next-app
  2. Add mobx and mobx-react
  3. Create a simple store file and export default createContext(new Store())
  4. In a function component const store = useContext(ImportedStore)
  5. Display some items in a list from the store
  6. Add a button that attempt to set the items to an empty array
  7. No re-render is triggered

Sample Project

Download next-mobx.zip

Expected behavior

A re-render should have been triggered by MobX

Additional context

This bare bones example works fine when using create-react-app instead of create-next-app

PostsStore.js

import { createContext } from 'react'
import { action, decorate, observable } from 'mobx'

export class PostsStore {
  items = [
    'Foo',
    'Bar'
  ]

  reset() {
    this.items = ['Reset']
  }
}

decorate(PostsStore, {
  items: observable,
  reset: action
})

export default createContext(new PostsStore())

pages/index.js

import React, { useContext } from 'react'
import { observer } from 'mobx-react'
import PostsStore from '../stores/posts'

const Home = () => {
  const store = useContext(PostsStore)

  return (
    <div>
      {store.items.map(item => (<p key={item}>{item}</p>))}
      <p>
        {/* Clicking reset() DOES NOT trigger a re-render */}
        <button onClick={() => store.reset()}>Reset</button>
      </p>
    </div>
  )
}

export default observer(Home)
good first issue story

Most helpful comment

I don't use classes at all, they are so messy 🙊 Decorators are just too weird or maybe I am weird, who knows :) Hooks forever! ✨

All 21 comments

Also tagging @FredyC to help with this, I am completely stumped

Sorry, I have no experience with NextJS. I just recall a similar issue but doesn't seem there was any conclusion. Perhaps @programbo has some insights?

From my vague knowledge I would perhaps try to utilize Provider instead of setting a store as default value in context. Next might have some issue with globally scoped variables.

fixed

import { createContext } from 'react'
import { action, decorate, observable, computed } from 'mobx'

export class PostsStore {

  _items = observable.object({
    values: ['Foo','Bar']
  })

  // this is computed
  get items(){
    return this._items.values
  }

  set items(v){
     this._items.values = v
  }

  reset = action(() => {
    console.log('resetting items')
    // @ts-ignore
    this.items = ['Reset']
    console.log(this.items)
  })
}

export default createContext(new PostsStore())

@jeremy-coleman that’s really help, but maybe not fixed :)

@FredyC any idea why the decorate method wouldn’t work correctly here?

@AndrewBarba Sorry, no idea, I don't use that and never was. Too verbose to my taste.

You’re saying you don’t use decorate? I’m guessing you use the decorator syntax instead? If so I’ll give that a shot. I just want the least, most clear code possible. The example Jeremy posted is way too verbose for my liking

I don't use classes at all, they are so messy 🙊 Decorators are just too weird or maybe I am weird, who knows :) Hooks forever! ✨

Ah okay. So I would really like to use hooks entirely but I struggled with examples that didn’t all use the local store variety. Do you have any suggestions for using hooks that still let us share global stores the way we would with classes?

https://mobx-react.js.org/recipes-context

There a whole lot of information mainly about Hooks.

I’ve read those docs a bunch but I think it finally all makes sense now. I may work on a new simplified example for next that uses the hooks/context/provider approach. Thanks for the help

My example was verbose to show you how mobx works. It proxies objects, which means the ref to the object does not change, which also means you cant make primitives observable. The above code basically just does what observable.box() does. However, The example shows idiomatic use of mobx when you need to intercept the get with a computed. The simples thing is to just make an observable object like let x = observable({}) or observable.map If you want to hand optimize stuff. Also for what youre describing, have a look at undux, its pretty nice

this is all you need to get it to work though

const posts = observable({
  items:['Foo','Bar'],
  reset:()=>{
    posts.items = ['Reset']
  }
})
export default createContext(posts)

Thanks thats helpful

I don't really see any benefit of your package against regular React Context. It's kinda funny you actually want a donation for that :D Hard pass.

Besides, how is it related to this issue?

@FredyC it's just for fun 😄 not really want a donation. I don't know maybe someone interested to it, because everyone talking about mobx context, saving their time 😸 . It have hooks, btw.

useContext is not working in functional components when we use nextjs

Hi there! @medmin I was able to get useContext to work with the help of @jeremy-coleman's answer
I was struggling with this too but wanted to keep the class syntax for certain reasons.
I was able to get it working so that you could just do:

import { createContext } from 'react'
import { action, decorate, observable, computed } from 'mobx'

export class PostsStore {
....
}

export default createContext(observable(new PostsStore()))

And then useContext seemed to work. I don't love it, but I hope it helps.

I'm on next 9.4.2 and I can't even get the store initial data even after trying multiples approaches.
Is this a bug with next or the mobx bindings?

We've recently updated our Mobx examples which should address this issue. Thanks!

@Timer Do you have a link to the examples? 🙇

You can find the examples here: https://github.com/vercel/next.js/tree/canary/examples/with-mobx-react-lite / https://github.com/vercel/next.js/tree/canary/examples/
The problem however with this example is the use of useObservable which has been deprecated for some time now.

Was this page helpful?
0 / 5 - 0 ratings