I'm building a JHipster Monolithic application with React. In the Backend, I can use the application.yml / ApplicationProperties.java to add properties (such as API keys) which may change between environments (Dev, Prod etc).
My question is, can I do the same thing on the React front-end? This is a Spring application so the same application.yml and ApplicationProperties.java are in place. Does anyone have a code example of surfacing custom properties to the UI?
I have read the answer from this post (JHipster React Front End (Gateway) Application Properties) but it did not help me because it is a monolithic application in my case.
The solution from the other post works for a monolith. For a full solution, see below:
First, make sure you have configured the config you want exposed. Also configure it in ApplicationProperties.java (or set ignoreUnknownFields to false):
application:
my-value: 'TestValue'
Create a REST endpoint for exposing the config value to the client (modified from the other answer):
package com.mycompany.myapp.web.rest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Resource to return custom config
*/
#RestController
#RequestMapping("/api")
public class CustomConfigResource {
#Value("${application.my-value:}")
private String myValue;
#GetMapping("/custom-config")
public CustomConfigVM getCustomConfig() {
return new CustomConfigVM(myValue);
}
class CustomConfigVM {
private String myValue;
CustomConfigVM(String myValue) {
this.myValue = myValue;
}
public String getMyValue() {
return myValue;
}
public void setMyValue(String myValue) {
this.myValue = myValue;
}
}
}
Create a reducer to fetch and store the information:
import axios from 'axios';
import { SUCCESS } from 'app/shared/reducers/action-type.util';
export const ACTION_TYPES = {
GET_CONFIG: 'customConfig/GET_CONFIG',
};
const initialState = {
myValue: '',
};
export type CustomConfigState = Readonly<typeof initialState>;
export default (state: CustomConfigState = initialState, action): CustomConfigState => {
switch (action.type) {
case SUCCESS(ACTION_TYPES.GET_CONFIG): {
const { data } = action.payload;
return {
...state,
myValue: data['myValue'],
};
}
default:
return state;
}
};
export const getConfig = () => ({
type: ACTION_TYPES.GET_CONFIG,
payload: axios.get('api/custom-config'),
});
Finally, call the reducer's getConfig method from a component, for example in App.tsx:
import { getConfig } from 'app/shared/reducers/custom-config';
...
useEffect(() => {
props.getConfig();
}, []);
...
const mapDispatchToProps = { getConfig };
...
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 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 {
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
I'm hiding tabs in Ionic 2 for certain #Pages (an Ionic 2 decorator) using a simple TabsProvider:
tabs.ts
import { Injectable } from 'angular2/core';
import { BehaviorSubject } from 'rxjs';
#Injectable()
export class TabsProvider {
currentState = new BehaviorSubject<boolean>(true);
public showTabs(){
this.currentState.next(true);
}
public hideTabs(){
this.currentState.next(false);
}
}
The tabs component subscribes to currentState, and TabsProvider is injected into various pages as below:
sample-page.ts:
import {Page} from 'ionic-angular';
import { TabsProvider } from './tabs';
#Page({
...
})
export class SamplePage {
tabsProvider: TabsProvider;
constructor(tabsProvider: TabsProvider) {
this.tabsProvider = tabsProvider;
}
onPageWillEnter(){
this.tabsProvider.hideTabs();
}
onPageWillLeave(){
this.tabsProvider.showTabs();
}
}
This code is practically all boilerplate, and would be much cleaner if I could define this functionality in a decorator (or annotation), e.g.:
import { Page } from 'ionic-angular';
import { hideTabs } from './tabs';
#hideTabs()
#Page({
...
})
export class BuyPage {
}
But I'm having trouble determining how to inject TabsProvider and add the onPageWillEnter and onPageWillLeave methods to SamplePage.
Can a decorator (or annotation) somehow inject additional Angular providers?
The farthest I've gotten so far:
in tabs.ts:
export function hideTabs() {
return function(cls: any) {
cls.prototype.onPageWillEnter = function() {
this.tabsProvider.hideTabs();
};
cls.prototype.onPageWillLeave = function() {
this.tabsProvider.showTabs();
};
return cls;
}
}
This gets us part of what we're looking for, but it's still necessary to import and inject TabsProvider as a specific instance member:
sample-page.ts
import {Page, Events} from 'ionic-angular';
import { hideTabs, TabsProvider } from './tabs';
#hideTabs()
#Page({
...
})
export class SamplePage {
constructor(public tabsProvider: TabsProvider) {
}
}
Is it possible to fully abstract this into #hideTabs()?
Edit:
Relevant parts of the tabs component (for anyone interested in implementing) pages/tabs/tabs.ts:
import { Page } from 'ionic-angular';
import { TabsProvider } from './tabs';
#Page({
...
})
export class TabsPage {
...
currentState: boolean;
constructor(TabsProvider: TabsProvider) {
TabsProvider.currentState.subscribe((state: boolean) => {
this.currentState = state;
});
}
}
pages/tabs/tabs.html:
<div [ngClass]="{'hide-tabs': !currentState}">
<ion-tabs>
...
</ion-tabs>
</div>
pages/tabs/tabs.scss:
.hide-tabs ion-tabbar-section {
display: none;
}
I have just had the same problem, and this is the solution I came up with. The general approach is to:
Use Reflect.getOwnMetadata to read the existing dependencies from the class.
Add to the list of dependencies
Create a new constructor which wraps the old constructor and deals with these dependencies.
tabs.ts:
import TabsProvider from "./tabs";
export function hideTabs() {
return function(cls: Function) : any {
// Save existing onPageWillEnter and onPageWillLeave inplementations
let enter = cls.prototype.onPageWillEnter || function () {};
let leave = cls.prototype.onPageWillLeave || function () {};
// Create new constructor for class
const newCls = function (tabsProvider, ...args) {
this.__hideTabs__tabsProvider = tabsProvider;
return cls.apply(this, args);
}
// Copy prototype to new constructor
newCls.prototype = constructor.prototype;
// Copy metadata to new constructor
let metadatakeys = Reflect.getMetadataKeys(cls);
metadatakeys.forEach(function (key) {
Reflect.defineMetadata(key, Reflect.getOwnMetadata(key, cls), newCls)
});
// Read dependencies list from 'cls', add our own dependency, and write list to 'newCls'
let dependencies = Reflect.getOwnMetadata('design:paramtypes', cls);
dependencies = [TabsProvider].concat(dependencies)
Reflect.defineMetadata('design:paramtypes', params, newCls);
// Define new onPageWillEnter and onPageWillLeave implementation which implement tab show/hide functionality
// and also call the old implementation saved above (if they exist)
newCls.prototype.onPageWillEnter = function() {
this.tabsProvider.hideTabs();
enter.apply(this, arguments);
};
newCls.prototype.onPageWillLeave = function() {
this.tabsProvider.showTabs();
leave.apply(this, arguments);
};
return newCls;
}
}
I would also recommend moving the #hideTabs decorator below the #Page decorator