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.
Related
I'm using react-router-dom v6. I'm versed with redux and react but only just starting to "go deep" with react-router-dom.
I have nested routes. One of the "nests" has access to the redux <Provider> context.
Despite navigating within the "nest" of Routes with access to the redux context, all of the rendered components in the Router context are being re-rendered - and with it resetting redux.
Finally, there are several reasons for this design, but one of them is so that I can initialize the middleware for a given project (specified in the url).
const middleware = (projectId) => {
let initialized = false;
return (store) => (next) => (action) => {
if (!initialized) {
initialized = true;
next({ type: "SET_READY", projectId });
}
next(action);
};
};
Here is a link to the sandbox that highlights the issue.
Thank you #DrewReese for engaging me on this issue.
In the sandboxed description of the issue, I solve the issue by memoizing the function that instantiates the store. The middleware has a two-step initialization process: (1) set the project id (2) load the store. The store value in the second step depends on what is returned by the server (i.e., if null, use a new store).
// core-app.jsx
// Three versions of the store instantiation process
// 1. resets store on every new page within the project detail view (bad)
// const storeWithProject = store(projectId);
// 2. ##INIT instantiates using initialState where projectId = null (see reducer)
// const storeWithProject = useMemo(() => store(projectId), [projectId]);
// 3. The comprehensive solution
const storeWithProject = useMemo(
() => store(projectId, seedState(projectId)),
[projectId]
);
This all said, in the actual app, I wasn't able to get use the redux <Provider> within the <BrowserRouter> context. I was not able to prevent a re-render of the SubApp component with every route change (as described above); my reliance on useEffect to fetch data (e.g., list of projects) at various points in the app is beyond my current skill-set :-/ (prop and state changes in context of react-router is tough-stuff to manage).
The solution that I'm working on now is to "raise" the redux provider to minimize the number of parents that might trigger a re-render. A somewhat disappointing conclusion because I can't leverage the "natural" entry point for when to instantiate the redux store.
My team develops a few different React applications that share similar components and pages.
how would approach sharing whole pages within different React applications? given that these pages have large amount of components in different hierarchies and each application has its own implementation of accessing the backend?
we are using React, React hooks, Redux, Saga.
In the end those pages are React components, so best way would be to identify all common components in the different React applications your team is developing, and create a component library to put all those components so the are all in a single code base, and is easier to maintain and use. I recommend you check this package https://www.npmjs.com/package/create-react-library
I have used before, is really easy to get it run. Still you can create a new library on your own, but this library will save you some time.
Another approach would be a step forward using micro-frontends architecture, I haven't used yet but looks really promising.
You can check this link https://micro-frontends.org/ and are many resources out there to learn about it. Like I said, I haven't used yet so I can't make you any recommendations about it.
If you decide to use a library for common components, then you can pass actions and hooks you need to execute in the library as props to the components in the library
export const RegisterView = ({ login, useMutation, useQuery, ...props }) => {
const [username, setUsername] = useState('')
const [email, setEmail] = useState('')
const [phone, setPhone] = useState('')
const [submitError, setSubmitError] = useState(null)
const [register, { loading }] = useMutation(REGISTER)
const { data: duplicatedUsername } = useQuery(CHECK_IF_USERNAME_EXIST, {
skip: !username,
variables: { username }
})
const { data: duplicatedEmail } = useQuery(CHECK_IF_EMAIL_EXIST, {
skip: !email,
variables: { email }
})
const { data: duplicatedPhone } = useQuery(CHECK_IF_PHONE_EXIST, {
skip: !phone,
variables: { phone }
})
the component is too large, this is only a fragment, but hope this shows the case you will probably need to use. In this case useQuery, useMutation are hooks from apollo-client I'm passing from the app, the schemas for this calls are also in the library. The login prop is also a hook that calls the right login request, so the RegisterView component only receive those props coming from the app where is being imported and executes them.
Whatever logic your common component need to use or execute that is not shared between all apps, you must put it outside the library and pass it as props.
The create-react-library package also comes with a react app inside to test the components in your library so you are able to debug them and test them in isolation on the apps where you will import them.
Because of the architecture of the application I'm working on, I need to have access to the states of one child component in another child component and I can't take these states to the parent or use react-redux. So I use a technique where I store pointers to the states of the target child in the parent.
In parent I create state for pointers:
export default function App() {
const [compOneStatePointers, setCompOneStatePointers] = useState({});
return (
<div className="App">
<CompOne setCompOneStatePointers={setCompOneStatePointers}></CompOne>
<CompTwo compOneStatePointers={compOneStatePointers}></CompTwo>
</div>
In CompOne I create state and save pointer for this state in parent's 'compOneStatePointers' state.
const [state, setState] = useState(0);
useEffect(() => {
setCompOneStatePointers((prev) => {
const temp = { ...prev };
temp.state = state;
temp.setState = setState;
return temp;
});
}, [setCompOneStatePointers, state]);
After that I can use state from CompOne in CompTwo. Like this:
<button className="button" onClick={() => {
props.compOneStatePointers.setState(prev=> prev+1)
}}>Change state of Comp One</button>
Example of this technique: https://codesandbox.io/s/state-pointers-object-62dkl?file=/src/CompTwo.js
The question is, can I do this or are there pitfalls?
There is a concept of Context API in react js which provides a way to pass data to all the components(called Component Tree) in your react application without passing the props deep to all the levels of component explicitly.
You can read the same concept from the official Documentation of Reactjs https://reactjs.org/docs/context.html#when-to-use-context.
From production perspective, it would be better to use Context API for passing date from one component to one on more component. In real life react application, there would be 100 or more components, and using the context they will interact.
When you keep your app architecture as simple as this, I would say, you have a good approach there. Nothing wrong with passing setState and state to different components from a parent.
Once you get a little more complex, I recommend using something structured like Redux or somehting more native and flexible like rx-global (its only 13kb large) or build your own state management library (like rx-global is).
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.
I started my app without any state management dependencies and my app's state looked something like this:
state = {
...initState,
}
///some actions I need to perform
handleChange = (e) => {
this.setState(setDefaultValues(e));
this.setState(setBmr);
this.setState(setTdee);
this.setState(setTrainingAndRestDaysCal);
this.setState(setTrainingMacros);
this.setState(setRestMacros);
}
here I import my initState from separate file (to save some space). Then I have handleChange where I'm passing functions to multiple this.setState because my next state data depends on previous. I'm importing those functions from separate file (to save some space as well)
Then I came to the point where I realized I'm passing props all over the place so I introduced the new react's context API. Which works very well in my opinion. Kind of like a redux just without a big boilerplate. So this context API helped me with that prop drilling through the child components. To use the context API i had to do some changes to my state object, so it looks like this now:
state = {
...initState,
handleChange: (e) => {
this.setState(setDefaultValues(e));
this.setState(setBmr);
this.setState(setTdee);
this.setState(setTrainingAndRestDaysCal);
this.setState(setTrainingMacros);
this.setState(setRestMacros);
},
setTrainingTrueOrFalse: (isIt) => {
this.setState({ trainingToday: !isIt })
},
saveNewMeal: (meal) => {
const meals = this.state.meals
this.setState({
meals: { ...meals, meal }
})
}
Here I added my handleChange to my state so I can pass it via context api. Then I have created some new functions on the state an realized my state now is getting too messy.
I have a function on the state (handleChange ) that uses other functions imported from setStateFunctions.js
I have functions where logic is not extracted to setStateFunctions.js
On a high level my app has 2 main components: Calculator & MealPlanner
Calculator - calculates the macros and calories and passes the result
to MealPlanner
MealPlanner - uses data from calculator and creates meals
==================================================
What's the better approach to structure my app's state and functions?
Do I really need to introduce redux?
What would be my reducers structure?
How would this look like using just react's new context API?
Your app is sized right to go without adding redux for state management.
1) What's the better approach to structure my app's state and functions?
Let a top-level app maintain state that includes "handler" functions.
2) Do I really need to introduce redux?
Not needed. Keep your props organized and let the App handle the "events".
3) What would be my reducers structure?
Avoid it and go vanilla react.
4) How would this look like using just react's new context API?
Context feels overkill and possibly entangling (two components could drift on the understanding of how to use what is exposed as shared, global state in the context).
Let your composing App manage the state and pass props to the child components (Calculator and MealPlanner). You want two-way communication between those, so also pass "handling" functions that change the state within App to get the effect to ripple to the other via passed-in props. Something like the following:
class App extends React.Component {
state = {
...initState, // not sure if this is global or what, but assuming defined
handleCalculation: () => {
// do some state changes on this ala this.setState()
},
handlePlanning: () => {
},
};
render() {
return (
<div>
<MealPlanner {...this.state} />
<Calculator {...this.state} />
</div>
);
}
}
Let MealPlanner and Calculator set required propTypes accordingly.