mobx-toolbox v1.3.5
Mobx-toolkit, mini-lib for easy in MobX using
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
// SomeComponent.tsx
const { commentsList } = commentsStore // store from MobX
const updateComments = useMobxUpdate(commentsList)
return (
<div>
{commentsList.map(comment => (
<button
key={comment.id}
onClick={() => {
updateComments(
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
)