Setup:
BabelJS (es2015, react, stage-1)
Webpack
React / redux
New to CommonJS and ES6. I know the difference between an object instance and a static container of methods but I am not sure how they behave when separated to modules. So I wonder what are the differences between returning an instance (is this pattern valid at all?):
// StateParser.js
class StateParser {
constructor() {
}
method1() {
...
}
}
export default new StateParser()
and exporting const methods:
// StateParser.js
let state = {
}
export const method1 = () => { ... }
Method A: Would there be a new instance every time I import?
Method B: Is one of the benefits the ability to use object destructuring:
import { method1 } from '../utils/StateParser.js';
and then use method1 as if it existed locally?
Method A: Is one of the benefits the ability to initialize state in the constructor?
So basically I am not sure when to use which for my utility classes and would appreciate your input.
Would there be a new instance every time I import A?
No, modules are only evaluated once.
Is one of the benefits of B the ability to use object destructuring and then use method1 as if it existed locally?
Yes, though it's not called "destructuring". They're named imports (or named exports of the module), and they don't nest and use a different syntax for aliasing.
Is one of the benefits of A the ability to initialize state in the constructor?
No. You can initialise module state just directly in the module scope as well, you don't need a constructor function for that.
But yes, if you have state in the instances, it's a good idea to use a class which you can instantiate multiple times. For that, you need to export the class itself, not an instance, of course.
Is the export default new … pattern valid at all?
No, it's an antipattern for the reasons outlined above. Given the class is used nowhere else, it's quite similar to the anonymous class antipattern. And exporting multiple named exports is much better than default-exporting objects anyway.
We don't recommend exporting an evaluation (e.g. new StateParser()) for several reasons.
In this case, the module exports the result which is only evaluated once (also mentioned by #Bergi). This is rarely the desired outcome, but if it is, a Singleton pattern should be used instead. Some ES6 module benefits are lost (tree-shaking and faster access to imports), it makes imports slower and makes them possible to cause side-effects which should rather happen upon invocation. I also think this is an anti-pattern and the drawbacks can be avoided via exporting a function or class.
It would make more sense to compare export default StateParser with exporting const methods.
See also:
All exports are static
ES6 modules are only evaluated once
Lost ES6 benefits
Related
I'm currently thinking about the perfect architecture for my professionals projects needs.
I read a lot of article about (clean) architecture and I got to the point were I think that I want my UI managed with React totally separated from the application business logic that will be managed by "application manager". The issue is that I want the "application manager" to config and trigger mutations (I think get queries can be used in components without any issue). But since react-query require it to be in React component by using hooks, I don't think it is possible.
I am wrong ?
Does it exist a workaround ?
Maybe you have a library that manage that better ? I'm thinking about RTK Query maybe...
I am a heavy user of RQ for quite some time and since architecture question can never have an objectively correct answer, I can demonstrate what I do personally.
First, I extract all queries and components into API modules by domain, given a simple app with posts, authors and comments, I would have files along these lines with those exports:
// apis/posts.js
export function useGetPosts() {}
export function useGetPost(postId) {}
export function usePutPost() {}
export function usePostPost() {}
export function useDeletePost() {}
// apis/comments.js
export function useGetComments(postId) {}
export function useGetComment(commentId) {}
export function usePutComment() {}
export function usePostComment() {}
export function useDeleteComment() {}
// apis/authors.js
export function useGetAuthors() {}
export function useGetAuthor(authorId) {}
export function usePutAuthor() {}
export function usePostAuthor() {}
export function useDeleteAuthor() {}
Each of those modules would internally handle everything necessary to work as a whole, like useDeleteAuthor would have a mutation and also modify the cache on success, or possibly implement optimistic updates.
Each will have a system of query keys so that the consumer (your components) don't have to know a thing about them.
function MyComponent() {
const posts = useGetPosts()
}
function MyOtherComponent() {
const deletePost = useDeletePost()
}
Try to make the APIs as complete as possible, but also don't forget that mutations can, for example, accept callbacks on call-site:
deletePost.mutate(payload, {
onMutate: () => setState(false)
})
Let's assume you can use this to for example close a confirmation modal before deleting. Something like this doesn't belong to API module, so we just provide it as a local callback to the mutation.
As stated above, there is no correct answer. There is definitely an argument for doing it the other way round and using collocation more, putting queries next to the components where you are using them. But if you want separation, this would be a place to start in my opinion.
As Ben wrote in the comment to your question, RQ is just hooks, so I agree that trying to put it "outside of react" is non-sensical.
You're right, the short answer is react-query is not compatible with clean architecture, and by experience it leads to tight coupling between logic and components
One way that I'm experimenting with is using the queries in components as is, without implementing side effects. Unless it is side effects specifically for that components.
Then inside my logic layer, I would use the QueryObserver and subscribe to changes to whatever key/keys I need.
const observer = new QueryObserver(myQueryClient, {
queryKey: ['key']
})
observer.subscribe(result => console.log(result))
In this example I have my queryClient defined in its own file.
This way I can have my logic seperated from the view layer, but still use the awesome way react-query works.
Note that this way, the logic will only run when a component is mounted that the query function is resolved.
Also the subscibe function can only be called after the inital useQuery is mounted. Else you will get a "Missing queryFn" error. Which is not ideal. Or even close.
I would like to store some variables inside a module (export) to be used as constants though out my react app. I would like to avoid context because there is no need for components to re-render and also I need those constants to be used outside my react components.
Where should I do it (where to import it), in order to prevent garbage collection?
One idea I have is to import and re-export it on top of my root component.
EDIT:
To be more precise, there will be a component that will set the constant once (mutate the variable), so that other components or files can access it.
So, what you will need is some sort of setter/getter pattern. Though I mostly don't recommend it unless you know what you are doing, because React won't re-render if the variable changes and because of that you need to be sure the variable is set before it is used.
You should have something like the example below in order for it to work the way you want. You can find an example of it working on this Codesandbox.
export let MY_VARIABLE = "";
export const setMyVariable = value => (MY_VARIABLE = value);
PS: I've added some console.log to the code in order for you to see how the import/get/set behaves.
After digging more into this I found that es6 module spec states:
When import your module it gets loaded => parsed => evaluated and cached (singleton). It also says that when you import modules its value is passed by reference (aka assignment). I didn't find anything mentioning when or how es6 modules are unloaded from that cache.
So that means, when you import a module once, it is there for as long as the program is running, and all modules access its values directly.
reference
https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/
https://medium.com/#mivanichok/understanding-es6-modules-in-depth-article-b49612926e39
You can create an config.js inside src folder and write the your constant variable like
//config.js
module.exports = {
CONST_VAR : 'const value',
}
import config.js in your component and use it
The following component displays some static data.
What is the idiomatic way to store static data that is local to components?
Is it fine as I have done to store the data in a module scoped variable called data?
Or should I do something else like perhaps creating a ./data.json file that I import?
import * as React from 'react';
import SectionContainer, {
InnerSectionContainer,
FlexContainer,
FlexItem
} from '../../ui/SectionContainer';
import ScaledImage from '../../ui/ScaledImage';
import { SectionHeading } from '../../ui/Headings';
const data = {
title: 'Trusted by',
imageSrcs: [
require('../../../images/logo1.png'),
require('../../../images/logo2.png'),
require('../../../images/logo3.png')
]
};
const Logos = () => {
const logoItems = data.imageSrcs.map((imageSrc, index) => {
return (
<FlexItem key={index}>
<ScaledImage src={imageSrc} />
</FlexItem>
);
});
return (
<SectionContainer>
<InnerSectionContainer>
<SectionHeading>
{data.title}
</SectionHeading>
<FlexContainer>
{logoItems}
</FlexContainer>
</InnerSectionContainer>
</SectionContainer>
);
};
export default Logos;
I usually have one or more consts.js file(s) on my codebase.
If it's static, needs to be on the frontend and is used to just ONE component, I just put it along with the component's own file.
If it's being used by a few components under the same component structure (say, a component that solves a particular problem, that has some sub-components, but the static information is not relevant to any other component outside of this scope), I'd create a consts.js file on that component tree.
And if it's something that's going to be used by everyone, say, a set of style colors that reactjs is going to need for some reason, just create a global consts.js file and import as needed.
That being said, it's just how I use it. You can create your own approach. Try, experiment, and use what seems to work best for you.
EDIT:
I just saw this part of your code:
const data = {
title: 'Trusted by',
imageSrcs: [
require('../../../images/logo1.png'),
require('../../../images/logo2.png'),
require('../../../images/logo3.png')
]
};
This approach will work on a javascript file type like consts.js, like I mentioned above, but NOT on a json file, since you can't use require inside them (all data is static. there is no logic or imports).
You should tend towards declaring static data as close as possible to where it will be used. In other words, in the smallest scope that makes it visible to the code that needs it.
In javascript, then, if the data is needed:
by one function, declare it in the function
by one file, declare in the file
by the methods of an object/class/component, declare as a static or instance property in the class
by users of a class or object declaration, then also as a class static
by several components of a package, then in utils.js (or similar) file
by many classes/components, declare and export from a file in a "common" or "library" package
I wouldn't quibble about how you structured your example; it's perfectly fine. Putting that data into its own .js file is unnecessary unless you know for sure it will be used by at least one other component. Even then, if that other component also needs your Logos component, then exporting from where it is now would be fine.
When deciding about how to structure things, I think the first consideration should be: how can I make it easiest, for a future reader of my code, to figure out what I have done.
I am reading through the internet trying to find some performance resolute or preferred method of declaring static data, variables in react so I would like to hear your opinion.
This goes for react stateless and class components.
Let’s say I have an array with colors that I want to use somewhere inside React return().
const colors = ["red, "green", "blue"];
1) Declare it inside of render()
I suppose this is not preferred, snce it will be recreated on every render.
2) Declare it in constructor as a global variable
this.colors = ["red, "green", "blue"];
Nice, but maybe not preferred in some cases to have global variables.
3) Declare it as a return of function placed inside React component but outside of render(). We call the function from React return()
4) I think I saw somewhere using defaultProps.
Is there a best practice?
Few common approaches are to
declare it above a class or in beginning of a file after imports
if its a file specific constants.
const CONST1 = [0,1,2,];
class xxx extends yy {
....
}
or you can keep it in seperate file and import it when its common to many places.
something like
a json file
file a.json
{
"color": "red"
}
usage b.js
import constant from 'constants/a.json';
console.log(constant.color);
or even in global.color = 'red' which i would not advice to use
For class components, I've used the approach of declaring static variables on the class recently.
import React from 'react';
class Example extends React.Component {
// never changes, but may be used in other places, such as parent components
static displayName = 'Example';
state = {
someStateData: 'World'
};
render() {
const { someStateData } = this.state;
// do work
return <p>Hello {someStateData}</p>;
}
}
export default Example;
Well, I think it depends on your needs, most cases 2 and 3 might be enough. I've seen several proyect sources (such as create-react-app, react-native-maps, for instance) and they all handle this consts and "Global resources" in a same way:
They put them in separated files, and they import them as modules in every file where they are needed. I've used this approach and I can tell you is a really good and common practice
I want to bring up this because it's a really good thing:
https://www.npmjs.com/package/reactn
ReactN is a extension of React that includes global state management. It treats global state as if it were built into React itself -- without the boilerplate of third party libraries.
You can use [ global, setGlobal ] = useGlobal() to access the entire global state object.
It works in class, function, and non-react files.
I use it since 2 or 3 years ago, without a glitch.
I know that, given the OP's question, this is overkill. But anyway, I wanted to draw attention into this excellent tool.
If you have a parent component file that already imports React, why does any of its rendered children files also need to import React? Is it just a safety measure in case those children are ever rendered somewhere else where React has not been imported yet?
In nodejs each file is a module, that has its own scope of variables. When you import variable into file (React for example) you add this variable to the module scope, but not to the global scope.
In case of webpack you can use providePlugin to easily make React variable global:
new webpack.ProvidePlugin({
React: 'react' // ReactJS module name in node_modules folder
})
After that you are able to skip importing React variable in all of your modules. Webpack will do it itself where needed.
If you use JSX and babel, you have to import React in every file because babel-preset-react will transpile your JSX code into React.createElement() calls, so this code
const Foo = () => <h1>Test</h1>;
will be
var Foo = function Foo() {
return React.createElement(
"h1",
null,
"Test"
);
};
DEMO: Babel REPL
This is the reason why React should be accessible in the proper scope and one way to do it, is to import React in a file.
The root of the question is one of dependency management -- how do I, the author, describe and obtain external dependencies I need in my "thing" (application/component/module) for it to do its job?
JavaScript benefits (or suffers) from having a global namespace in which dependencies can be injected. While this can often simplify dependency management in the short term (e.g. you can ignore it and expect everything you need to be available in the global namespace), it can often cause issues as an application grows and changes. For example, given an application with multiple contributors, one might decide to change a part of the application to no longer use a particular dependency and therefore remove it. If another part of the application needed it but that dependency wasn't formally declared anywhere, it could be easily missed.
To do dependency management well, each discrete "thing" should describe its dependencies independent of any other "thing" such that it can be safely used in any given context. This ensures that each "thing" has exactly what it needs no matter how it is used and what its "parent" (the code importing the "thing") has.
An alternative to this approach is dependency injection. In this model, the "thing" provides an interface for passing the dependencies into the "thing" from the consumer. There are flexibility and testability advantages to this which are out of scope of your question. :)
// export a function that expects the React and PropTypes
// dependencies to be injected as parameters and returns
// the component rather than importing the dependencies
// and exporting the component
export default (React, PropTypes) => {
return class extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired
}
render () {
return (
<div />
);
}
};
};
All of that to say, it is somewhat a "safety measure" to have each "thing" import its own dependencies. It lets you safely refactor your code without the cognitive overhead of remembering what is providing those dependencies.
The reason is to avoid unnecessary compiled a JavaScript code that you don't have to compile jsx. For example, you have a file that have a function to do adding, or whatever function that doesn't need to compile jsx, then you don't put import React from 'react' on the top of that file. This will save you compile time.