I am trying to create a store using typescript something like this:
class GetDataStore {
public getData() {
const url = "http://localhost:50774/api/Data";
fetch(url).then(res => {
if (res.ok) {
return res.json();
} else {
throw new Error("Something went wrong in retrieving data");
}
}).then(resData => { return resData })
.catch(error => { console.log(error) });
}
}
export default GetDataStore;
I am trying to instantiate an instance of this store as :
import { GetDataStore } from"../../stores/GetDataStore";
..
..
export class View extends React.Component<RouteComponentProps<{}>> {
private store = new GetDataStore();
public componentWillMount() {
const data = store.getData();
}
}
But I am getting an error:
_GetDataStore.GetDataStore is not a constructor at new View
What am I doing wrong
You are exporting the GetDataStore class as a default export but importing it as a named export.
You can remove the brackets from around the import:
import GetDataStore from"../../stores/GetDataStore";
RTFM should work here as well: export guide from MDN
Your import statement is a named import, however you are using a default export.
Solution 1 (using default export):
Try replacing:
import { GetDataStore } from"../../stores/GetDataStore";
with:
import GetDataStore from "../../stores/GetDataStore";
Solution 2 (using named export):
Remove:
export default GetDataStore;
And replace:
class GetDataStore {
with:
export class GetDataStore {
Related
I'm trying to get mobx to work with my react hooks and I am starting off with a very simple example but it still does not work. I must have missed something but I have been trying for hours to find what that might be.
Here is my store:
import { observable, decorate } from 'mobx';
import { createContext } from 'react';
import { IUser } from '../models/IUser';
export class UserStore {
public user: IUser;
public getUser() {
myApi
.get(myUrl)
.then(response => {
this.user = response;
});
}
}
decorate(UserStore, {
user: observable,
});
export default createContext(new UserStore());
And here is the component printing the username of the user:
import React, { useContext } from 'react';
import { observer } from 'mobx-react-lite';
import UserStore from '../../stores/UserStore';
const MyComponent = observer(() => {
const userStore = useContext(UserStore);
return (
<div>
{userStore.user && userStore.user.userName}
</div>
);
});
export default MyComponent;
And to fire the api call, App does the following:
const App: React.FC = () => {
const userStore = useContext(UserStore);
useEffect(() => {
userStore.getUser();
}, []);
return(...);
};
export default App;
I can see that
1: App performs the call
2: The user is set to the response of the call
3: If I console log the userStore.user.userName after it has been set, it looks just fine.
The quirk is that the label in MyComponent never gets updated. Why?
I believe the bug is in decorate.
Changing the behavior from using the decorate syntax, to wrapping the FC with observable works just fine, like this:
const UserStore = observable({
user: {}
});
Another thing that also works is to have your stores as classes and using the old decorator syntax like this:
export class UserStore {
#observable public user: IUser;
}
This requires you to add the following to .babelrc:
["#babel/plugin-proposal-decorators", { "legacy": true }]
Usual way to map state and actions in React/Redux looks something like this, so mapping functions are placed separately from component code:
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import myAction from 'actions/request';
class MyComponent extends Component {
/* BODY */
}
function mapStateToProps(state) {
return {
myComponentProp: state.myReducer.myReducerProp
};
}
function mapDispatchToProps(dispatch) {
return {
myComponentPropAction: bindActionCreators(myAction, dispatch),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
The only described way to map state and actions I have found in Vue looks like this
import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState('myReducer', {
myComponentProp: (state) => state.myReducerProp,
}),
...{
/* aditional manipulations with this.myComponentProp */
}
},
methods: {
...mapActions('myReducer', [
'myReducerAction'
]),
...{
myEventHandler: function() {
/* checke conditions before action call */
this.myReducerAction();
},
}
}
}
Because of the number of spreading, the code looks fuzzy, so the question is:
Is there a way to move mapState and mapActions outside component like in react/redux usual approach.
Thanks for help!
Okay so along with typescript support they also added in a vue-class-component decorator which could be used to achieve what your after. The link to the repository for this can be found here but I would suggest instead creating a new project via the CLI and going from there as it was added in v3 Vue Class Component Github Repository.
<script>
function Getter (getterType) {
return createDecorator((options, key) => {
if (!options.computed) options.computed = {}
options.computed[key] = function () {
return this.$store.getters[getterType]
}
})
}
import Vue from 'vue'
import Component from 'vue-class-component'
#Component({
props: {
propMessage: String
}
})
export default class App extends Vue {
#Getter('foo') bar
#Setter('psu') psi
// computed
get computedMsg () {
return 'computed ' + this.msg
}
// method
greet () {
alert('greeting: ' + this.msg)
}
}
</script>
As you can see were calling in our getters and setters using a function here which is less than optimal but is closer to a succulent answer. Now in comes the vuex-class-binding package which abstracts all of those murky functions: vuex class
import Vue from 'vue'
import Component from 'vue-class-component'
import {
State,
Getter,
Action,
Mutation,
namespace
} from 'vuex-class'
const someModule = namespace('path/to/module')
#Component
export class MyComp extends Vue {
#State('foo') stateFoo
#State(state => state.bar) stateBar
#Getter('foo') getterFoo
#Action('foo') actionFoo
#Mutation('foo') mutationFoo
#someModule.Getter('foo') moduleGetterFoo
// If the argument is omitted, use the property name
// for each state/getter/action/mutation type
#State foo
#Getter bar
#Action baz
#Mutation qux
created () {
this.stateFoo // -> store.state.foo
this.stateBar // -> store.state.bar
this.getterFoo // -> store.getters.foo
this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
this.moduleGetterFoo // -> store.getters['path/to/module/foo']
}
}
This is there example and it's really nice because were able to take a namespaced module and call all of it's getters and setters without any nasty custom functions and we can import all of that ready to use into a const like above. Now you'd have access to all of your modules functionality using just decorators. This is as close as it really gets to being able to assign your functionality into the component sadly, but it looks pretty nice once you've got it all setup. You can do this with or without TS I think but I've always done it in TS as it has the first class support for the vue class components which are still relatively new.
I'm trying to build a fetch method that can be shared to a bunch of Reader components through a higher order component. I believe I've built the HOC right, but I'm not 100% sure.
import React, { Component } from 'react';
import base from "./firebase";
export default (ChildComponent) => {
class GetPage extends Component<{},any> {
constructor(props: any) {
super(props);
this.state = {
text: "Hii"
};
}
public getPage(page: string) {
base
.fetch(page, { context: this, })
.then(data => this.setState({ text: data }));
console.log(this.state.text)
}
public render() {
return <ChildComponent getPage={this.getPage} text={...this.state.text} {...this.props}/>;
}
}
return GetPage;
};
You can see that I'm importing the HOC on the second line , but despite this, the 'Reader' component is throwing an error that 'getPage' is no where to be found.
import * as React from "react";
import GetPage from "./fetch";
class Reader extends React.Component<{},any>{
public componentWillMount() {
this.getPage('1A1');
}
public render() {
return <div{...getPage('1A1')}>{...this.state.text}</div>;
}
}
export default (GetPage(Reader));
Inside your Reader component instead of accessing this.getpage try with this.props.getpage
and I don't understand why you are doing with following:
<div{...getPage('1A1')}>
Currently working with Redux and was wondering if there was a way to require in multiple modules into a single file, which is then exported again as a single module?
For example, in my actions/bookmark.js I group all actions related to bookmarks accordingly:
module.exports = {
fetchBookmarkList: () => {
return {
type: 'FETCH_LIST'
}
},
fetchBookmark: () => {
return {
type: 'FETCH_BOOKMARK'
}
}
}
Then in my actions/index.js file, I require in all groups of actions (which will include bookmark actions as well as others). Then I would like to export the entire file as a single module.
Schematically I had something like this in mind (obviously this code does not work):
actions/index.js:
module.exports = {
require('./bookmark');
require('./tags');
}
The reason that I want to do this is so that I only have to import a single action file that contains all my actions (i.e. the actions/index.js file):
Example component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
class BookmarkList extends Component {
constructor(props) {
super(props);
this.props.fetchBookmarkList();
}
render() {
return (
<div></div>
);
}
}
export default connect(null, actions)(BookmarkList);
I see that you es6 modules syntax in components then, why not in your redux files?
export const fetchBookmarkList = () => {
return {
type: 'FETCH_LIST'
}
};
export const fetchBookmark = () => {
return {
type: 'FETCH_BOOKMARK'
}
}
Based on this reference I think it could be possible to re-export everything like this:
export * from './bookmark'
export * from './tags;
But haven't tried it and I could be wrong.
I would NOT recommend you to pass all the actions to mapDispatchToProps, only the actions needed by your component
But you could just use in actions/index.js:
Using the spread operator:
module.exports = {
...require('./bookmark');
...require('./tags');
}
And in ES6 syntax:
actions/index.js:
export * from './bookmark'
export * from './tags'
Currently I have the following code to expose react-intl to non-components, but it throws an error for intl as undefined.
I have created a separate component as 'CurrentLocale' and inject-intl to it. The exporting function t will use intl formatMessage from CurrentLocale context.
import React from 'react';
import {injectIntl} from 'react-intl';
import PropTypes from 'prop-types';
import { flow } from 'lodash';
class CurrentLocale extends React.Component {
constructor(props,context){
super();
console.log(context,props);
console.log(this.formatMessage);
const { intl } = this.context.intl;//this.props;
this.formatMessage = intl.formatMessage;
}
render() {
return false;
}
}
CurrentLocale.contextTypes={
intl:PropTypes.object,
};
injectIntl(CurrentLocale);
function intl() {
return new CurrentLocale();
}
function formatMessage(...args) {
return intl().formatMessage(...args);
}
const t = opts => {
const id = opts.id;
const type = opts.type;
const values = opts.values;
let t;
switch (type){
case 'message':
default:
t = formatMessage(id, values);
}
return t;
}
export default t;
t is called as in another plain javascript file as,
import t from './locale/t';
t( { type: 'message', id:'button.Next'});
Following is the error message.
Thanks in advance.
There's also another approach very simple I used for solving a similar problem: Provide access to the intl object for a non-component:
import { IntlProvider, addLocaleData } from 'react-intl';
import localeDataDE from 'react-intl/locale-data/de';
import localeDataEN from 'react-intl/locale-data/en';
import { formMessages } from '../../../store/i18n'; // I defined some messages here
import { Locale } from '../../../../utils'; //I set the locale fom here
addLocaleData([...localeDataEN, ...localeDataDE]);
const locale = Locale.setLocale(); //my own methods to retrieve locale
const messages = Locale.setMessages(); //getting messages from the json file.
const intlProvider = new IntlProvider({ locale, messages });
const { intl } = intlProvider.getChildContext();
export const SCHEMA = {
salutation: {
label: intl.formatMessage(formMessages.salutationLabel),
errormessages: {
required: intl.formatMessage(formMessages.salutationError),
},
},
academic_title_code: {
label: intl.formatMessage(formMessages.academicTitleLabel),
},
};
It's working like a charm!
UPDATE for v3.x
After migration to react-intl 3.x
import { createIntl, createIntlCache } from 'react-intl'
import { formMessages } from '../../../store/i18n'; // I defined some messages here
import { Locale } from '../../../../utils'; //I set the locale fom here
const locale = Locale.setLocale(); //my own methods to retrieve locale
const messages = Locale.setMessages(); //getting messages from the json file.
// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache();
const intl = createIntl({ locale, messages }, cache)
export const SCHEMA = {
salutation: {
label: intl.formatMessage(formMessages.salutationLabel),
errormessages: {
required: intl.formatMessage(formMessages.salutationError),
},
},
academic_title_code: {
label: intl.formatMessage(formMessages.academicTitleLabel),
},
};
There's a new way to do it pretty easily with createIntl, it returns an object that you can use outside React components. Here's an example from the documentation.
import {createIntl, createIntlCache, RawIntlProvider} from 'react-intl'
// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()
const intl = createIntl({
locale: 'fr-FR',
messages: {}
}, cache)
// Call imperatively
intl.formatNumber(20)
// Pass it to IntlProvider
<RawIntlProvider value={intl}>{foo}</RawIntlProvider>
I personally store the intl object in Redux store so I can access it everywhere in my app.
This line: const { intl } = this.context.intl; should be const { intl } = this.context;
Here is a reference post of someone doing almost the exact same thing as you are: https://github.com/yahoo/react-intl/issues/983#issuecomment-342314143
In the above the author is creating essentially a singleton that is exported instead of creating a new instance each time like you have above. This might be something you want to consider as well.
There's also another way solving a similar problem to used react-intl formatMessage for non-components.
Create a LocaleStore.js store file.
import _formatMessage from "format-message";
export default class LocaleStore {
formatMessage = (id, values) => {
if (!(id in this.messages)) {
console.warn("Id not found in intl list: " + id);
return id;
}
return _formatMessage(this.messages[id], values);
};
}
import LocaleStore your CombinedStores.js
import LocaleStore from "./stores/LocaleStore";
import en from "./translations/en";
import de from "./translations/de";
import Global from "./stores/global"
const locale = new LocaleStore("en", {
en,
de
});
export default {
global:new Global(locale)
}
now you can use this in your GlobalStore.js
class GlobalStore {
constructor(locale) {
this.locale = locale;
}
formatMessage=(message_is,formatLanguage="en")=> {
return this.locale.formatMessage(message_id, formatLanguage);
}
}
react-intl decorates your React.Component with wrapped component which is injected internationalized message dynamically so that the locale data is able to be loaded dynamically.
import { injectIntl } from 'react-intl';
class MyComponent extends Component {
render() {
const intl = this.props;
const title = intl.formatMessage({ id: 'title' });
return (<div>{title}</div>);
}
};
export default injectIntl(MyComponent);
It can be applied only in view layer such as React.Component.
react-intl can't be used in Vanilla JS. For example,
export default const rules = {
noSpace(value) {
if (value.includes(' ')) {
return 'Space is not allowed.';
}
}
};
One of alternative is react-intl-universal. It can be used not only in Component but also in Vanilla JS.
For example:
import intl from 'react-intl-universal';
export default const rules = {
noSpace(value) {
if (value.includes(' ')) {
return intl.get('no_space');
}
}
};
See react-intl-universal online example
If you can accept to use a function component I prefer to use the useIntl hook
https://reactjs.org/docs/components-and-props.html#function-and-class-components
I can then get values like this:
import { useIntl } from "react-intl";
const intl = useIntl()
intl.formatMessage({ id: 'myId' }),
https://formatjs.io/docs/react-intl/api/#useintl-hook