Socket
Book a DemoInstallSign in
Socket

mobx-toolbox

Package Overview
Dependencies
Maintainers
1
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mobx-toolbox

`mobxSaiHandler` is a super simple way to handle `data`/`error` reactions from any MobX-powered API state (like MobxSai) using just 1 line of code πŸ™Œ

1.5.1
latest
Source
npmnpm
Version published
Weekly downloads
12
-80.33%
Maintainers
1
Weekly downloads
Β 
Created
Source

Mobx-toolkit, mini-lib for easy in MobX using

mobxSaiHandler

mobxSaiHandler is a super simple way to handle data/error reactions from any MobX-powered API state (like MobxSai) using just 1 line of code πŸ™Œ

Usage

// some-store.ts
class FeedStore {
	constructor() {makeAutoObservable(this)}

	postsFeed: MobxSaiInstance<VirtualList<GetPostFeedResponse[]>> = {}

	getPostsFeedAction = () => {
		this.postsFeed = mobxSaiFetch(getPostsFeed())

		mobxSaiHandler(
			this.postsFeed,
			(data) => {
				console.log('Loaded posts:', data)
			},
			(error) => {
				console.log('Something went wrong:', error)
			},
		)
	}
}

Now we just call mobxSaiHandler, pass in our sai instance, and boom β€” we get clean success and error handling in one place, no reaction boilerplate, no disposers, nothing else needed.

What's it for?

Sometimes you get tired of writing reaction(() => [data, error], ...) every time you want to check when your MobxSaiInstance updates.
This thing solves that. You just pass it in, give it what to do on success and on error, and it does the rest β€” including auto disposing itself after first fire.

It works in stores, in components, in hooks, anywhere you want to react to sai updates ✨

mobxSaiHandler also has support for type guard!

If you want to make sure your data is exactly what you expect β€” just pass a guard as the 4th param.
Super handy when data could be null, Error, or anything else.

Options

mobxSaiHandler

Takes 4 simple params:

ParamTypeDescriptionInitialRequired
saiMobxSaiInstance<T>Your sai instanceβœ…
onSuccess(data: T) => voidCallback that fires when data is validβœ…
onError(error: any) => voidCallback that fires if there's an error❌
guard(data: unknown) => data is TOptional type guard to verify data before calling onSuccess❌

Why it’s useful?

  • βœ… Replaces all that reaction boilerplate
  • βœ… Auto-disposes after 1 use
  • βœ… Works with or without a type guard
  • βœ… Helps reduce code when working with async data in MobX
  • βœ… You can use it in literally 1 line, anywhere

mobxDebouncer

mobxDebouncer can help with any difficulty of debounce system by using 1 line of code :)

Usage

// some-store.ts
class TestStore {
	constructor() {
		makeAutoObservable(this)
	}

	postUpdater: null | MobxUpdateInstance<Post> = null
	setPostUpdater = (updater: MobxUpdateInstance<Post>) =>
		(this.postUpdater = updater)

	toggleLikePost = (postId: number, post: GetPostFeedResponse) => {
		if (!this.postUpdater) return

		runInAction(() => {
			this.postUpdater(
				postId,
				'likesCount',
				(prev: number) => prev + (post?.isLiked ? -1 : 1)
			)
			this.postUpdater(postId, 'isLiked', (prev: boolean) => !prev)
		})

		mobxDebouncer.debouncedAction(
			postId, // id of debounced action
			() => console.log('CALLBACK'), // callback
			1000, // delay
			'like-fav' // group key
		)
	}

	toggleFavPost = (postId: number, post: GetPostFeedResponse) => {
		if (!this.postUpdater) return

		runInAction(() => {
			this.postUpdater(
				postId,
				'favoritesCount',
				(prev: number) => prev + (post?.isFavorited ? -1 : 1)
			)
			this.postUpdater(postId, 'isFavorited', (prev: boolean) => !prev)
		})

		mobxDebouncer.debouncedAction(
			postId,
			() => console.log('CALLBACK'),
			1000,
			'like-fav'
		)
	}
}

// then you can use it in your component like this:
// some-component.tsx
const { currentTheme } = themeStore
const { toggleLikePost, toggleFavPost } = postInteractionsStore

const toggleLikeHandler = () => toggleLikePost(post?.id, post)
const toggleFavHandler = () => toggleFavPost(post?.id, post)

return (
	<div>
		<button onClick={toggleLikeHandler}>
			{post?.isLiked ? 'Liked' : 'Like'}
			{post?.likesCount}
		</button>
		<button onClick={toggleFavHandler}>
			{post?.isFavorited ? 'Favorited' : 'Fav'}
			{post?.favoritesCount}
		</button>
	</div>
)

Now we have 2 debounced actions, and they will be called only after 1 second of last call, and if we call them in 1 second, they will be called only once

Cool right? Just 1 line of code and you have debounced action with group key, and you can use it in any component, in any place, with any action in group.

This is very useful for some cases, like when you need to debounce some actions, but you need to call them in one place, and you need to call them in any component, in any place, with any action in group.

mobxDebouncer can reduce your code by 2/3+ times, depending on the volume of your functionality

mobxDebouncer also have cancelAllDebouncedActions cancelDebouncedActionsByGroup cancelDebouncedActions and flushDebouncedActions functions.

cancelAllDebouncedActions - Cancel all debounced actions

cancelDebouncedActionsByGroup - Cancel all debounced actions by group key

cancelDebouncedActions - Cancel debounced action by id

flushDebouncedActions - Flush all debounced actions

But the main and most useful/important function is debouncedAction so i will explain options only for it

Options

mobxDebouncer.debouncedAction

Function mobxDebouncer.debouncedAction 4 params, needs do your life with debounce much easier

Params

ParamTypeDescriptionInitialRequired
idstringId of debounced actiontrue
callback() => voidCallback to calltrue
delaynumberDelay of debounce1000false
groupKeystringGroup key of debounced actionnullfalse

mobxSaiFetch

mobxSaiFetch can reduce your actions in mobx, and make life easier :)

Usage

// some-store.ts
class TestStore {
	constructor() {
		makeAutoObservable(this)
	}

	saiData: MobxSaiInstance<TestFetchData> = {}
	saiDataPage = mobxState(1)('saiDataPage')
	isFetchUp = mobxState(false)('isFetchUp')

	getSaiMessageAction = async () => {
		const { messagePage, messageLimit } = messageApiStore
		const { selectedChat } = chatStore

		try {
			this.saiData = mobxSaiFetch(
				getMessage({ page: messagePage, limit: messageLimit })
			)
		} catch (err) {
			console.log(err)
		}
	}
}

export const testStore = new TestStore()

// SomeComponent.tsx
const {
	saiData: {
		data,
		status, // or isPending
	},
} = testStore

return (
	<div>
		{status == 'pending' ? (
			<Loading />
		) : (
			data?.message?.map(msg => <Messages msg={msg} />)
		)}
	</div>
)

That was interesting right? Now i con show you more interesting things in mobxSaiFetch:

Usage #2 [Pro]

// some-store.ts
class TestStore {
	constructor() {
		makeAutoObservable(this)
	}

	saiData: MobxSaiInstance<TestFetchData> = {}
	saiDataPage = mobxState(1)('saiDataPage')
	isFetchUp = mobxState(false)('isFetchUp')

	getSaiMessageAction = async () => {
		const { messagePage, messageLimit } = messageApiStore
		const { selectedChat } = chatStore

		try {
			this.saiData = mobxSaiFetch(
				getMessage.bind(null, { page: messagePage, limit: messageLimit }), // you need to write bind or () => getMessage() if you want to provide settings
				{
					id: selectedChatId, // u need to provide special id, not index from arr method or smthng
					page: this.saiData, // for pagination
					pageSetterName: 'saiDataPage', // also for pagination
					isFetchUp: this.isFetchUp.isFetchUp, // and also for pagination (fetch up or down), if down then +1 from page, otherwise -1
					fetchType: 'pagination', // without "pagination", your page, setterName and isFetchUp are useless | Initial: "default"
					fetchIfPending: false, // If true, it will be fetch without any coldowns | Initial: false
					fetchIfHaveData: true, // If false, it wont do fetch if you have a response from last request | Initial: true
				}
			)
		} catch (err) {
			console.log(err)
		}
	}
}

export const testStore = new TestStore()

// SomeComponent.tsx
const {
	saiData: {
		data,
		status, // or isPending
	},
} = testStore

return (
	<div>
		{status == 'pending' ? (
			<Loading />
		) : (
			data?.message?.map(msg => <Messages msg={msg} />)
		)}
	</div>
)

Usage #3 [Pro] Data scope in scroll

// some-store.ts
class TestStore {
	constructor() {
		makeAutoObservable(this)
	}

	selectedMessage = {}

	saiData: MobxSaiInstance<TestFetchData> = {}
	saiDataPage = mobxState(1)('saiDataPage')
	isFetchUp = mobxState(false)('isFetchUp')
	messagesCache = mobxState([])('messagesCache')
	saiDataLimit = 40

	getSaiMessageAction = async () => {
		try {
			const params = mobxState<GetChatProfileMediaParams>({
				limit: this.saiDataLimit,
				up: this.isFetchUp,
			})('params')

			this.chatMediaProfile = mobxSaiFetch(
				() => getChatProfileMedia(selectedMessage.id, params.params),
				{
					id: selectedMessage.id,
					fetchIfHaveData: false,
					cacheSystem: {
						limit: this.saiDataLimit,
						setCache: this.setMessagesCache,
					},
					dataScope: {
						class: 'our-scroller',
						startFrom: 'top',
						topPercentage: 20,
						botPercentage: 80,
						relativeParamsKey: 'relativeMessageId',
						upOrDownParamsKey: 'up',
						isHaveMoreResKey: 'isHaveMoreBotOrTop',
						setParams: params.setParams,
					},
					fetchAddTo: {
						path: 'data',
						addTo: 'start',
					},
				}
			)
		} catch (err) {
			console.log(err)
		}
	}
}

// SomeComponent.tsx
const {
	saiData: {
		data,
		status, // or isPending
	},
} = testStore

return (
	<div className='our-scroller'>
		{status == 'pending' ? (
			<Loading />
		) : (
			data?.message?.map(msg => <Messages msg={msg} />)
		)}
	</div>
)

Its very hard to use because, but this code can do logic with data scope. Like messages in telegram or media files in scroll scope. This options can reduce 200 strokes of code

Options

mobxSaiFetch

Function mobxSaiFetch 2 params, needs do your life with requests much easier

Params

ParamTypeDescriptionInitialRequired
function() => Promise<ay>Function to requesttrue
optionsMobxSaiFetchOptionsOptions to your mobxSaiFetch{}false

Returns

ParamTypeDescription
dataunknownyour data from fetch
status"pending" / "fulfillef" / "rejected"Your fetch status
errorstringJust error message
isFetchedbooleanCan give you information about if mobxSaiFetch is already fetched
isPengingboolean
isFulfulledboolean
isRejectedboolean
addedToEndCountnumberHow much times we added new data to the end of our data array
addedToStartCountnumberHow much times we added new data to the start of our data array
fetchedCountnumberHow much times we fetched with mobxSaiFetch
scrollProgressnumberOur scroll progress (if we passed class in dataScope option)
gettedToTopMobxStateHow much we getted to top
botStatus"pending" / "fulfillef" / "rejected"Fetch to the bottom status
topState"pending" / "fulfillef" / "rejected"Fetch to the top status
scrollCachedDataMobxStateOur cached data if we passed cacheSystem and dataScope options
isBotPendingboolean
isBotRejectedboolean
isBotFulfilledboolean
isTopPendingboolean
isTopRejectedboolean
isTopFulfilledboolean
topErrorstringFetch to top error
botErrorstringFetch to bot error
isHaveMoreBotMobxStateHave we more data to the bottom?
isHaveMoreTotMobxStateHave we more data to the top?

-----------------------------

useMobxUpdate

useMobxUpdate can reduce your code by 2/3+ times, depending on the volume of your functionality

Usage

// comment-interactions.ts
commentUpdater: MobxUpdateInstance<GetCommentsResponse> | null = null
setCommentUpdater = (updater: MobxUpdateInstance<GetCommentsResponse>) => this.commentUpdater = updater

// comment-actions.ts
getCommentsAction = async () => {
	const { selectedPost } = postInteractionsStore
	const {
		setCommentUpdater
	} = commentInteractionsStore

	if (!selectedPost?.id) return
	const postId = selectedPost.id

	try {
		const params = mobxState<GetCommentsParams>({
			relativeId: null,
			limit: 20,
			up: false
		})("params")

		this.comments = mobxSaiFetch(() => getComments(postId, params.params))

		const disposer = reaction(
			() => this.comments?.data,
			(data) => {
				if (!data) return

				setCommentUpdater(useMobxUpdate(() => data?.items)) // initialize updater with data list

				disposer()
			}
		)
	} catch (error) {
		console.log("Error getting comments", error)
	}
}

// SomeComponent.tsx
const { comments: { data } } = commentsActionsStore
const { commentUpdater } = commentInteractionsStore

return (
	<div>
		{data?.items?.map(comment => (
			<button
				key={comment.id}
				onClick={() => {
					commentUpdater!(
						comment.id, // id
						'likes', // path to update
						prev => prev + 1 // update callback [! DONT USER prev++ !]
					)
				}}
			>
				{comment.likes} // will update
			</button>
		))}
	</div>
)

Usage #2

You can export your updater and use it EVERYWHERE!

export const updateComment = useMobxUpdate(commentsStore.commentsList)

Code with useMobxUpdate vs Code without useMobxUpdate

Code without useMobxUpdate:

// SomeComponent.tsx
export const SomeComponents = ({ comment }) => {
	const [likes, setLikes] = useState(comment.likes)
	const [dislikes, setDislikes] = useState(comment.dislikes)
	const [replies, setReplies] = useState(comment.replies)

	return (
		<div>
			<span>{likes}</span>
			<span>{dislikes}</span>
			<span>{replies}</span>

			<AnotherComponent
				comment={comment}
				setLikes={setLikes}
				setDislikes={setDislikes}
				setReplies={setReplies}
			/>

			<button
				onClick={() => {
					setLikes(prev => prev + 1)
					setDislikes(prev => prev + 1)
					setReplies(prev => prev + 1)
				}}
			>
				Update states
			</button>
		</div>
	)
}

Code with useMobxUpdate:

// Updaters.ts
export const updateComment = useMobxUpdate(commentsStore.commentsList)

// SomeComponent.tsx
import { updateComment } from 'path-to-updater'

export const SomeComponents = observer(({ comment }) => {
	return (
		<div>
			<span>{comment.likes}</span>
			<span>{comment.dislikes}</span>
			<span>{comment.replies)}</span>

			<AnotherComponent comment={comment} /> // you don't need to provide useState, update EVERYWHERE!

			<button
				onClick={() => {
					updateComment(comment.id, "likes", prev => prev + 1)
					updateComment(comment.id, "dislikes", prev => prev + 1)
					updateComment(comment.id, "replies", prev => prev + 1)
				}}
			>
				Update states
			</button>
		</div>
	)
})

Options

useMobxUpdate

Function useMobxUpdate 2 params, needs to update values in current element of array from mobx store

Params

ParamTypeDescriptionInitialRequired
arrayArrayArray from mobx storetrue
annotationsAnnotationsMapmakeAutoObservable second param{}false

Returns

ParamTypeDescription
Function(id: string or number, path: string, updater: (prev: any) => void or any) => voidyour updater

-----------------------------

mobxState (like useState)

Usage

// counter-store.ts
class Counter {
	constructor() {
		makeAutoObservable(this)
	}

	count = mobxState(1)('count')
}

const counterStore = new Counter()

// App.tsx
export const App = () => {
	const {
		count: { count, setCount },
	} = counterStore

	return <div onClick={setCount(count + 1)}>{count}</div>
}

You can also use setCount like useState, for example:

setCount(prev => prev + 1)
setCount(prev => {
	return prev ** 2
})

Type whatever you want

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

	count = mobxState<SomeType | number>(1)('count')
}

You can also use mobxState without "clear" functions

Just use { reset: true } option, you state will be clear only if your state unobserved

count = mobxState(0)('count', { reset: true })

Without mobxState VS With mobxState

Code without mobxState:

// posts-store.ts
class PostsStore {
  constructor() {makeAutoObservable(this)}

  count = 1
  addCount = () => this.count += 1

  posts: Post[] = []
  setPosts = (posts: Post[]) => this.posts = posts
}
export const postsStore = new PostsStore()

// App.tsx
const {
  setPosts,
  posts,
  count,
  addCount
} = postsStore

<div
  onClick={() => {
    setPosts(posts.filter(t => t.id !== postId))
    addCount()
  }}
>
  {count}
</div>

Code with mobxState

// posts-store.ts
class PostsStore {
  constructor() {makeAutoObservable(this)}

  count = mobxState(1)('count')
  posts = mobxState<Post[]>([])('posts')
}
export const postsStore = new PostsStore()

// App.tsx
const {
  posts: { setPosts },
  count: { setCount }
} = postsStore

<div
  onClick={() => {
    setPosts(prev => prev.filter(t => t.id !== postId))
    setCount(prev => prev + 1)
  }}
>
  {count}
</div>

Options

mobxState

Function mobxState 3 params, need to create getter and setter logic

Params

ParamTypeDescriptionInitialRequired
initialValuegenericalObject with keys for inputstrue
annotationsAnnotationsMapmakeAutoObservable second param{}false
optionsMakeObservableOptionsmakeAutoObservable third param{}false
@returns_namestringName of state, to create set and get with your nametrue
@optionsMobxStateOptionsYou can set { reset: true } to reset your value on onmount (only if you state ){}false

Returns

ParamTypeDescription
(returns_name)Keyyour value
set(returns_name)() => newValue or (prevValue) => newValueyour setter for value

-----------------------------

useMobxForm (like RHF + Zod, but this is MobX)

Create scheme

// CREATING SCHEME
export const orderFormSchema = m.schema({
	name: m
		.reset()
		.required({ message: 'This is required' })
		.string({ message: 'стринги' })
		.minLength(3, { message: '3 min bro' })
		.maxLength(6, { message: '6 max bro' })
		.build(),
	description: m
		.reset()
		.required({ message: 'Bro?...' })
		.string({ message: 'стринги' })
		.minLength(4, { message: '4 min bro' })
		.maxLength(7, { message: '7 max bro' })
		.build(),
})

Create form

import orderFormSchema from './schema'

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

	orderForm = useMobxForm({ name: '', description: '' }, orderFormSchema)

	submitForm() {
		if (!this.orderForm.validate()) return
		alert('done')
		this.orderForm.reset()
	}
}
export const formStore = new FormStore()

Use in component

const {
	orderForm: {
		setValue,
		values: { name, description },
		errors: { nameErr, descriptionErr },
	},
} = formStore

return (
	<form onSubmit={handleSubmit}>
		<div>
			<label htmlFor='name'>Name:</label>
			<input
				type='text'
				name='name'
				value={name}
				onChange={e => {
					e.preventDefault()
					setValue(e.target.name, e.target.value)
				}}
			/>
			{nameErr && <span>{nameErr}</span>}
		</div>

		<div>
			<label htmlFor='description'>Description:</label>
			<input
				type='text'
				name='description'
				value={description}
				onChange={e => {
					e.preventDefault()
					setValue(e.target.name, e.target.value)
				}}
			/>
			{descriptionErr && <span>{descriptionErr}</span>}
		</div>

		<button type='submit'>Submit</button>
	</form>
)

Options

useMobxForm

Function useMobxForm 3 params, need to create a form, have many options

Params

ParamTypeDescriptionRequired
initialValuesObjectObject with keys for inputstrue
validationSchemaanyYour created schematrue
optionsPartial<FormStateOptions>Options to formfalse

options: Partial:

instaValidate - Instantly validates form onChange input | initial true inputResetErr - Reset errors onChange input | initial true validateAllOnChange - Validating all inputs in form onChange | initial false resetErrIfNoValue - Reset err in current field if input have empty string | initial true disabled - Disable state | initial false observableAnnotations - Annotations for makeAutoObservable | initial {} observableOptions - Options for makeAutoObservable | initial {}

Returns

ParamTypeDescriptionInitial
valuesObjectYour current values
errorsObjectYour errors here, with key+'Err'
initialValuesObjectYour passed initial values DOESN'T CHANGE
disabledbooleanDisable state for inputs or something elseinitial false
optionsPartial<FormStateOptions>Your passed form options
reset'all' or 'values' or 'errors'Resets what u needinitial all
setError(key, value) => voidSet your errors
setValue(key, value) => voidSet your values
validate() => booleanValidate you values and returns true if no errors

-----------------------------

Schemas for useMobxForm

Usage

// CREATING SCHEME
export const orderFormSchema = m.schema({
	name: m
		.reset()
		.required({ message: 'This is required' })
		.string({ message: 'Strings' })
		.minLength(3, { message: '3 min bro' })
		.maxLength(6, { message: '6 max bro' })
		.build(),
	description: m
		.reset()
		.required({ message: 'Bro?...' })
		.string({ message: 'Strings' })
		.minLength(4, { message: '4 min bro' })
		.maxLength(7, { message: '7 max bro' })
		.build(),
})

.reset() required to be in the beginning, and .build() required to be at the end

U can pick and extend validation keys from sheme

// pick function, u need to pass keys as a string array
export const signScheme = emailScheme.pick(['email', 'password'])
export const emailScheme = m.schema({
	email: m
		.reset()
		.required({ message: 'Please write mail' })
		.regex(emailRegex, { message: 'Write correct mail' })
		.build(),
})

// extend function, just like extends from classes :P
export const signScheme = emailScheme.extend({
	password: m
		.reset()
		.required({ message: 'Please write password' })
		.minLength(6, { message: 'Min length of password, 6 bytes' })
		.build(),
})

// extend also have second param, override with initial state false, if override is false your validations in same keys will be connected to one, if override is true, then only validations from the new key will be setted
export const newScheme = someScheme.extend(
	{
		// validations
	},
	true
)

REPO

https://github.com/aianov/mobx-toolkit

Keywords

mobx

FAQs

Package last updated on 07 Aug 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

About

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.

  • Terms
  • Privacy
  • Security

Made with ⚑️ by Socket Inc

U.S. Patent No. 12,346,443 & 12,314,394. Other pending.