FlexLayout icon indicating copy to clipboard operation
FlexLayout copied to clipboard

Rerender after changing tabs

Open klar-C opened this issue 4 years ago • 9 comments

Hi -

My question is more conceptual.

Whenever the active tab changes it seems to trigger a cycle for the provided factory function - and hence reruns the component generation.

Is there actually a way to turn that off?

Thanks for the awesome library.

klar-C avatar Feb 11 '21 15:02 klar-C

All updates to the layout happen thru actions, each action will cause a re-render, including changing tab. You could make your components pure, so they only depend on props, then they will only re-render if props change.

nealus avatar Feb 16 '21 09:02 nealus

Thank you!

klar-C avatar Apr 25 '21 00:04 klar-C

I have been evaluating rc-dock and flexlayout recently. during my first round of experiment, i encountered a case where one component with internal state e.g. useRef / useState was re-rendered and internal state was lost during the process. In rc-dock, there is an option to cache content of a tab and no internal state is lost. Keeping internal state is very important to allow smooth adoption of a layout system without entailing unnecessary refactoring e.g. redesign existing components to be pure function which is not always possible for complex scenarios. For flex layout, is this something by design or any plan to enable cached behavior in the future?

GOVYANSONG avatar Mar 04 '22 11:03 GOVYANSONG

flexlayout renders the tabs as a keyed list of react elements, so it should not lose state between renders. Do you have an example where state was not maintained?

nealus avatar Mar 04 '22 12:03 nealus

@nealus , thanks for the quick follow up. In my case, the layout is wrapped within a react context provider. the react context is created to facilitate communication / state sharing among different components in the layout. following script for illustration:

context.ts

============== import React, { Context, useState, createContext, useEffect } from "react"; import { AgGridReact as AgGridReactType } from "ag-grid-react/lib/agGridReact";

export type TLayoutContextContent = { gridInstance: AgGridReactType | null; wsJson: object | null; }

export const createStore = () => { const [content, setContent] = useState<Partial<TLayoutContextContent>>({} as TLayoutContextContent);

/*
useEffect(()=>{
    console.log("layout context store content has been updated...")
    console.log(content);
}, [content]);
*/

return {content, setContent};

}

export type TLayoutContext = ReturnType<typeof createStore>; export const LayoutContext = createContext<TLayoutContext>({} as any);

RootLayout.tsx

============== // @ts-nocheck import "/node_modules/flexlayout-react/style/gray.css";

import { useRef, useEffect } from "react"; import { Layout, Model, TabNode, IJsonModel } from "flexlayout-react";

import { LayoutContext, createStore } from "./context"; import { default as gridLayout } from "./layout.json"; import { default as Part_grid } from "./Part_grid"; import { default as Part_action } from "./Part_action";

export default function RootLayout(): JSX.Element { const layoutRef = useRef<Layout | null>(null); const store = createStore();

const factory = (node: TabNode) : React.ReactNode => {
    const Component = node.getComponent();

    if(Component === "grid")
    {
        return (<Part_grid/>);
    }

    if(Component === "ribbon")
    {
        return (<Part_action orientation={node.getOrientation().toString()}/>);
    }
 
    return null;
}

console.log("render layout context");

/*
return (
    <div style={{position:"relative", height:"100vh"}}>
        <LayoutContext.Provider value={ store }>
            <Layout
                ref={ layoutRef }
                model={ Model.fromJson(gridLayout as IJsonModel) }
                factory={factory}                
            />    
        </LayoutContext.Provider>             
    </div>        
);
*/
return (
    <div style={{position:"relative", height:"100vh"}}>
        <LayoutContext.Provider value={ store }>
            <Part_grid/>
            <Part_action orientation="column"/>   
        </LayoutContext.Provider>             
    </div>        
);

}

In component part_action, there are two useRefs to store certain local states, when a button is clicked, it update the react context to trigger re-rendering of itself and part_grid. When flex layout is used, the ref variables get null or undefined value. If i removed layout (as shown above), everything works as expected.

GOVYANSONG avatar Mar 04 '22 13:03 GOVYANSONG

I cannot see why that should not work, can you create a jsfiddle that demonstrates the issue so I can debug it?

nealus avatar Mar 04 '22 14:03 nealus

try creating the model only once, outside the RootLayout or in a useEffect(()=>{}) function

so it becomes:

<div style={{position:"relative", height:"100vh"}}>
    <LayoutContext.Provider value={ store }>
        <Layout
            ref={ layoutRef }
            model={model}
            factory={factory}                
        />    
    </LayoutContext.Provider>             
</div> 

nealus avatar Mar 04 '22 14:03 nealus

@nealus , i tried your suggestion to create model outside layoutcontext and layout. The same issue remained. I was able to capture trace of logic flow with the error in the image attached below: if you pay attention to the trace line old value, it has good content at the beginning and after ribbon is re-created, it becomes empty.

workspace_screenshot3

GOVYANSONG avatar Mar 04 '22 15:03 GOVYANSONG

@nealus , i apologize for having misunderstood your suggestion in previous post. Yes, it worked after i created the model outside of RouteLayout function component (vs. previously within RouteLayout but outside return function). I noticed that the tabset located on right hand border remained open after react context was updated, which is in contrast to before where tab-action/ribbon was automatically closed and recreated. this is awesome :)

Cheers.

GOVYANSONG avatar Mar 04 '22 19:03 GOVYANSONG