Skip to content

Commit

Permalink
feat: Created createContextModel for context specific stores
Browse files Browse the repository at this point in the history
  • Loading branch information
joelmoss committed May 26, 2022
1 parent 1ca7d85 commit 149436c
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 19 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
"scripts": {
"clean": "rimraf dist",
"test": "jest",
"build": "microbundle",
"dev": "microbundle watch",
"build": "microbundle --jsx React.createElement",
"dev": "microbundle watch --jsx React.createElement",
"prepublishOnly": "yarn clean && yarn build",
"size": "microbundle && size-limit"
},
Expand Down
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { default as useIbiza } from './use_ibiza.js'
export { default as store, rawStateOf } from './store.js'
export { createModel } from './model.js'
export { createModel, createContextModel, IbizaProvider } from './model.jsx'
export * from './helpers.js'
66 changes: 52 additions & 14 deletions src/model.js → src/model.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
import { useDebugValue } from 'react'
import * as React from 'react'
import { useContext, useState, createContext, useDebugValue } from 'react'

import useIbiza from './use_ibiza.js'
import store from './store.js'

const IbizaContext = createContext()

export const IbizaProvider = ({ children, ...props }) => {
const [modelName] = useState(uuid)

return <IbizaContext.Provider value={modelName}>{children}</IbizaContext.Provider>
}

export function createContextModel(modelDef = {}, options = {}) {
const nameContext = IbizaContext

function useIbizaModel(slice, props) {
const modelName = useContext(nameContext)

useDebugValue(`[Ibiza]${modelName}`)

if (typeof slice !== 'string') {
props = slice
slice = undefined
}

initState(modelName, modelDef, props)
store.modelOptions[modelName] = options

return useIbiza(slice ? [modelName, slice].join('.') : modelName)
}

return useIbizaModel
}

// Creates an Ibiza model, and returns a React hook wrapping useIbiza with the given `modelName` as
// the slice. The given `modelDef` is assigned as the initial state to the `modelName` state key. If
// `modelDef` is a function, it will be called with any passed initial state, and props that are
Expand All @@ -26,29 +57,36 @@ import store from './store.js'
//
export function createModel(modelName, modelDef = {}, options = {}) {
function useIbizaModel(slice, props) {
useDebugValue(modelName)
useDebugValue(`[Ibiza]${modelName}`)

if (typeof slice !== 'string') {
props = slice
slice = undefined
}

if (modelName in store.state === false) {
if (typeof modelDef === 'function') {
const initialState = (store.modelInitializers[modelName] = state => modelDef(state, props))

if (modelName.indexOf('/') !== 0) {
store.state[modelName] = initialState(store.state[modelName])
}
} else {
store.state[modelName] = modelDef
}
}

initState(modelName, modelDef, props)
store.modelOptions[modelName] = options

return useIbiza(slice ? [modelName, slice].join('.') : modelName)
}

return useIbizaModel
}

function initState(modelName, modelDef, props) {
if (modelName in store.state === false) {
if (typeof modelDef === 'function') {
const initialState = (store.modelInitializers[modelName] = state => modelDef(state, props))

if (modelName.indexOf('/') !== 0) {
store.state[modelName] = initialState(store.state[modelName])
}
} else {
store.state[modelName] = modelDef
}
}
}

function uuid() {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
}
58 changes: 56 additions & 2 deletions test/model.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { renderHook, act as hookAct } from '@testing-library/react-hooks'
import React, { Suspense } from 'react'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import { store, freeze, createModel } from 'ibiza'
import { store, freeze, createModel, createContextModel, IbizaProvider } from 'ibiza'

const server = setupServer(
rest.get('/post', async (req, res, ctx) => {
Expand All @@ -25,7 +25,7 @@ afterEach(() => {
afterAll(() => server.close())

it('merges initial state', () => {
const usePost = createModel('post', (state, props) => ({
const usePost = createModel('post', (_, props) => ({
title: 'Post#1',
...props
}))
Expand Down Expand Up @@ -356,3 +356,57 @@ describe('freezing', () => {
expect(result.current.user.deep.name.firstName).toBe('Joel')
})
})

describe('createContextModel', () => {
it('multiple instances', () => {
const usePost = createContextModel((_, props) => ({
title: 'Post#?',
...props
}))

const Post = ({ title }) => {
const post = usePost({ title })
return <h1>{post.title}</h1>
}
const App = () => {
return (
<>
<IbizaProvider>
<Post title="Post#1" />
</IbizaProvider>
<IbizaProvider>
<Post title="Post#2" />
</IbizaProvider>
</>
)
}

render(<App />)

screen.getByText('Post#1')
screen.getByText('Post#2')
})

test('basic', () => {
const usePost = createContextModel((_, props) => ({
title: 'Post#1',
...props
}))

const Post = () => {
const post = usePost({ title: 'Post#2' })
return <h1>{post.title}</h1>
}
const App = () => {
return (
<IbizaProvider>
<Post />
</IbizaProvider>
)
}

render(<App />)

screen.getByText('Post#2')
})
})

0 comments on commit 149436c

Please sign in to comment.