npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

Iโ€™ve always been into building performant and accessible sites, but lately Iโ€™ve been taking it extremely seriously. So much so that Iโ€™ve been building a tool to help me optimize and monitor the sites that I build to make sure that Iโ€™m making an attempt to offer the best experience to those who visit them. If youโ€™re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, ๐Ÿ‘‹, Iโ€™m Ryan Hefnerย  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If youโ€™re interested in other things Iโ€™m working on, follow me on Twitter or check out the open source projects Iโ€™ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soonโ€“ish.

Open Software & Tools

This site wouldnโ€™t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you ๐Ÿ™

ยฉ 2025 โ€“ย Pkg Stats / Ryan Hefner

mobx-toolbox

v1.5.3

Published

`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 ๐Ÿ™Œ

Readme

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:

| Param | Type | Description | Initial | Required | | ---------- | ------------ | ----------------------------- | ------- | -------- | | sai | MobxSaiInstance<T> | Your sai instance | | โœ… | | onSuccess | (data: T) => void | Callback that fires when data is valid | | โœ… | | onError | (error: any) => void | Callback that fires if there's an error | | โŒ | | guard | (data: unknown) => data is T | Optional 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

| Param | Type | Description | Initial | Required | | ---------- | ------------ | ----------------------------- | ------- | -------- | | id | string | Id of debounced action | | true | | callback | () => void | Callback to call | | true | | delay | number | Delay of debounce | 1000 | false | | groupKey | string | Group key of debounced action | null | false |

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

| Param | Type | Description | Initial | Required | | ---------- | --------------------- | ---------------------------- | ------- | -------- | | function | () => Promise<ay> | Function to request | | true | | options | MobxSaiFetchOptions | Options to your mobxSaiFetch | {} | false |

Returns

| Param | Type | Description | | ------------------- | -------------------------------------- | ----------------------------------------------------------------- | | data | unknown | your data from fetch | | status | "pending" / "fulfillef" / "rejected" | Your fetch status | | error | string | Just error message | | isFetched | boolean | Can give you information about if mobxSaiFetch is already fetched | | isPenging | boolean | | | isFulfulled | boolean | | | isRejected | boolean | | | addedToEndCount | number | How much times we added new data to the end of our data array | | addedToStartCount | number | How much times we added new data to the start of our data array | | fetchedCount | number | How much times we fetched with mobxSaiFetch | | scrollProgress | number | Our scroll progress (if we passed class in dataScope option) | | gettedToTop | MobxState | How much we getted to top | | botStatus | "pending" / "fulfillef" / "rejected" | Fetch to the bottom status | | topState | "pending" / "fulfillef" / "rejected" | Fetch to the top status | | scrollCachedData | MobxState | Our cached data if we passed cacheSystem and dataScope options | | isBotPending | boolean | | | isBotRejected | boolean | | | isBotFulfilled | boolean | | | isTopPending | boolean | | | isTopRejected | boolean | | | isTopFulfilled | boolean | | | topError | string | Fetch to top error | | botError | string | Fetch to bot error | | isHaveMoreBot | MobxState | Have we more data to the bottom? | | isHaveMoreTot | MobxState | Have 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

| Param | Type | Description | Initial | Required | | ------------- | ---------------- | ------------------------------- | ------- | -------- | | array | Array | Array from mobx store | | true | | annotations | AnnotationsMap | makeAutoObservable second param | {} | false |

Returns

| Param | Type | Description | | ---------- | ----------------------------------------------------------------------------------- | ------------ | | Function | (id: string or number, path: string, updater: (prev: any) => void or any) => void | your 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

| Param | Type | Description | Initial | Required | | --------------- | ----------------------- | ------------------------------------------------------------------------------- | ------- | -------- | | initialValue | generical | Object with keys for inputs | | true | | annotations | AnnotationsMap | makeAutoObservable second param | {} | false | | options | MakeObservableOptions | makeAutoObservable third param | {} | false | | @returns_name | string | Name of state, to create set and get with your name | | true | | @options | MobxStateOptions | You can set { reset: true } to reset your value on onmount (only if you state ) | {} | false |

Returns

| Param | Type | Description | | ------------------- | ------------------------------------------- | --------------------- | | (returns_name) | Key | your value | | set(returns_name) | () => newValue or (prevValue) => newValue | your 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

| Param | Type | Description | Required | | ------------------ | --------------------------- | --------------------------- | -------- | | initialValues | Object | Object with keys for inputs | true | | validationSchema | any | Your created schema | true | | options | Partial<FormStateOptions> | Options to form | false |

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

| Param | Type | Description | Initial | | --------------- | ------------------------------- | --------------------------------------------------- | --------------- | | values | Object | Your current values | | | errors | Object | Your errors here, with key+'Err' | | | initialValues | Object | Your passed initial values DOESN'T CHANGE | | | disabled | boolean | Disable state for inputs or something else | initial false | | options | Partial<FormStateOptions> | Your passed form options | | | reset | 'all' or 'values' or 'errors' | Resets what u need | initial all | | setError | (key, value) => void | Set your errors | | | setValue | (key, value) => void | Set your values | | | validate | () => boolean | Validate 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