import React, { Component, useState, useEffect, useMemo, useRef } from 'react'; import { useDispatch, useStore, useSelector } from 'react-redux'; import isEmpty from 'lodash/isEmpty'; import forEach from 'lodash/forEach'; import remove from 'lodash/remove'; import find from 'lodash/find'; import isEqual from 'lodash/isEqual'; /* !- Actions */ import { switchGroup, switchView, addSettings, removeSettings, toggleView } from './actions'; import { getActiveViews } from './reducers'; /* !- React Elements */ // ... /* !- Constants */ // ... /** * Create settings via childs * View constructor push this settings and active view to Redux View Store. * * If settings props not defined, automatically create group and active via children. * If props defined, then validate props by children * * @private * @return {void} * @example * * // => view widthout props * *
0
*
1
*
2
*
* * // => generated settings * { * active: 'first', * groups: [ * { id: '0', pos: 0, status: 1 }, * { id: 'first', pos: 1, status: 1 }, * { id: 'second', pos: 2, status: 1 }, * ] * } */ export const initSettings = (props) => { let settings = { ...props.settings }; if (!settings.groups && typeof props.children === 'object') { const children = (Array.isArray(props.children)) ? props.children : [props.children]; const group = children.map((child, index) => { let item = { id: child.props['data-view'] || index.toString(), status: parseInt(child.props['data-view-status'] || 1), pos: index, }; if (typeof child.props['data-view'] === 'object') { item = { ...item, ...child.props['data-view'], }; } return item; }); settings = ({ active: '0', groups: { 0: group, }, }); } else if (typeof settings.groups === 'object') { // forEach(settings.groups, (items, groupId) => // { // nested miatt ertelmetlen // settings.groups[groupId] = remove(items, item => // (isNaN(item.id) && find(props.children, child => child.props['data-view'] === item.id)) || // (!isNaN(item.id) && props.children.length > parseInt(item.id)), // ); // // if (!settings.groups[groupId].length) // { // delete settings.groups[groupId]; // } // }); } if (!isEmpty(settings)) { if (!settings.active) { settings.active = Object.keys(settings.groups)[0]; } } return settings; }; /* !- Types */ type SettingsType = { active?: string, groups: { [key: string]: { id: string, status: 0 | 1, pos?: number, title?: string | React.FC, }[], } } const defaultProps = { /** * If false, every content will be rendered only css visibility changes */ lazyload: true, nested: false, className: '', }; type PropTypes = typeof defaultProps & { settings: SettingsType, /** * Determine default group. When mounting switch to group */ defaultView?: string, onChange?: (next: {}, prev: {}, isChanged: boolean) => void, // @todo view State object children: JSX.Element | JSX.Element[], }; /** * Manage view component visibility, similarly tab. * Component automatically generate via childs or use settings */ export const View = ({ settings, defaultView, lazyload, nested, className, onChange, children, }: PropTypes) => { const viewRef = useRef(null); const dispatch = useDispatch(); const store = useStore(); const activeViews = useSelector( getActiveViews({ nested }), (oldValue, newValue) => { let isChanged = isEqual(viewRef.current, newValue) === false; if (isEqual(viewRef.current, newValue) === false) { viewRef.current = newValue; } if (isChanged && typeof onChange === 'function') { // if props.OnChange modify views isChanged = onChange(newValue, oldValue, isChanged) || isChanged; } return !isChanged; }, ); // componentWillMount => constructor useMemo( () => { console.log('componentWillMount'); dispatch(addSettings( initSettings({ settings, children, }) )); if (defaultView !== undefined) { dispatch(switchGroup(defaultView)); } }, [], ); // componentDidMount, componentWillUnmount useEffect( () => { console.log('componentDidMount', 1111); return () => { console.log('componentWillUnmount', 222); dispatch(removeSettings(initSettings({ settings, children, }))); }; }, [], ); const getGroupViews = (group) => { const viewState = store.getState().view; return viewState.groups[group]; } const getFilteredChildren = (view = [], children = []) => { if (!Array.isArray(children) && typeof children === 'object') { children = [children]; } if (!Array.isArray(view) || !Array.isArray(children)) { return []; } return view.reduce( (result, item) => { /** * Filter all dom child by status and data-view * @type {array} child elements */ const itemChildren = children.filter( (child, index) => (item.status || !lazyload) && typeof child !== 'string' && ( (typeof child.props['data-view'] === 'undefined' && index === parseInt(item.id)) || (typeof child.props['data-view'] === 'object' && child.props['data-view'].id === item.id) || child.props['data-view'] === item.id ), ); if (itemChildren.length) { let newResult = []; // not lazyload if (lazyload === false) { newResult.push(itemChildren.map((child, n) =>
{child}
)) } // lazyload else { newResult.push(itemChildren[0]); } // nested if (nested && getGroupViews(item.id)) { newResult = newResult.map(child => { const filteredChildren = this.getFilteredChildren(getGroupViews(item.id), child.props.children); return ({ ...child, props: { ...child.props, children: filteredChildren, }, }); }) } result.push(...newResult); } return result; }, [], ) } // const views = nested ? viewRef.current[Object.keys(settingsRef.current.groups)[0]] : viewRef.current; const views = nested ? activeViews[0] : activeViews; console.log(views); // console.log('render', views, nested); return (
{ getFilteredChildren(views, children) }
) } View.defaultProps = defaultProps; export default View;