React i18next useTranslation Hook in helper method - javascript

I'm using React with react-i18next
My index.tsx File contains some components and I can use the Translation function there
index.js
import React, { Suspense } from 'react'
import ReactDOM from 'react-dom';
import * as utils from './Utils';
import './i18n';
import { useTranslation, withTranslation, Trans } from 'react-i18next';
...
const { t, i18n } = useTranslation();
//I can use the translate function here
t("title");
//call a util function
utils.helperFunction(...);
...
Everything works fine here.
I now created some helper functions in an additional file
Utils.tsx
...
import { useTranslation, withTranslation, Trans } from 'react-i18next';
...
export function helperFunction(props: any){
//do stuff
//now I need some translation here - but useTranslation is not working?
const { t, i18n } = useTranslation();
t("needTranslation");
}
How can I use the same translation logic inside my helper function? (without always passing the t function to the helper-method)
Or is the usage of the hook the wrong approach here?
The following error occurs
React Hook "useTranslation" is called in function "helperFunction" which is neither a React function component or a custom React Hook function react-hooks/rules-of-hooks

I fixed my issue by not using the useTranslation hook anymore
Instead I moved the i18n initalizitation to a file (i18n.tsx - exports i18n)
and import and use it in my Utils class
My Utils.tsx file now looks like this
Utils.tsx
...
import i18n from '../i18n';
...
export function helperFunction(props: any){
//do stuff
//use imported i18n and call the t() method
i18n.t("needTranslation");
}
i18n.tsx
import i18n from "i18next";
import Backend from 'i18next-xhr-backend';
import { initReactI18next } from 'react-i18next';
i18n
.use(Backend)
.use(initReactI18next) // passes i18n down to react-i18next
.init({
lng: "es",
fallbackLng: 'en',
backend: {
loadPath: '/static/locales/{{lng}}/{{ns}}.json'
},
interpolation: {
escapeValue: false
}
});
export default i18n;

Or is the usage of the hook the wrong approach here?
I would say that because useTranslation is a hook and if you try to use it in the helper function, it won't allow you to do so and you'd have to return a react function component or custom React Hook function as the message says

It looks like you forgot a quote t("needTranslation); -> t("needTranslation");
After I ran your code I see why your code isn't working. If you want to extract component logic into reusable functions, then you should make a custom hook. For more info look at the docs.
import React from "react";
import ReactDOM from "react-dom";
import i18n from "i18next";
import "./i18n.js";
import { useTranslation, initReactI18next } from "react-i18next";
i18n
.use(initReactI18next)
.init({
resources: {
en: {
translation: {
title: "Hello world",
subtitle: "Hello stackoverflow"
}
}
},
lng: "en",
fallbackLng: "en",
interpolation: {
escapeValue: false
}
});
function App() {
const { t } = useTranslation();
useHelperFunction();
return <h1>{t("title")}</h1>;
}
// you need to turn the helper function into a hook
function useHelperFunction() {
const { t } = useTranslation();
return <h2>{t("subtitle")}</h2>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Related

No QueryClient set, use QueryClientProvider to set one

I was trying to react query for the first time then I got this at the start of my React app.
import React from 'react'
import { useQuery } from "react-query";
const fetchPanets = async () => {
const result = await fetch('https://swapi.dev/api/people')
return result.json()
}
const Planets = () => {
const { data, status } = useQuery('Planets', fetchPanets)
console.log("data", data, "status", status)
return (
<div>
<h2>Planets</h2>
</div>
)
}
export default Planets
As the error suggests, you need to wrap your application in a QueryClientProvider. This is on the first page of the docs:
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
While this is most commonly caused by not having your application wrapped in a <QueryClientProvider>, in my case it happened because I was importing some shared components, which ended up with a different context. You can fix this by setting the contextSharing option to true
That would look like:
import { QueryClient, QueryClientProvider } from 'react-query'
const queryClient = new QueryClient()
function App() {
return <QueryClientProvider client={queryClient} contextSharing={true}>...</QueryClientProvider>
}
From the docs: (https://react-query.tanstack.com/reference/QueryClientProvider)
contextSharing: boolean (defaults to false)
Set this to true to enable context sharing, which will share the first and at least one instance of the context across the window to ensure that if React Query is used across different bundles or microfrontends they will all use the same instance of context, regardless of module scoping.
Just make changes like below it will work fine
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
ReactDOM.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>,
document.getElementById('root')
);
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
const queryClient = new QueryClient();
const fetchPanets = async () => {
const result = await fetch('https://swapi.dev/api/people')
return result.json()
}
const Planets = () => {
const { data, status } = useQuery('Planets', fetchPanets)
console.log("data", data, "status", status)
return (
<div>
<h2>Planets</h2>
</div>
);
}
export default function Wraped(){
return(<QueryClientProvider client={queryClient}>
<Planets/>
</QueryClientProvider>
);
}
Single SPA (micro-frontend) - React Query v3.34.15
I was getting this error while trying to integrate a sigle-spa react parcel into the root application.
I used craco-plugin-single-spa-application for the building of a CRA app as a way to adapt it for a parcel. In the entry config I was pointing to my single-spa-react config.
// craco.config.js
const singleSpaApplicationPlugin = require('craco-plugin-single-spa-application')
module.exports = {
plugins: [
{
plugin: singleSpaApplicationPlugin,
options: {
orgName: 'uh-platform',
projectName: 'hosting',
entry: 'src/config/single-spa-index.cf.js',
orgPackagesAsExternal: false,
reactPackagesAsExternal: true,
externals: [],
minimize: false
}
}
]
}
In the single-spa-index.cf.js file I had the following configs.
import React from 'react'
import ReactDOM from 'react-dom'
import singleSpaReact from 'single-spa-react'
import App from '../App'
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary() {
return <div>Ocorreu um erro desconhecido!</div>
}
})
export const { bootstrap, mount, unmount } = lifecycles
After reading a bunch of forums and the react-query documentation, the only thing that I figured out I needed to change was pass in the QueryClientProvider the prop contextSharing as true. After had did this change, ran the building and access the route that opens my parcel. I got the same error.
import React from 'react'
import ReactDOM from 'react-dom'
import { QueryClient, QueryClientProvider } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
import App from './App'
const queryClient = new QueryClient()
const isDevelopmentEnv = process.env.NODE_ENV === 'development'
if (isDevelopmentEnv) {
import('./config/msw/worker').then(({ worker }) => worker.start())
}
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider contextSharing={true} client={queryClient}>
<App />
{isDevelopmentEnv && <ReactQueryDevtools initialIsOpen={false} />}
</QueryClientProvider>
</React.StrictMode>,
document.getElementById('root')
)
But, how do I solved that. Well, it was was simple. I couldn't even imagine why it was working locally. But not after building and integration.
The problem was because I put the React Query Provider inside the index o the application and in my single-spa-index.cf.js I was importing import App from '../App' which really wasn't wrapped by the provider. Once I also was importing App in the application index, where It was wrapped making It works locally. 😒😒
So after figure that out, my code was like that:
CODE AFTER SOLUTION
// craco.config.js
const singleSpaApplicationPlugin = require('craco-plugin-single-spa-application')
module.exports = {
plugins: [
{
plugin: singleSpaApplicationPlugin,
options: {
orgName: 'uh-platform',
projectName: 'hosting',
entry: 'src/config/single-spa-index.cf.js',
orgPackagesAsExternal: false,
reactPackagesAsExternal: true,
externals: [],
minimize: false
}
}
]
}
// src/config/single-spa-index.cf.js
import React from 'react'
import ReactDOM from 'react-dom'
import singleSpaReact from 'single-spa-react'
import App from '../App'
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary() {
return <div>Ocorreu um erro desconhecido!</div>
}
})
export const { bootstrap, mount, unmount } = lifecycles
// App.tsx
import { QueryClient, QueryClientProvider } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
import { config } from 'config/react-query'
import Routes from 'routes'
import GlobalStyles from 'styles/global'
import * as S from './styles/shared'
const queryClient = new QueryClient(config)
const isDevelopmentEnv = process.env.NODE_ENV === 'development'
if (isDevelopmentEnv) {
import('./config/msw/worker').then(({ worker }) => worker.start())
}
function App() {
return (
<QueryClientProvider contextSharing={true} client={queryClient}>
<S.PanelWrapper>
<Routes />
<GlobalStyles />
</S.PanelWrapper>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
export default App
// index.tsx
import { StrictMode } from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
document.getElementById('root')
)
Well, it was long but I hope it helps someone that's undergoing for the same problem as mine. πŸ™ŒπŸ™ŒπŸ™Œ
I was trying to fix the same thing:
I followed the React Query docs
and used the concept of Higher Order Component
See if it helps:
import React from 'react';
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import Planet from './Planet';
const queryClient = new QueryClient();
const fetchPlanets = async () => {
const res = await fetch('http://swapi.dev/api/planets/');
return res.json();
}
const Planets = () => {
const { data, status } = useQuery('planets', fetchPlanets);
return (
<div>
<h2>Planets</h2>
{ status === 'loading' && (<div>Loading data...</div>)}
{ status === 'error' && (<div>Error fetching data</div>)}
{
status === 'success' && (
data.results.map(planet =>
<Planet
key={planet.name}
planet={planet}
/>
)
)
}
</div>
)
}
// Higher order function
const hof = (WrappedComponent) => {
// Its job is to return a react component warpping the baby component
return (props) => (
<QueryClientProvider client={queryClient}>
<WrappedComponent {...props} />
</QueryClientProvider>
);
};
export default hof(Planets);
In my case I was importtng from 'react-query' in one place and '#tanstack/react-query' in another.
I got that error when trying to add the react-query devtools.
The problem was I was installing it wrongly according my version, I was using react-query v3.
WRONG FOR react-query V3 (GOOD FOR V4)
import { ReactQueryDevtools } from '#tanstack/react-query-devtools';
OK FOR react-query V3
import { ReactQueryDevtools } from 'react-query/devtools';
In my case I accidentally used two different versions of react-query in my modules.
In my case
Error import { QueryClient, QueryClientProvider } from "#tanstack/react-query";
Solution import { QueryClient, QueryClientProvider } from "react-query";
remove it #tanstack/
Just be careful when upgrade from react-query v3 to #tanstack/react-query v4.
Ensure that you replace all imports as "react-query" to "#tanstack/react-query" and then run yarn remove the lib that you won't use anymore, otherwise you may accidentally import the unexpected one.
This happened to me and caused this error.

Problem to use VueI18n outside a component

I'm trying to use i18n outside a component I've found this solution https://github.com/dkfbasel/vuex-i18n/issues/16 telling to use Vue.i18n.translate('str'), but when I call this occurs an error Cannot read property 'translate' of undefined.
I'm using the following configuration
main.js
import i18n from './i18n/i18n';
new Vue({
router,
store,
i18n: i18n,
render: h => h(App)
}).$mount('#app')
i18n.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import i18nData from './i18nData'
Vue.use(VueI18n);
export default new VueI18n({
locale: 'en',
messages: i18nData,
});
i18nData.js
export default {
en: {
//my messages
}
}
Then I trying to use this
import Vue from 'vue';
Vue.i18n.translate('someMessage');
Can anyone help me?
To use i18n with Vue 3's composition API, but outside a component's setup(), you can access its translation API (such as the t function) on its global property.
E. g. in a file with unit-testable composition functions:
// i18n/index.js
import { createI18n } from 'vue-i18n'
import en from './en.json'
...
export default createI18n({
datetimeFormats: {en: {...}},
locale: 'en',
messages: { en }
})
// features/utils.js
//import { useI18n } from 'vue-i18n'
//const { t } = useI18n() // Uncaught SyntaxError: Must be called at the top of a `setup` function
import i18n from '../i18n'
const { t } = i18n.global
You should import i18n instead of Vue
import i18n from './i18n'
i18n.tc('someMessage')
You can use VueI18n outside components by importing i18n then, use "t" from i18n.global.
"t" doesn't need "$" and you can change Locale with i18n.global.locale.
import i18n from '../i18n';
const { t } = i18n.global;
i18n.global.locale = 'en-US'; // Change "Locale"
const data = {
name: t('name'), // "t" doesn't need "$"
description: t('description'), // "t" doesn't need "$"
};
I managed to make it work this way:
import router from '../router';
Translate a text:
let translatedMessage = router.app.$t('someMessage');
Get the current language:
let language = router.app.$i18n.locale;

Ant-Design CSS not loading properly

I am having an issue with rendering my Ant-Design CSS on the first-render using React.js. I have a very basic page, that is just rendering a button.
import React from 'react';
import { Button } from 'antd';
const LoginPage = () => {
return (
<div>
<Button type="primary">Button</Button>
</div>
)
};
export default LoginPage;
I am trying to import the Ant-Design modules through the config-overrides.js file, as per the documentation:
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
Here is my index.js file:
import { Provider } from 'react-redux';
import thunkMiddleware from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
import 'normalize.css';
import App from './components/App/App';
import reducers from './reducers';
import { fetchUser } from './actions';
import * as serviceWorker from './serviceWorker';
const store = createStore(reducers, applyMiddleware(thunkMiddleware));
store.dispatch(fetchUser()).then(() => console.log(store.getState()));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
And here is my App.js and App.css for more reference:
import React, { Component } from 'react';
import LoginPage from '../LoginPage/LoginPage';
import DashboardPage from '../DashboardPage/DashboardPage';
import { Spin } from 'antd';
import './App.css';
import { connect } from 'react-redux';
class App extends Component {
constructor(props){
super(props);
this.state = {
loggedIn: false
}
}
componentDidMount(){
this.setState({loggedIn: true });
}
render() {
return <LoginPage/>
}
}
const mapStateToProps = (state) => {
console.log(state);
return {
user: state.currUser
};
};
export default connect(mapStateToProps)(App);
#import '~antd/dist/antd.css';
However, on the first render it will only show a normal button, before fixing itself a second later. Here are two images that show the problem:
And here is the page after the second render:
Just import this file in your jsx file or js file:
If you import it in App.jsx file once then no need to import in other files
import "antd/dist/antd.css";
Add #import '~antd/dist/antd.css';
To the top of either/both of App.css and Index.css.
Hope that helps! πŸ‘.
P.S - If you are using a single component for instance lets say Input, then import only that part.
import 'antd/es/input/style/index.css';
this below import is used my react project with create-react-app cli
import 'antd/dist/antd.css',
use this import to your root component.
Do NOT import the root styles from ant, as they contain some global styles, unfortunately, and will affect yours styling anyway, this was addressed a lot of times to them, but still I found the only solution is to directly import the components styling like this (replace select with your component):
import "antd/lib/select/style/index.css";
In your index.js file, you can import ant style files:
import 'antd/dist/antd.css';
As of this issue, please use the minified version.
import "antd/dist/antd.min.css";

TypeError: undefined is not an object (evaluating 'store.getState')

I'm following the Let’s Build: Cryptocurrency Native Mobile App With React Native + Redux tutorial.
When I create my store in App.js, the app works fine
import { createStore, applyMiddleware, compose } from 'redux';
import devTools from 'remote-redux-devtools';
import React, { Component } from 'react';
import { Platform, View } from 'react-native';
import { Provider } from 'react-redux';
import promise from 'redux-promise';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import { Header, CryptoContainer } from './src/components';
import rootReducer from './src/reducers';
const middleware = applyMiddleware(thunk, promise, logger);
const Store = createStore(rootReducer, compose(middleware, devTools({
name: Platform.OS,
hostname: 'localhost',
port: 5678
}), ));
export default class App extends Component {
render() {
return (
<Provider store={Store}>
<View>
<Header />
<CryptoContainer />
</View>
</Provider>
);
}
}
but when I move the store logic to a new file ./src/Store.js,
import { Platform } from 'react-native';
import { createStore, applyMiddleware, compose } from 'redux';
import devTools from 'remote-redux-devtools';
import promise from 'redux-promise';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from './reducers';
const middleware = applyMiddleware(thunk, promise, logger);
const Store = createStore(rootReducer,compose(middleware,devTools({
name: Platform.OS,
hostname: 'localhost',
port: 5678
}),
)
);
export default Store;
and use it in App.js like
import React, { Component } from 'react';
import { View } from 'react-native';
import { Provider } from 'react-redux';
import { Header, CryptoContainer } from './src/components';
import { Store } from './src/Store';
export default class App extends Component {
render() {
return (
<Provider store={Store}>
<View>
<Header />
<CryptoContainer />
</View>
</Provider>
);
}
}
I get
TypeError: undefined is not an object (evaluating 'store.getState')
What's causing my build (expo start) to fail when I import Store.js?
It seems the import statement is not right. It should be:
import Store from './src/Store';
if you're importing a single named export
e.g where you've done export const MyComponent = () => {} you'd import it like import { MyComponent } from "./MyComponent"
if you're importing a default export e.g where you've done const MyComponent = () => {} export default MyComponent you'd import it like import MyDefaultComponent from "./MyDefaultExport"
I got this error because I was exporting the wrong component from my main App file.
I was exporting this:
import React from 'react'
import { Provider } from 'react-redux'
import { createAppContainer } from 'react-navigation'
import Navigator from './src/components/Navigator'
import { store } from './src/store'
const App = createAppContainer(Navigator);
const Wrapped = props => (
<Provider store={store}>
<App />
</Provider>
)
export default Provider; // wrong!
That last line should be:
export default Wrapped; // right!
The answer from Itunu Adekoya shows that you can decide how you want to export / import, and in this case about personal preference, as there isn't a perf difference.
In the case where you have a lot of exports from a file, and perhaps some are unrelated or won't all be used together, it is better to export them individual as consts and then in other file only import what you need via import { } format, this will be sure to only include relevant imprts
in my case its casing & named import issue. imported as
import store from './Redux/Store'
it should be
import {Store} from './Redux/Store'

Tips when testing a connected Redux Component

I've pasted my Component below which is very, very basic. When the Component is mounted, it will basically call the fetchMessage Action, which returns a message from an API. That message will in turn get set as state.feature.message in the mapStateToProps function.
I'm at a complete loss on where to begin testing this Component. I know that I want to test that:
1) The Feature Component is rendered
2) The fetchMessage function in props is called
3) It displays or has the correct message property when rendered using that
I've tried setting my test file up as you can see below, but I just end up with repeated error after error with everything that I try.
Could anyone point me in the right direction with what I'm doing wrong?
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from './actions';
class Feature extends Component {
static propTypes = {
fetchMessage: PropTypes.func.isRequired,
message: PropTypes.string
}
componentWillMount() {
this.props.fetchMessage();
}
render() {
return (
<div>{this.props.message}</div>
);
}
}
function mapStateToProps(state) {
return { message: state.feature.message };
}
export default connect(mapStateToProps, actions)(Feature);
Test file:
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import expect from 'expect';
import { shallow, render, mount } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
import Feature from '../index';
const mockStore = configureStore([thunk]);
describe('<Feature />', () => {
let store;
beforeEach(() => {
store = mockStore({
feature: {
message: 'This is the message'
}
});
});
it('renders a <Feature /> component and calls fetchMessage', () => {
const props = {
fetchMessage: sinon.spy()
};
const wrapper = mount(
<Provider store={store}>
<Feature {...props} />
</Provider>
);
expect(wrapper.find('Feature').length).toEqual(1);
expect(props.fetchMessage.calledOnce).toEqual(true);
});
});
You can use shallow() instead of mount() to test your component. The shallow() method calls the componentWillMount() life-cycle method so there is no reason to use mount(). (Disclaimer: I am not quite well at mount() yet.)
For connected components, you can pass a store object like this:
<connectedFeature {...props} store={store} />
And you should call shallow() method twice to make it work for connected components:
const wrapper = shallow(<connectedFeature {...props} store={store} />).shallow()
Testing Connected React Components
Use separate exports for the connected and unconnected versions of the components.
Export the unconnected component as a named export and the connected as a default export.
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from './actions';
// export the unwrapped component as a named export
export class Feature extends Component {
static propTypes = {
fetchMessage: PropTypes.func.isRequired,
message: PropTypes.string
}
componentWillMount() {
this.props.fetchMessage();
}
render() {
return (
<div>{this.props.message}</div>
);
}
}
function mapStateToProps(state) {
return { message: state.feature.message };
}
// export the wrapped component as a default export
export default connect(mapStateToProps, actions)(Feature);
Remember connected components must be wrapped in a Provider component as shown below.
Whereas unconnected components can be tested in isolation as they do not need to know about the Redux store.
Test file:
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import expect from 'expect';
import { shallow, render, mount } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
// import both the wrapped and unwrapped versions of the component
import ConnectedFeature, { feature as UnconnectedFeature } from '../index';
const mockStore = configureStore([thunk]);
describe('<Feature />', () => {
let store;
beforeEach(() => {
store = mockStore({
feature: {
message: 'This is the message'
}
});
});
it('renders a <Feature /> component and calls fetchMessage', () => {
const props = {
fetchMessage: sinon.spy()
};
const wrapper = mount(
<Provider store={store}>
<connectedFeature {...props} />
</Provider>
);
expect(wrapper.find('Feature').length).toEqual(1);
expect(props.fetchMessage.calledOnce).toEqual(true);
});
});

Categories