@rawewhat/stora

0.2.4 • Public • Published

StoRa

is a global state management library with no dependency and written purely on React hooks.

How it works?

  • in every component that call useStora() hook will subscribe to the global store on component mounted.
  • anytime you call set() to change state, all components that subscribed to global store will re-render.
  • when subscribed component unmounted, global store will un-subscribe it to prevent re-render.
  • with proper usage of useMemo() and useCallback() hook, we can control re-render to quantum level.

so basically, only mounted component that call useStora() hook will re-render when state change

What's next?

  • implement automatically memoization with useMemo and useCallback.
  • state specific component re-render, currently re-render all subscribed component.

Changelog

Content

Install

using npm
npm i @rawewhat/stora

using yarn
yarn add @rawewhat/stora

Setup

1. Initialize states and actions in stora.config.js

create a stora config file in either root or src directory of your project.

  • [project_root]/stora.config.js
    or
  • [project_root]/src/stora.config.js
export default {
  // This will be where you initialize your states
  states: {
    testComponent: {
      testState: 'testState',
    },
    demoComponent: {
      demoState: 'demoState',
    },
  },
  // This will be where you initialize your actions
  actions: {
    testComponent: {
      testAction: (stora) => {
        console.log('stora', stora)
      },
    },
    demoComponent: {
      demoAction: ({ states, actions }) => {
        console.log('states', states, 'actions', actions)
      },
    },
  },
  // If you need to do something before global state initialization
  init: (stora) => {
    console.log('stora', stora)
  },
}

Note: states and actions object use component based naming convention

2. Initialize states and actions in top-level component

if you don't want to use stora.config.js to initialize your states and actions, use below syntax instead

  • change your App.js or any top-level component
import React from 'react'
import useStora from '@rawewhat/stora'
// you can also import it from another place such as your config directory
import { initialStates, initialActions, initializer } from '../config'

const initialStates = {
  testComponent: {
    testState: 'testState',
  },
  demoComponent: {
    demoState: 'demoState',
  },
}

const initialActions = {
  testComponent: {
    testAction: (stora) => {
      console.log('stora', stora)
    },
  },
  demoComponent: {
    demoAction: ({ states, actions }) => {
      console.log('states', states, 'actions', actions)
    },
  },
}

const initializer = (stora) => {
  console.log('stora', stora)
}

const App = (props) => {
  const [states, actions] = useStora({
    states: initialStates,
    actions: initialActions,
    init: initializer,
  })
  console.log('states', states, 'actions', actions)
  return <span>Stora is awesome!</span>
}

export default App

Note: states and actions object use component based naming convention

3. Next.js server-side rendering

to get it working with server-side rendering, we need to make a custom HOC to wrap our _app.js.
if you don't use _app.js, you need to wrap the HOC in every pages.

  • create a HOC in anywhere you want, for example src/service/hoc.js
import React from 'react'
import useStora from '@rawewhat/stora'
// Import our stora config here
import config from '../stora.config'

export const withStora = (PageComponent, { ssr = true } = {}) => {
  const WithStora = (props) => {
    const { states, actions, init } = config
    useStora({ states, actions, init })
    return <PageComponent {...props} />
  }

  if (process.env.NODE_ENV !== 'production') {
    const displayName =
      PageComponent.displayName || PageComponent.name || 'Component'
    WithStora.displayName = `withStora(${displayName})`
  }

  if (ssr || PageComponent.getInitialProps) {
    WithStora.getInitialProps = async (context) => {
      const pageProps =
        typeof PageComponent.getInitialProps === 'function'
          ? await PageComponent.getInitialProps(context)
          : {}

      return {
        ...pageProps,
      }
    }
  }

  return WithStora
}
  • after that wrap your _app.js or page component with withStora HOC

in _app.js

import React from 'react'
import NextApp from 'next/app'
// import our withStora hoc from service/hoc.js
import { withStora } from '../service/hoc'

class App extends NextApp {
  static async getInitialProps(appContext) {
    const appProps = await NextApp.getInitialProps(appContext)

    return { ...appProps }
  }

  render() {
    const { Component, pageProps } = this.props
    return <Component {...pageProps} />
  }
}

// wrap our app with withStora HOC here
export default withStora(App)

in each page component

import { withStora } from '../service/hoc'
import HomeScreen from './screens/Home'
export default withStora(HomeScreen)

Usage

after initialized states and actions in stora.config.

  • just import useStora in any component you want to use it

import useStora from '@rawewhat/stora'

  • then run it in your function component

const [states, actions] = useStora()

API

1. In Actions Config

1.1 set(string, obj)

change only one component states using key and value arguments

const EXAMPLE_ACTIONS = {
  exampleComponent: {
    exampleAction: ({ set }) => {
      set('testComponent', {
        testState: 'changed',
      })
    },
  },
}

any previous states in testComponent will be added automatically

1.2 set(obj)

change multiple component states with new states object

const EXAMPLE_ACTIONS = {
  exampleComponent: {
    exampleAction: ({ set }) => {
      set({
        testComponent: {
          testState: 'changed',
        },
        demoComponent: {
          demoState: 'changed',
        },
      })
    },
  },
}

any previous component states will be added automatically

1.3 get(string)

get only one component states using string name

const EXAMPLE_ACTIONS = {
  exampleComponent: {
    exampleAction: ({ get }) => {
      const testComponent = get('testComponent')
    },
  },
}

1.4 get([string, string])

get multiple component states using arrays of string name

const EXAMPLE_ACTIONS = {
  exampleComponent: {
    exampleAction: ({ get }) => {
      const {
        testComponent: { testState },
        demoComponent: { demoState },
      } = get(['testComponent', 'demoComponent'])
    },
  },
}

2. In React Component

2.1 [{ component: { state }}]

access states of specific component or even multiple component states using destructuring syntax.

const [
  {
    testComponent: { testState },
    demoComponent: { demoState },
  },
  actions,
] = useStora()

2.2 [{ component: { action }}]

access actions of specific component or even multiple component actions using destructuring syntax.

const [
  states,
  {
    testComponent: { testAction },
    demoComponent: { demoAction },
  },
] = useStora()

2.3 [, actions]

skip states object and directly access actions.

const [, actions] = useStora()

2.4 actions.component.action()

use dot to access states and actions

const [states, actions] = useStora()

console.log('testState:', states.testComponent.testState)
actions.demoComponent.demoAction()

3. useStora({ mutate: { obj, func }})

sometimes you want to add more states, actions or both when using useStora hook on a component

  • mutate for adding additional states and actions.

Example: adding another component states

const [
  {
    newComponent: { newState }, // here you have access to the new added state
    testComponent: { testState },
    demoComponent: { demoState },
  },
  actions,
] = useStora({
  mutate: {
    newComponent: {
      newState: 'newState',
    },
  },
})

Example: adding another component actions

const [
  ,
  {
    newComponent: { newActions }, // here you have access to the new added action
    testComponent: { testAction },
    demoComponent: { demoAction },
  },
] = useStora({
  mutate: {
    newComponent: {
      newAction: (stora) => {
        console.log('stora', stora)
      },
    },
  },
})

Example: you can even mix states and actions inside mutate object

const [
  {
    newComponent: { newState }, // here you have access to the new added state
    testComponent: { testState },
    demoComponent: { demoState },
  },
  {
    newComponent: { newActions }, // here you have access to the new added action
    testComponent: { testAction },
    demoComponent: { demoAction },
  },
] = useStora({
  mutate: {
    newComponent: {
      newState: 'newState',
      newAction: (stora) => {
        console.log('stora', stora)
      },
    },
  },
})

by checking mutation object type, useStora is smart enough to know whether you want to add state or action

4. useStora({ query: { string | [string, string] }})

if you want to return only some component states from useStora hook

  • query for selecting specific component states

Example: select only one component states

const [states] = useStora({
  query: 'testComponent',
})
// states for this component contains only testComponent states
const {
  testComponent: { testState },
} = states

Example: select multiple component states

const [states] = useStora({
  query: ['testComponent', 'demoComponent'],
})
// states for this component contains only testComponent and demoComponent states
const {
  testComponent: { testState },
  demoComponent: { demoState },
} = states

5. useStora({ store: true })

if you want to get stora instance in a non-component javascript file

  • store for getting an instance of stora

Example: get access to stora from anywhere.

const stora = useStora({ store: true })

const { actions, states } = stora

Demo

I wrote a simple router using StoRa + RoutRa in this demo project.

  • git clone git@github.com:rawewhat/routra.git to clone the project
  • cd demo change to demo directory
  • yarn install to install dependencies
  • yarn dev it will be ready on http://localhost:1234

Example

import React, { useEffect } from 'react'
import useStora from '@rawewhat/stora'

const App = () => {
  const [, actions] = useStora()
  useEffect(() => {
    actions.testing()
  }, [])
  return (
    <div>
      StoRa Demo
      <Test />
    </div>
  )
}

const Test = () => {
  const [states, actions] = useStora({
    mutate: {
      love: () => {},
    },
  })
  console.log('states', states, 'actions', actions)
  return (
    <div>
      Test
      <Test2 />
    </div>
  )
}

const Test2 = () => {
  const [states, actions] = useStora({
    mutate: {
      test100: {
        test111: 'test111',
      },
    },
    query: ['test100'],
  })
  console.log('states', states, 'actions', actions)
  return <div>Test2</div>
}

export default App

Acknowledgement

This library is written and improved based on an article on medium by André Gardi

License

MIT License
-----------

Copyright (c) 2019 Cheng Sokdara (https://rawewhat-team.com)
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Package Sidebar

Install

npm i @rawewhat/stora

Weekly Downloads

1

Version

0.2.4

License

MIT

Unpacked Size

27.5 kB

Total Files

6

Last publish

Collaborators

  • chengsokdara