I'm attempting to create a singleton class that works in both the main and renderer processes, but I'm not sure what the best way of going about it is.
For example, I have a class which handles storing data:
class Prefs {
constructor() {
this.prefsCache = // Load the prefs from a json file into the cache
}
set(key, value) {
this.prefsCache[key] = value
// Also save the prefs to disk here
}
get(key) {
return this.prefsCache[key]
}
}
export default new Prefs()
This works great in a single process, but if the class is required in the main and renderer, the cache will become out of sync the minuet a preference is changed since each process has a different instance.
It looks like the solution is to use IPC, but I can't find an elegant solution for it. Here I create two different classes. PrefsMain handles saving and storing the data, and PrefsRenderer would communicate to the PrefsMain:
class PrefsMain {
constructor() {
ipcMain.on('set-prefs-value', (event, key, newValue) => {
this.set(key, newValue)
})
}
set(key, value) {
// ...
}
}
class PrefsRenderer {
set(key, newValue) {
ipcRenderer.sendSync('set-prefs-value', key, newValue)
}
}
Doing something like above works, but in reality, I have multiple singletons each with 20 or so methods. It seems like a lot of extra code needs to get written to accomplish it this way.
The other solution I found was to use electron's remote module:
const Prefs = require('electron').remote.require('./prefs.js')
This also works, but it looks like the remote module is going to be deprecated, so I'd rather not do it this way.
Is there a better solution for this? How can I elegantly create a singleton class that can be used in both the main and renderer processes, without having to create a duplicate IPC handler class for each?
You don't need to create two separate classes.
Just have a single Prefs class.
And then for your renderer to set or get things from the Prefs singleton, you would add ipc handlers to your main process:
ipcMain.on("setIntoPrefs", (event, key, value) => {
prefsInstance.set(key, value);
});
ipcMain.handle("getPrefsValue", (event, key) => prefsInstance.get(key));
And your renderer would do:
ipcRenderer.send("setIntoPrefs", key, val);
const val = await ipcRenderer.invoke("getPrefsValue", key);
Related
I'm a bit new to React and I'm currently developing a Proyect which has a service layer. To do so, I created a function component which would have the methods as variables:
const CustomComponent = () => {
method1 = () => {...},
method2 = () => {...},
method3 = () => {...}
}
export default CustomComponent;
This component would then be imported to the component that will use it.
To make my architecture as clean as possible, I wanted to make some of the methods private. However, as you may already know, that is not possible to do in the solution I proposed. Do hoy have an idea on how to achieve this, or maybe there is a convention to make a service layer I'm not aware of?
Thank you so much in advance!
The architecture which I find particularly clean and maintainable is one where you split off logic from presentation into two files like this:
Service layer (ts):
export class Service implements ServiceInterface {
constructor(private instanceVariable: string = "foo") { }
private methodOne(): string {
return this.instanceVariable
}
public methodTwo(argumentVariable: string): string {
const importantString = this.methodOne();
return importantString + argumentVariable;
}
}
interface ServiceInterface {
methodTwo(argumentVariable: string): string;
}
export default new Service();
Service layer (js):
export class Service {
instanceVariable;
constructor(contructorArgument) {
this.instanceVariable = contructorArgument;
}
methodOne() {
return this.instanceVariable
}
methodTwo(argumentVariable) {
const importantString = this.methodOne();
return importantString + argumentVariable;
}
}
export default new Service();
Presentation layer:
import Service from "./service.ts";
const FunctionalComponent = () => {
const [localState, setLocalState] = useState(localStateInit);
return (
<>
<div>{Service.methodTwo("bar")}</div>
</>
)
}
Few things happen here (mostly regarding ts implementation).
Keep component's service and presentation layers in separate files.
Use an interface to describe the service class and its methods. This will help to work with your service layer in your component as you'll get Typescript's IntelliSense.
For this example I'm exporting an instance of the service as default export from its file. This gives you a cleaner API in your component's file, where you can call methods without having to "pollute" component file with instance creation. This has at least the following two drawbacks:
you mostly lose ability to work nicely with static class members
preconfigured instance variable (initiated as a private member in constructor) means its value cannot be replaced in testing.
If any of above are a no go, then clean up the constructor, export just the class itself and instantiate it as required in component file.
I'm also exporting the class itself. This is for testing purposes. In testing you want to be able to swap out arguments passed into class' constructor and you need to have class definition to do that.
You'll notice the shorthand notation for declaring and instantiating a private class variable in the constructor: private instanceVariable: string = "foo". This is equivalent to something like this:
class Service {
private instanceVariable: string;
constructor(constructorArgument: string) {
this.instanceVariable = constructorArgument;
}
Such notation is particularly nice when used with dependency injection.
Overall, this setup will help you with unit testing logic in your service layer, as you can test it like any other class. This comes particularly handy when there's a lot of conditional rendering logic.
Let me know if this is what you've been looking for. Maybe we could tailor it better for your use case.
We are executing a time taking function on web worker. Also I have a Dispatcher Class that create a single instance for other classes to use looks like below:
class Dispatcher {
constructor() {
console.log("calling");
this.events = {};
}
//some code
}
const dispatcher = new Dispatcher();
export default dispatcher;
I have imported this module in another file named as DataManager Class at the top:
import dispatcher from '../../utility/dispatcher';
export class DataManager {
notifyRenderer = (data: ResultData): void => {
dispatcher.dispatch(NOTIFY_EVENT, data);
}
}
and my web worker is creating a new instance of this class and from here we are triggering a notifyRenderer method written in DataManager Class.
import { DataManager } from "./data-manager";
let dm: DataManager;
addEventListener('message', (e) => {
if (!dm) {
dm = new DataManager();
}
const res = dm.addOrUpdateData(e.data.input, true);
dm.notifyRenderer(res);
postMessage({ type: 'Dispatch', res });
}, false);
Also I am attaching a screenshot of my console showing console.log("calling"); two times. I have no idea why Dispatch class constructor is getting called two times.
Is there any mistake i am doing. Need help on this please.
I feel like module imports might be the problem. Is it?
Adding screenshot trace from worker:
Thank you in Advance!
Everyone learns from there mistake's and practice and same happened with me here.
Everything was correct in my module's either it is Dispatcher Class, Data Manager Class and instantiate a new worker thread. The problem was that I created a single instance of Dispatcher Class at the end of that module, as I thought resulting exports will get cached(as per mjs(require) file caching mechanism,) and tried to use(import) in both main thread and worker thread and both threads was creating there own version of module instances.(My Mistake!)
I didn't even realize until bergi gave me a hint. The correct way is to send data from worker thread to main thread using postmessage and listen on main thread.
Sent from worker thread:
postMessage(dm.addOrUpdateData(e.data.input, true) as ResultData);
Listen on main thread:
this.worker.onmessage = (e: MessageEvent) => {
if (!e.data) return;
dispatcher.dispatch(NOTIFY_EVENT, e.data);
};
Understand More on Realms and helping links must visit:
How to understand JS realms
ES6 import duplicates?
Thanks!
In the Apollo Server documentation, it describes the behaviour of the default resolver, which is quite handy.
I also gathered from somewhere else (another SO question if I recall), that you can override the default resolver function with your own, by passing a fieldResolver function into the options for the apollo-server instance:
const server = new ApolloServer({ typeDefs, resolvers,
fieldResolver: function (source, args, context, info) {
console.log("Field resolver triggered!")
return null;
}
});
What I would like to do is augment the default behaviour, rather than overriding it. Specifically, I am integrating with a REST API that returns fields in snake_case, whereas my schema is attempting to follow the advised convention of using camelCase for field names. I would like to wrap this field name conversion around the default resolver behaviour, rather than having to re-write it.
Alternatively, if somebody can point me to the source location for the default resolver implementation, I'd be happy enough to take that and adapt it either!
The default resolver is available through the graphql module:
const { defaultFieldResolver } = require('graphql')
However, converting a field from snake case to camel case can be done without calling the default resolver:
someField: (parent) => parent.some_field
If you want to create a reusable resolver function, you can do something like:
const camelCaseResolver = (parent, args, ctx, info) => {
return parent[_.snakeCase(info.fieldName)]
}
Or better yet, extract the logic into a schema directive:
class SnakeCaseDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
field.resolve = async function (parent, args, ctx, info) {
return parent[_.snakeCase(info.fieldName)]
}
}
}
Suppose I'm using an external API which works with Machine objects. You can create a Machine with createMachine, which will give you a complex object with several nested properties, and a set of functions to alter state on that object. The API provides for example: loadMemory, sleep, connectDevice. (Imagine anything similar, this is just an example).
I want to mantain a global Vuex Machine object, so I've an action to dispatch that initial creation and store the returned object just like:
actions: {
createChannel({ commit, state }, params) {
m.createMachine(params).then(
function (newMachine) {
commit('setMachine', newMachine);
}
).catch(err => { console.error("Error"); } )
}
}
The mutation is pretty straightforward in this case:
setMachine(state, machine) {
state.machine = machine;
}
Now that API set for the "Machine" objects, as we know has a bunch of state-modifying calls -we don't know what specific fields they change-.
As they modify state, and I want to use them to affect the global Machine object in the Vuex store, I would like to wrap them in actions.
An action could call
this.state.machine.loadMemory(mem.addr)
But if this call itself modified the machine object, how do I commit the new state? Should I clone the old object, apply the state-changing method and replace the object ?
I know cloning is not an easy task.
Thanks.
You can re-mount your complex object. According to the example, the mutation could be:
loadMemory(state, newAddr) {
const { machine } = state;
state.machine = {
...machine, // set all machine's properties
addr: newAddr,
};
}
It works in any level of nested objects you want. Another example:
loadMemory(state, newValue) {
const { machine } = state;
const { machineObjProp } = machine;
state.machine = {
...machine, // set all machine's properties
machineObjProp: {
...machineObjProp, // set all machineObjProp's properties
value: newValue,
},
};
}
One way is using lodash cloneDeep, it will copy app properties and methods of object
import _ from lodash
this.state.machine.loadMemory(mem.addr)
const copyMachine = _.cloneDeep(this.state.machine)
this.$store.commit('setMachine', copyMachine)
What is the current idiomatic way to lazy load properties when using MobX?
I've been struggling with this for a few days, and I haven't found any good examples since strict mode has become a thing. I like the idea of strict mode, but I'm starting to think lazy-loading is at odds with it (accessing, or observing a property should trigger the side effect of loading the data if it's not already there).
That's the crux of my question, but to see how I got here keep reading.
The basics of my current setup (without posting a ton of code):
React Component 1 (ListView):
componentWillMount
componentWillMount & componentWillReceiveProps - the component gets filter values from route params (react-router), saves it as an observable object on ListView, and tells the store to fetch 'proposals' based on it
Store.fetchProposals checks to see if that request has already been made (requests are stored in an ObservableMap, keys by serializing the filter object so two identical filters will return the same response object). It makes the request if it needs to and returns the observable response object that contains info on whether the request is finished or has errors.
ListView saves the observable response object as a property so it can display a loading or error indicator.
ListView has a computed property that calls Store.getProposals using the same filter object used to fetch
Store.getProposals is a transformer that takes a filter object, gets all proposals from an ObservableMap (keys on proposal.id), filters the list using the filter object and returns a Proposal[] (empty if nothing matched the filter, including if no proposals are loaded yet)
This all appears to work well.
The problem is that proposals have properties for client and clientId. Proposal.clientId is a string that's loaded with the proposal. I want to wait until client is actually accessed to tell the store to fetch it from the server (assuming it's not already in the store). In this case ListView happens to display the client name, so it should be loaded shortly after the Proposal is.
My closest I've gotten is setting up a autorun in the Proposal's constructor list this, but part of it is not reacting where I'm indending. (truncated to relevant sections):
#observable private clientId: string = '';
#observable private clientFilter: IClientFilter = null;
#observable client: Client = null;
constructor(sourceJson?: any) {
super(sourceJson);
if (sourceJson) {
this.mapFromJson(sourceJson);
}
//this one works. I'm turning the clientId string into an object for the getClients transformer
autorun(() => { runInAction(() => { this.clientFilter = { id: this.clientId }; }) });
autorun(() => {
runInAction(() => {
if (this.clientId && this.clientFilter) {
const clients = DataStore.getClients(this.clientFilter);
const response = DataStore.fetchClients(this.clientFilter);
if (response.finishedTime !== null && !response.hasErrors) {
this.client = clients[0] || null;
console.log('This is never called, but I should see a client here: %o', DataStore.getClients(this.clientFilter));
}
}
})
});
}
The response object is observable:
export class QueryRequest<T extends PersistentItem | Enum> {
#observable startTime: Date = new Date();
#observable finishedTime: Date = null;
#observable errors: (string | Error)[] = [];
#observable items: T[] = [];
#computed get hasErrors() { return this.errors.length > 0; }
#observable usedCache: boolean = false;
}
I'm getting the feeling I'm fighting the system, and setting up autoruns in the constructor doesn't seem ideal anyway. Anyone solve this pattern in a reasonable way? I'm open to suggestions on the whole thing if my setup looks crazy.
EDIT 1: removed #Mobx for clarity.
EDIT 2:
Trying to re-evaluate my situation, I (again) found the excellent lib mobx-utils, which has a lazyObservable function that may suite my needs. Currently it's looking like this:
client = lazyObservable((sink) => {
autorun('lazy fetching client', () => {
if (this.clientFilter && this.clientFilter.id) {
const request = DataStore.fetchClients(this.clientFilter);
if (request.finishedTime !== null && !request.hasErrors) {
sink(request.items[0]);
}
}
})
}, null);
This is working!
I think I need the autorun in there to update based on this objects clientId/clientFilter property (if this object is later assigned to a new client I'd want the lazyObservable to be updated). I don't mind a little boilerplate for lazy properties, but I'm, definitely open to suggestions there.
If this ends up being the way to go I'll also be looking at fromPromise from the same lib instead of my observable request object. Not sure because I'm keeping track of start time to check for staleness. Linking here in case someone else has not come across it:)
I've been using a different approach in my projects and I extracted it into a separate npm package: https://github.com/mdebbar/mobx-cache
Here's a quick example:
First, we need a React component to display the client info:
#observer
class ClientView extends React.Component {
render() {
const entry = clientCache.get(this.props.clientId)
if (entry.status !== 'success') {
// Return some kind of loading indicator here.
return <div>Still loading client...</div>
}
const clientInfo = entry.value
// Now you can render your UI based on clientInfo.
return (
<div>
<h2>{clientInfo.name}</h2>
</div>
)
}
}
Then, we need to setup the clientCache:
import MobxCache from "mobx-cache";
function fetchClient(id) {
// Use any fetching mechanism you like. Just make sure to return a promise.
}
const clientCache = new MobxCache(fetchClient)
That's all you need to do. MobxCache will automatically call fetchClient(id) when it's needed and will cache the data for you.