<commit_before>import React, { useEffect, useRef } from 'react'
import { connect as reduxConnect } from 'react-redux'

import { getContext } from '../context'

import { createConstants } from '../core/steps/constants'
import { getLocalPlugins, runPlugins, reservedProxiedKeys } from '../plugins'

import { getBuiltLogic } from './logic'
import { getPathStringForInput } from './path'
import { mountPaths, unmountPaths } from './mount'

function createWrapperFunction (input) {
  const wrapper = (Klass) => {
    const plugins = getLocalPlugins(input)

    runPlugins(plugins, 'beforeWrapper', input, Klass)

    // make this.actions work if it's a React.Component we're operating with
    injectActionsIntoClass(Klass)

    let isUnmounting = {}
    let lastState

    // TODO: why is isUnmounting with a key, but lastState without?
    // it seems to work, but why?

    const createConnect = reduxConnect(
      (state, ownProps) => {
        // At the moment when we unmount and detach from redux, react-redux will still be subscribed to the store
        // and will run this function to see if anything changed. Since we are detached from the store, all
        // selectors of this logic will crash. To avoid this, cache and return the last state.
        // Nothing will be rendered anywa.
        if (isUnmounting[input.key ? input.key(ownProps) : '*']) {
          return lastState
        }

        // TODO: any better way to get it?
        const logic = getBuiltLogic({ input, props: ownProps })

        let resp = {}
        Object.entries(logic.selectors).forEach(([key, selector]) => {
          resp[key] = selector(state, ownProps)
        })

        lastState = resp

        return resp
      },
      (dispatch, ownProps) => {
        // TODO: any better way to get it?
        const logic = getBuiltLogic({ input, props: ownProps })

        let actions = Object.assign({}, ownProps.actions)

        Object.entries(logic.actions).forEach(([key, action]) => {
          actions[key] = (...args) => dispatch(action(...args))
        })

        return {
          dispatch: dispatch,
          actions: actions
        }
      }
    )
    const Connect = createConnect(Klass)

    // inject proptypes into the class if it's a React.Component
    // not using useRef here since we do it only once per component
    let injectPropTypes = !isStateless(Klass)

    const Kea = function (props) {
      // TODO: any better way to get it?
      const logic = getBuiltLogic({ input, props, extendedInputs: wrapper._extendWith })

      // inject proptypes to React.Component
      if (injectPropTypes && logic.propTypes) {
        injectPropTypes = false
        Klass.propTypes = Object.assign(Klass.propTypes || {}, logic.propTypes)
      }

      // mount paths only on first render
      const firstRender = useRef(true)
      if (firstRender.current) {
        firstRender.current = false

        mountPaths(logic, plugins)
      }

      // unmount paths when component gets removed
      useEffect(() => () => {
        // set this as mapStateToProps can still run even if we have detached from redux
        const key = input.key ? input.key(props) : '*'
        isUnmounting[key] = true
        unmountPaths(logic, plugins)
        delete isUnmounting[key]
      }, [])

      // TODO: unmount & remount if path changed
      runPlugins(plugins, 'beforeRender', logic, props)
      return <Connect {...props} />
    }

    Kea._wrapper = wrapper
    Kea._wrappedKlass = Klass

    runPlugins(plugins, 'afterWrapper', input, Klass, Kea)
    return Kea
  }

  return wrapper
}

export function kea (input) {
  // TODO: this might not be needed
  storeInputOnContext(input)

  const wrapper = createWrapperFunction(input)

  wrapper._isKea = true
  wrapper._isKeaWithKey = typeof input.key !== 'undefined'

  wrapper._extendWith = []
  wrapper.extend = (extendedInput) => {
    if (!input.key && wrapper.isBuilt()) {
      throw new Error('[KEA] Can not extend logic once it has been built!')
    }
    wrapper._extendWith.push(extendedInput)
    return wrapper
  }

  if (input.key) {
    wrapper.buildWithKey = (key) => {
      return getBuiltLogic({ input, key, extendedInputs: wrapper._extendWith })
    }

    wrapper.withKey = keyCreator => {<commit_msg>document wrapper functions<commit_after>import React, { useEffect, useRef } from 'react'
import { connect as reduxConnect } from 'react-redux'

import { getContext } from '../context'

import { createConstants } from '../core/steps/constants'
import { getLocalPlugins, runPlugins, reservedProxiedKeys } from '../plugins'

import { getBuiltLogic } from './logic'
import { getPathStringForInput } from './path'
import { mountPaths, unmountPaths } from './mount'

function createWrapperFunction (input) {
  const wrapper = (Klass) => {
    const plugins = getLocalPlugins(input)

    runPlugins(plugins, 'beforeWrapper', input, Klass)

    // make this.actions work if it's a React.Component we're operating with
    injectActionsIntoClass(Klass)

    let isUnmounting = {}
    let lastState

    // TODO: why is isUnmounting with a key, but lastState without?
    // it seems to work, but why?

    const createConnect = reduxConnect(
      (state, ownProps) => {
        // At the moment when we unmount and detach from redux, react-redux will still be subscribed to the store
        // and will run this function to see if anything changed. Since we are detached from the store, all
        // selectors of this logic will crash. To avoid this, cache and return the last state.
        // Nothing will be rendered anywa.
        if (isUnmounting[input.key ? input.key(ownProps) : '*']) {
          return lastState
        }

        // TODO: any better way to get it?
        const logic = getBuiltLogic({ input, props: ownProps })

        let resp = {}
        Object.entries(logic.selectors).forEach(([key, selector]) => {
          resp[key] = selector(state, ownProps)
        })

        lastState = resp

        return resp
      },
      (dispatch, ownProps) => {
        // TODO: any better way to get it?
        const logic = getBuiltLogic({ input, props: ownProps })

        let actions = Object.assign({}, ownProps.actions)

        Object.entries(logic.actions).forEach(([key, action]) => {
          actions[key] = (...args) => dispatch(action(...args))
        })

        return {
          dispatch: dispatch,
          actions: actions
        }
      }
    )
    const Connect = createConnect(Klass)

    // inject proptypes into the class if it's a React.Component
    // not using useRef here since we do it only once per component
    let injectPropTypes = !isStateless(Klass)

    const Kea = function (props) {
      // TODO: any better way to get it?
      const logic = getBuiltLogic({ input, props, extendedInputs: wrapper._extendWith })

      // inject proptypes to React.Component
      if (injectPropTypes && logic.propTypes) {
        injectPropTypes = false
        Klass.propTypes = Object.assign(Klass.propTypes || {}, logic.propTypes)
      }

      // mount paths only on first render
      const firstRender = useRef(true)
      if (firstRender.current) {
        firstRender.current = false

        mountPaths(logic, plugins)
      }

      // unmount paths when component gets removed
      useEffect(() => () => {
        // set this as mapStateToProps can still run even if we have detached from redux
        const key = input.key ? input.key(props) : '*'
        isUnmounting[key] = true
        unmountPaths(logic, plugins)
        delete isUnmounting[key]
      }, [])

      // TODO: unmount & remount if path changed
      runPlugins(plugins, 'beforeRender', logic, props)
      return <Connect {...props} />
    }

    Kea._wrapper = wrapper
    Kea._wrappedKlass = Klass

    runPlugins(plugins, 'afterWrapper', input, Klass, Kea)
    return Kea
  }

  return wrapper
}

/*
  Initialize logic and create a wrapper function that can be used to apply this
  logic onto React components.

  const logic = kea(input)

  The wrapper will delegate all the fields of the logic onto the logic itself
  and define a few functions to manipulate the logic's state on the context (e.g mounting)

  NB! This list is a work in progress and will still change

  Constants:

  - logic._isKea
  - logic._isKeaWithKey

  Functions defined on all wrappers:

  - logic.extend

  Functions defined on wrappers with keys:

  - logic.withKey
  - logic.buildWithKey
  - logic.mountWithKey

  Functions defined on wrappers without keys:

  - logic.isBuilt
  - logic.build
  - logic.mount

  Delegated fields on wrappers without keys:

  - logic.path
  - logic.pathString
  - logic.plugins
  - logic.props

  - logic.connections
  - logic.constants
  - logic.actions
  - logic.defaults
  - logic.reducers
  - logic.reducerOptions
  - logic.reducer
  - logic.selector
  - logic.selectors
  - logic.propTypes

*/
export function kea (input) {
  // TODO: this might not be needed
  storeInputOnContext(input)

  const wrapper = createWrapperFunction(input)

  wrapper._isKea = true
  wrapper._isKeaWithKey = typeof input.key !== 'undefined'

  wrapper._extendWith = []
  wrapper.extend = (extendedInput) => {
    if (!input.key && wrapper.isBuilt()) {
      throw new Error('[KEA] Can not extend logic once it has been built!')
    }
    wrapper._extendWith.push(extendedInput)
    return wrapper
  }

  if (input.key) {
    wrapper.buildWithKey = (key) => {
      return getBuiltLogic({ input, key, extendedInputs: wrapper._extendWith })
    }

    wrapper.withKey = keyCreator => {
{'fraction_non_alphanumeric': 0.10236630876680913, 'fraction_numerical': 0.0, 'mean_word_length': 2.6081233546446034, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4842926', 'n_tokens_mistral': 2790, 'n_tokens_neox': 2693, 'n_words': 879}