reactjsreact-hookseslintmobxmobx-react

Using a method from the mobx store in React useMemo


I have a MobX Store that contains user role information and a method that validates the role:

class Store {
    constructor() {
        makeAutoObservable(this)
    }

    role: string = ""

    setRole(name: string) {
        this.role = name
    }

    checkRole(name: string) {
        return this.role === name
    }
}

I want to use the checkRole method inside useMemo in my component, in order not to recalculate the value if the role has not changed in any way. (I realize that this example is a simple calculation and useMemo is not needed, but it's just an example)

My component looks like this:

const App = observer(() => {
    const [, forceRender] = useReducer((prev) => prev + 1, 0)
    const store = useContext(StoreContext)

    const isHaveAccess = useMemo(() => {
        return store.checkRole('ADMIN')
    }, [store, store.role])

    return (
        <div>
            {isHaveAccess ? 'Access allowed' : 'Access denied'}
            <button
                onClick={() => store.setRole('ADMIN')}
            >Set Role</button>
            <button onClick={forceRender}>Force Render</button>
        </div>
    );
})

It works, but I get a warning from ESLint: React Hook useMemo has an unnecessary dependency: 'store.role', which says that store.role is not used inside useMemo.

Also, if I move the logic from the checkRole method to useMemo, the warning disappears.

Is this a normal practice or is there a better way to do it?

I tried using computed with arguments, but that didn't solve the problem and the checkRole method was called on every render.


Solution

  • You can just suppress this eslint warning in that case and keep the code as is. The downside is that your code is not very transparent that way, basically useMemo somehow knows the internal implementation of checkRole, that it needs store.role to recalculate. Imagine that you change implementation in the future and it will be easy to forget to add new deps to the memo call, or remove extra. It's a bit dirty, but it's more or less normal practice if you are fine with it being dirty.

    I would suggest you to try computedFn from mobx-utils, it's basically exactly what you need. Be careful though, it needs to be almost pure and only rely on observables and function arguments.

    //...
    
    checkRole = computedFn((name) => this.role === name)
    
    // ...
    

    Another option is to make checkRole pure and just pass both current and target role all the time, but it's a bit more verbose. eslint will be happy though.