I'm building a VSCode extension using React. When the tab loses focus, VSCode shuts down the WebView and only reloads it when the tab gets focus again. The app fully reloads from the start.
VSCode already provides a way to save arbitrary state object and then get it back when restoring the WebView.
What remains is to serialize the full state of the React app (the whole React DOM, states etc) into a simple JSON-like object.
How can I serialize the full state of the React app and then reload it?
I know that React has some features like Server-Side Rendering - maybe they can be used to serialize DOM and state?
To accomplish that, you need some kind of global state object, which holds all the state data that you want to preserve. You can then save and restore this object using the VSCode API you mentioned.
There are several ways to do that and different 3rd-party libraries for this purpose. Here I will outline some of the options.
Context API
Context API is built into React. You need to create an instance and wrap your app with a context provider. Then you can access the state in your child components with useContext.
Here's an example of how you would use it to store some user and page data, as well as control some textarea field in a child component, which would normally be a local state.
const App = () => {
const [user, setUser] = useState();
const [textAreaValue, setTextAreaValue] = useState("");
const [currentPage, setCurrentPage] = useState("home");
// etc.
// this is your global state object that you can then save using VSCode magic
const globalState = { user, setUser, /* etc. */ };
return (
<GlobalStateContext.Provider value={globalState}>
<Child />
</GlobalStateContext.Provider>
);
}
...
const Child = () => {
const { textAreaValue, setTextAreaValue } = useContext(GlobalStateContext);
const handleChange = (e) => {
setTextAreaValue(e.target.value);
}
return (
<textarea value={textAreaValue} onChange={handleChange} />
);
}
Of course, this will be cumbersome if you have a lot of state data to manage. Furthermore, whenever any field in the context changes, all components using it will re-render. This could cause performance issues, so this solution does not scale well. It should be fine for a simple application though.
Custom store hook
Another solution would be to use a global store functionality. You could write a custom hook for that and then use it like this:
const Child = () => {
const { textAreaValue, setTextAreaValue } = useStore("textarea");
const handleChange = (e) => {
setTextAreaValue(e.target.value);
}
return (
<textarea value={textAreaValue} onChange={handleChange} />
);
}
I won't provide a full example of how to implement this for brevity, but here is one guide that could be useful.
3rd-party library
There are also 3rd-party libraries that implement the global store functionality. A popular choice is Redux, although I personally wouldn't recommend it if you haven't used it before, due to its verbosity and somewhat of a learning curve. Other options include Recoil, react-hooks-global-state and ReactN.
I am working with a third-party component which doesn't forward the ref to its DOM component and unfortunately, I need to get a ref to its DOM element in my code.
The code below obviously fails:
const ThirdPartyComponent = (props) => {
return <div>I'm a third party component</div>;
};
const MyComponent = () => {
const ref = useRef();
return <ThirdPartyComponent ref={ref} />;
};
"Function components cannot be given refs. Attempts to access this ref
will fail. Did you mean to use React.forwardRef()?"
See codesandbox here.
Is there anyway to get a ref to the DOM element inside the third party component? I'm aware of the deprecated findDOMNode API. I'm not too keen on it, but even though, I did not manage to make it work.
Note: I know that what I'm asking for is not considered as good practice. The reason I'm doing this is to have react-beautiful-dnd work with DevExtreme's Reactive Grid. Draggable components expects a ref to the draggable elements (the <tr>s here, which I don't have). Just trying to simplify the question.
This is a part of Think in React.
Thinking in React is the hard part for me because I see many developers do React with different mindsets.
When I was writing code for the Notification component that will be used by developers, suddenly I've noticed that there are different experiences to use the component:
Passing many Props like Bootstrap
<Notification
title="New Feature"
body={message}
action={action}/>
Passing one Prop as an Object
const data = {
title:"",
subtitle:"",
message:""
}
<Notification data={data}/>
Passing nested Children
<Notification>
<Title></Title>
<Body><Body/>
<Action><Action>
</Notification>
I followed the passing nested Children because ( I guess) It seems if I scale the component, I don't need to provide a Bootstrap-like experience for the developers.
import React from "react"
import { Wrapper, Text } from "./Styled"
const Body = ({ message }) => (
<Wrapper>
<Text>{message}</Text>
</Wrapper>
)
export default Body
The problem is I'm thinking about it is when I want to scale the Component and let's say adding 3 additional features that require 3 additional props
I'm confused about the reasons why each approach might be chosen, and what's the "best" developer experience.
To answer this question let's review all possibilities given React Element and a Function Component:
const c1 = <div>React Element</div>;
const C2 = () => <div>Function Component</div>;
Notice that from performance perspective, every component can be memoized and not cause useless renders.
Pass React element through props
const ObjectProps = ({ component }) => {
console.log("render object props");
return component;
};
<ObjectProps component={c1} />
Pros
Simple.
Lets you define the exact usage of passed component (contract).
For example you might decide "messages" have to be on top of "actions"
Cons
No lazy rendering
Passing heavy component may cause performance issues.
Hard to inject props (need to use React.cloneElement).
As a library writer you would like to inject your styles and refs.
Passing Function Component
const FunctionComponentProps = ({ FunctionComponent }) => {
console.log("render function component");
return <FunctionComponent />;
};
<FunctionComponentProps FunctionComponent={C2} />
Pros
Lazy rendering.
Easy to pass props and inject props for inner implementation.
Lets you define the exact usage of passed component (contract).
Cons
Confusing
Children Render
const ChildrenRender = ({ children }) => {
console.log("render function component");
return children;
};
<ChildrenRender>
{c1}
<C2 />
</ChildrenRender>
Pros
No restriction for the developer (no contract)
Cons
Hard to inject children (need to use React.Children API + React.cloneElement combo)
No contract
The developer might pass "buttons" and then "messages" and break the view.
Implementing ChildrenRender usually comes with component instances which results a minimal "contract" mentioned above.
const ChildrenRender = ({ children }) => {...};
ChildrenRender.InnerComp1 = <SomeComponent .../>
ChildrenRender.InnerComp2 = <SomeComponent2 .../>
<ChildrenRender>
<ChildrenRender.InnerComp1>{c1}</ChildrenRender.InnerComp1>
<ChildrenRender.InnerComp2><C2/></ChildrenRender.InnerComp2>
</ChildrenRender>
In Conclusion
It heavily depends on the component's usage, usually the hybrid approach suits well - passing components through props and add an option for passing children too.
Another technique is Render Props.
No so long ago Dan updated his article about Presentational and Container Components
saying that with hooks we don't need connect() and containers no more.
But I can't understand what is the profit making views so tightly coupled to model. Now component should know where and how to get data which make it much less isolated which in my opinion is like very big minus
For example I have a ProgressBar component which had only one prop - width
const ProgressBar = (props) => (
<div style={{ width: `${props.width}%` }} className={styles['progress-bar']}></div>
);
I could use this this component anywhere knowing its contract: it takes one prop width. The component itself is unaware of props comes from and in my opinion it's what makes dummy components great.
In container component I collect data from store and send it to dummy component and I think that makes code very clear.
Without container ProgressBar component should collect data from store by itself:
const ProgressBar = () => {
const width = useSelector(state => state.progress.width);
return (
<div style={{ width: `${width}%` }} className={styles['progress-bar']}></div>
);
}
In this case:
In order to work View should know the structure of store (Model)
Looking at component I'm unaware about the contract and should read all the code inside of it to get what it need to work
I cannot reuse it in another components if I want data to be collected not from the store
TL;DR: I'm wondering if a solution I've thought of will cause performance issues.
The title is a bit confusing so let me clarify. A standard way of defining my header button component would be something like this (the code is using typescript but it's not relevant to the problem so you can probably ignore it):
import React from "react";
const HeaderButton = (props: Client.Common.Header.HeaderButton.Import) => {
const service = useHeaderButton();
return <div>
//header component JSX here
<div>
};
export default HeaderButton;
I want to change it up a bit. I've found I would much rather be able to expose some internal component methods to the parent component. In this case, I would like to be able to provide a toggle method to the parent instead of using an "active" property to determine the active state of the button. My reasoning is that I'd rather avoid having to set up the toggle logic in every parent component I use my HeaderButton in if I can instead define it in my button component and then have the parents use that method.
I've done this and it works as I'd like it to (so far, at least). I'm relatively new to both React and programming in general and self taught so I have gaps in my knowledge. I'm not that knowledgeable about React under the hood and how it does its performance optimizations etc so I'm worried I've done something that will cause unpredictable issues. Here's what I've done:
//Header.tsx (this component is using the regular style)
import React, { useEffect } from "react";
import "./Header.scss";
import HeaderButton from "./Components/HeaderButton/HeaderButton";
const Header = () => {
const HeaderButtonService = HeaderButton({ renderProps: <div>TEST</div>, class: "languageSelectionButton" });
useEffect(() => {
setTimeout(() => {
HeaderButtonService.toggle();
}, 5 * 1000);
}, []);
return (
<div id="headerBar">
<div className="headerNavigationButtonsContainer"></div>
{HeaderButtonService.view}
</div>
);
};
export default Header;
//HeaderButton.ts
import HeaderButtonView from "./HeaderButtonView";
const HeaderButton = (props: Client.Common.Header.HeaderButton.Import) => {
const service = useHeaderButton();
return {
toggle: service.toggleActive,
view: HeaderButtonView({ ...props, service.active })
};
};
export default HeaderButton;
//HeaderButtonView.tsx
import React from "react";
import "./HeaderButton.scss";
const HeaderButtonView = (props: Client.Common.Header.HeaderButton.Import) => {
return (
<div className={"headerButton" + (props.active ? " active" : "")
+ (props.class ? " " + props.class : "")}
style={{ ...props.style }}>
{props.renderProps && props.renderProps}
</div>
);
};
export default HeaderButtonView;
In my solution, I import HeaderButton.ts instead of HeaderButton.tsx. The parent component passes the relevant props to HeaderButton.ts which passes them down to HeaderButtonView.tsx while adding the "active" prop which it gets from a custom hook, not the parent component. It then returns the result of invoking HeaderButtonView with these new props as well as the method to toggle the active state.
This is a simple example but I would potentially use this template to expose state values and multiple methods to parent components.
The code works, it renders what I want it to render and toggles the active state after 5 seconds.
My concern is that, not knowing all that much about how react works under the hood, I might have created an optimization problem. Is there any reason I shouldn't be using this pattern?
I have done some testing and it doesn't seem to break anything. I added a counter to the state of header.tsx and increment it every 3 seconds then watch for re-renders. I was concerned that react would not be able to recognize that the old and new HeaderButton components are the same but it did. Though react goes through the component tree, it doesn't re-render the button (except after the first 5 seconds when activity is toggled).
Also, should HeaderButton.ts be a hook? It's working as intended atm so I'm not sure what exactly I gain/lose from adding "use" in front of it.
Your approach here is 1) contrary to how 99% of people use React, 2) contrary to very way React is intended to be used, and 3) overcomplicated in a way that adds a level of abstraction to React that absolutely does not need to be there.
1) This is just not how people write React code. Sure it might work for you and make sense on some level but no one else follows this pattern. What about when you start importing and using other people's components? What about when you have to partner with someone else to write an app? What about when you hand off or are handed off a bunch of code that is patterned in a completely different way? There is absolutely a lot to be said for following prevailing (or at least common) coding patterns because it makes your code a lot more interoperable and easy to understand compared to the rest of the framework ecosystem, and vice-versa.
2) React at its very core is intended to be declarative. It is the number one adjective most people would use to describe it, and features heavily on the very front page of the React website. Your proposed pattern here is very un-declarative, and directly defeats not just the declarative nature of React but the inherent patterns of component state and props. I could link you to examples in the documentation as to how declarative coding, state management, and props feature heavily in React design patterns but the list would include practically every page in the website: Components and Props, State and Lifecycle, Lifting State Up, Thinking in React, etc etc.
3) Your proposed pattern is just... needlessly complicated and abstract. It adds a layer of confusion that does not actually make things easier. I can barely follow even your basic minimal example code!
Your core rationale seems to be this:
My reasoning is that I'd rather avoid having to set up the toggle logic in every parent component I use my HeaderButton in if I can instead define it in my button component and then have the parents use that method.
That's a great instinct - make things reusable and modular so that you don't have to repeat yourself too often. Well, you can do that beautifully in React while adhering to the tenants of React's declarative nature!
First let's rewrite your components in a way that is a more "traditional" React style - just make the <HeaderButton> a regular component that accepts an active prop, and storing that state in the parent, <Header>. This is called "lifting state up" and is a key concept in React - state should live at the lowest common denominator that allows the necessary components access to it. In this case the parent <Header> needs access to the state, because it needs to not only pass it into <HeaderButton> as a prop, but be able to modify that state:
const HeaderButton = ({active}) => {
return <div>{active ? 'Active' : 'Inactive'}</div>
};
const Header = () => {
const [active, setActive] = React.useState(false);
const toggleActive = () => {
setTimeout(() => {
setActive(oldActiveState => !oldActiveState);
}, 5 * 1000);
};
return (
<header>
<button onClick={toggleActive} >Toggle active state</button>
<HeaderButton active={active} />
</header>
);
}
Cool, now state lives in the parent, in can modify that state, and it passes that state as a prop to <HeaderButton>. It is very declarative, easy to understand, and it's clear where state lives and what component is rendering what.
Now on to your concern about reusing the toggle logic. What if we want to use <HeaderButton> somewhere else and have the same toggle logic? What if we want to have five header buttons inside of <Header>? Do we need to copy and paste the same logic many times?
React provides a great solution here with custom hooks. Custom hooks allow you to encapsulate logic and state in a clean way. And - this is very important - the state it encapsulates still lives inside of the component that calls the custom hook. This means we can encapsulate the state and logic but they will still "live" inside of <Header> so we have access to it to pass as a prop. Let's try it:
const useHeaderButtonState = () => {
const [active, setActive] = React.useState(false);
const toggleActive = () => {
setTimeout(() => {
setActive(oldActiveState => !oldActiveState);
}, 5 * 1000);
};
return [active, toggleActive];
}
const HeaderButton = ({active}) => {
return <div>{active ? 'Active' : 'Inactive'}</div>
};
const Header = () => {
const [active, toggleActive] = useHeaderButtonState();
return (
<header>
<button onClick={toggleActive} >Toggle active state</button>
<HeaderButton active={active} />
</header>
);
}
Now, the state and the toggle logic live inside of useHeaderButtonState(). When called, it returns both a value (active) and a function for updating that value (toggleActive). Inside of <Header>, we can deconstruct the result of the custom hook call and use it to render.
We could even extend this custom hook even further to return not just the state and updater function, but a component to render. Then, if we want to render multiple instances of a component, including all its associated state and logic, and still have access to the state and logic in the parent component (<Header>), we can do that:
const useHeaderButtonState = () => {
const [active, setActive] = React.useState(false);
const toggleActive = () => {
setTimeout(() => {
setActive(oldActiveState => !oldActiveState);
}, 5 * 1000);
};
const headerButtonComponent = (
<>
<button onClick={toggleActive}>Toggle active state</button>
<HeaderButton active={active} />
</>
);
return [headerButtonComponent, active, toggleActive];
};
const HeaderButton = ({ active }) => {
return <div>{active ? "Active" : "Inactive"}</div>;
};
const Header = () => {
const [
headerButtonComponent1,
active1,
toggleActive1
] = useHeaderButtonState();
const [
headerButtonComponent2,
active2,
toggleActive2
] = useHeaderButtonState();
const [
headerButtonComponent3,
active3,
toggleActive3
] = useHeaderButtonState();
return (
<header>
{headerButtonComponent1}
{headerButtonComponent2}
{headerButtonComponent3}
</header>
);
};
https://codesandbox.io/s/pensive-sutherland-2onx9
Now we're cooking with gas! We're reusing state and logic but doing it in a declarative way that makes sense inside of React.
Sorry to be so heavy handed but I really, really want to discourage you from using the imperative pattern you proposed above. I've been writing React code for 3+ years and trust me when I say that sticking to the established patterns of React will pay off in the long run. Not only is it legitimately easier to write and comprehend, if will help your career to write code that other devs can more easily understand and work with.
I conduct a lot of hiring interviews and if I saw someone submit the code you wrote above, I would think they have no idea how React works or is intended to work and would immediately disqualify them. If you find it difficult or counterintuitive to understand, I would suggest keep on learning and practicing (with a more declarative React-compatible style) until it clicks. Otherwise, perhaps React just isn't the framework for you and you'd be best served with a different framework that more closely matches your preferences, style, and mental models!
Good luck!
Edit: Oh and one last thing I'll touch upon. You mentioned concerns about performance. In this case the performance differences are actually completely negligible and not worth even considering. In general React is very well optimized on its own and you don't need to worry about performance except in very specific edge cases. You should typically only optimize if and when you actually run into a performance bottleneck, and you solve for that. As they say, premature optimization is the root of all evil.
My response instead addresses the core programming pattern that you are proposing here on the basis that it makes the process of developing, debugging, and understanding the code itself needlessly difficult.