Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Patterns for maybe or union types in React#2259

Unanswered
davetapley asked this question inQ&A
Discussion options

When it comes to 'doing control flow' (i.e. something ismaybe or aunion) there are two possible approaches:

  1. Branch in a parent component and pass model instances of the model down to child components, or:
  2. Never pass model instances as props and use e.gmobx-store-provider to inspect the state of the tree in each component.

Here's the problem:

With 1. Things get weird because, as I understand it, passing model instances leads to the kind of errors I'm running in to.

With 2. I want to do this:

constappStore=useStore(AppStore);if(!appStore.whatever){return<></>}// more logic

But then later in the component:

constfoo=useHook(appStore.whatever)

And I can't do that because early return violates therules of hooks.


I feel like 1. is the 'more correct' approach, but I keep running in to these kinds of problems:

You must be logged in to vote

Replies: 2 comments 2 replies

Comment options

Don't you just love React? 😭

You could consider something like suspense, or depending on what kind of control you have overuseHook, you could make it be able to work withnull values, but the easiest approach is this:

constMyComponent=()=>{constappStore=useStore(AppStore);if(!appStore.whatever){return<></>}return<MyComponentInnerwhatever={appStore.whatever}/>}constMyComponentInner=(props)=>{constfoo=useHook(props.whatever)// more logic}

Breaking up your components in this way is my go-to when I need to get around the rules of hooks.

You must be logged in to vote
2 replies
@davetapley
Comment options

I'm confused r.e. the that inner component pattern:
You are still passing an MST model into a component, which is what is to be avoided, no?

@thegedge
Comment options

thegedgeApr 3, 2025
Collaborator

You are still passing an MST model into a component, which is what is to be avoided, no?

Should be no reason to not do that. We do it all the time at my company 🙂

But if I'm reading things correctly, I think I missed the specifics of what you're working through. My bad! Sounds like you have a model instance that is alive when you render the component, but not alive sometime later. We definitely have had to deal with that issue too, but overall I don't think it's been a major source of pain for us. We have a fewisAlive checks, but not many (and we have a pretty large codebase).

Maybe instead of talking in the abstract, you could provide a minimal example that shows the issue you're running into and we could work through that together?

The first thing that's popping into my head would be some kind of wrapper (like how there'sobserver formobx-react) that checks all of the props for state tree nodes (isStateTreeNode) and then makes sure they're all alive. If not, the wrapping component returns a given fallback.

Comment options

Sorry if I answer out of place (maybe I didn't grab the core problem here).

Here's how we do it all the time with Mobx.

First, a couple of utility functions:

// A function to get rid of null contextexportfunctionassertContext<Textendsobject|null>(name:string,ctx:T){if(!ctx)thrownewError(`Need${name}Context.Provider`)returnctxasNonNullable<T>}// An optional utility to create store only one time/** * Creates a class by its name and constructor parameters. * Useful for replacing lines like: `const store = useRef(new Cls(arg1, arg2...)).current`, * as it is not creating classes every render. *@param Cls classname *@param args constructor arguments */exportfunctionuseCreateStore<T,Argsextendsunknown[]>(Cls:Constructor<T,Args>,  ...args:Args){constref=useRef<T>()if(ref.current==null){ref.current=newCls(...args)}returnref.current}

Store definition:

exportclassFooStore{...}exportconstFooStoreContext=createContext<null|FooStore>(null)exportconstuseFooStore=()=>assertContext('FooStore',useContext(FooStoreContext))

Store creation:

functionApp(){constfooStore=useCreateStore(FooStore, ...)return<FooStoreContext.Providervalue={fooStore}>...</FooStoreContext.Provider>}

Store usage:

exportconstFooComp=observer(functionFooComp(){constfooStore=useFooStore();// No need for null check, because context is guaranteed to be not-nullconsole.log(fooStore.whatever)return(<div>     ...{/* If you don't want to redraw the whole component */}<Observer>{()=><Fieldvalue={fooStore.whatever}/>}</Observer>      ...</div>)})

As a general rule for nulls: we avoid using them.

  • If a store seems to be needing a value, we can change its definition so that it could live without one.
  • If an object type needs some required values, we apply Partial/PartialDeep so that we can assign{} to it.

Regarding theuseHook()

Aswhatever is what we have control over, we could do this:

exportconstsomeModel=types.model('Recipe',{whatever:types.maybe(types.string)}).views((self)=>({getwhateverValue(){returnself.whatever??''}})...functionFoo(){constres=useHook(someValue.whateverValue)}

What about stores passing via props?

We also do it all the time, they're just values.

Render or not to render?

It's sometimes difficult to think of a particular render if there is no value to display. Our approach is to still render something, then nothing. Because this way our UI will flicker less.

You must be logged in to vote
0 replies
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Category
Q&A
Labels
None yet
3 participants
@davetapley@OnkelTem@thegedge

[8]ページ先頭

©2009-2025 Movatter.jp