Is it possible to use the layout in the _app.jsx component with next-i18next? - react-hooks

To create a site, I use nextjs, when creating pages, I took the general layout with the header and footer into a separate hoc component and wrapped the page components in the file with it _app.jsx:
function App({ Component, ...rest }) {
const { store, props } = wrapper.useWrappedStore(rest)
return (
<Provider store={store}>
<Layout>
<Component {...props.pageProps} />
</Layout>
</Provider>
)
}
Everything worked fine until localization became a problem, after using the next-18next library for translations and adding serverSideTranslations, two errors began to appear on each page:
react-i18next:: You will need to pass in an i18next instance by using initReactI18next
frontend-node_1 | TypeError: Cannot read properties of undefined (reading 'label')
frontend-node_1 | at DropdownSwitcher (webpack-internal:///./src/components/header/translation/DropdownSwitcher.jsx:45:36)
frontend-node_1 | at renderWithHooks (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5658:16)
frontend-node_1 | at renderIndeterminateComponent (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5731:15)
frontend-node_1 | at renderElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5946:7)
frontend-node_1 | at renderMemo (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5868:3)
frontend-node_1 | at renderElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6011:11)
frontend-node_1 | at renderNodeDestructiveImpl (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6104:11)
frontend-node_1 | at renderNodeDestructive (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6076:14)
frontend-node_1 | at renderNode (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:6259:12)
frontend-node_1 | at renderHostElement (/app/node_modules/react-dom/cjs/react-dom-server.browser.development.js:5642:3)
The error with "label" occurs because the i18n object is empty on the server:
const DropdownSwitcher = () => {
const { i18n } = useTranslation()
const currentLanguage = useMemo(() => { // language as undefined
return LANGUAGES.find((item) => item.language === i18n.language)
}, [i18n.language])
....
But everything is fine on the client and there are no errors. What could be the reason and how to fix it, since the App itself from the _app.jsx file is wrapped in appWithTranslation from next-i18next.
Therefore, two questions arise, how to fix react-i18next:: You will need to pass in an i18next instance by using initReactI18next and why there is no i18n object on the server?
I moved the layout to the level of the page itself, removing it from _app.js, but for some reason, then something, useEffect() is repeated in the header, although the header component has not changed in any way and bringing the layout to the level of _app.jsx fixes it
If there is not enough information or you need a visual example, I will try to create a small program that demonstrates this with open source. Please write in a comment.

I solved my problem, but I forgot to provide an answer here, but I noticed that someone also has this problem, so I will try to help people who come across this post, although it is relevant only for nextjs version 12, since with the appearance of version 14, the structure there has improved a lot with as I think there should be no more questions like mine.
1. Rendering the layout
In the official doc, there is a whole section that describes how to correctly divide the layout so that it works according to the SPA type.
pages/index.jsx
// pages/index.jsx
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
export default function Page() {
return (
/** Your content */
)
}
Page.getLayout = function getLayout(page) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>
)
}
pages/_app.js
// pages/_app.js
export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}
This component method approach is much better than using its direction in _app.jsx because you can extend or replace them and not make a crude monolith, example how I used it:
// pages/ingex.jsx
function HomePage() {
return (
<HomeLayout>
<Main />
</HomeLayout>
)
}
HomePage.getLayout = (page) => <MainLayout>{page}</MainLayout>
// pages/about-us.jsx
const AboutUsPage = () => {
return (
<>
<HomeLayout>
<AboutUs />
</HomeLayout>
</>
)
}
AboutUsPage.getLayout = (page) => (
<MainLayout withNav>
<LayoutContext.Consumer>
{({ device }) => device.isMobile && <NavigationMobile />}
</LayoutContext.Consumer>
{page}
</MainLayout>
)
With this approach, react still works like a spa and a similar page to about-us, which will also have NavigationMobile, will simply compare it.
2. Error with next-i18next
The whole point was that the next-i18next library was configured incorrectly in the first place (more precisely, it needed to be corrected). In order to configure everything correctly, I had to do the following:
- Move the folder with translation files to the public folder. This is necessary so that the library config, which we will configure a little below, can see the translation files and interact with them
- Configure next-i18next.config.js to work with the client. Here is an example setup with some comments. And also a link to the documentation, and some other resources I found while setting up.
next-i18next.config.js
const path = require('path')
const LANGUAGES = ['en', 'pl', 'uk']
const DEFAULT_LANGUAGE = 'en'
// if it is the server, then the full path, if the client, then the relative path.
const localePath =
typeof window === 'undefined' ? path.resolve('public', 'translation') : '/public/translation'
module.exports = {
i18n: {
defaultLocale: DEFAULT_LANGUAGE,
locales: LANGUAGES,
fallbackLng: LANGUAGES,
nsSeparator: '::',
keySeparator: '::',
// How to use libraries for i18next like LanguageDetector
use: [require('i18next-intervalplural-postprocessor')],
serializeConfig: false,
},
localePath: localePath,
}
- Configure next-i18next in the _app.jsx file. Here everything is as described in the documentation.
import { appWithTranslation } from 'next-i18next'
import nextI18NextConfig from '../../next-i18next.config'
function App({ Component, ...rest }) {
const { store, props } = wrapper.useWrappedStore(rest)
const getLayout = Component.getLayout || ((page) => page)
//WARNING!!! You don't have to have your own i18next initialization like i18next.use(LanguageDetector).use(intervalPlural).init({ detection: options }) this is all done by the next-i18next library
return (
<Provider store={store}>
<AppHOC>{getLayout(<Component {...props.pageProps} />)}</AppHOC>
</Provider>
)
}
export default appWithTranslation(App, nextI18NextConfig)
- You need to pass the config when calling the serverSideTranslations function. To make your life easier, it is better to transfer the implementation of this function to another file, here is an example of how I did it:
// utils/serverSideTranslations.js
import { serverSideTranslations as baseServerSideTranslations } from 'next-i18next/serverSideTranslations'
import { dt } from '../../constants/defaultTranslate'
import { DEFAULT_LANGUAGE } from '../../constants/languages'
import nextI18NextConfig from '../../../next-i18next.config.js'
const serverSideTranslations = async (locale, domains = []) => {
return await baseServerSideTranslations(locale, [...dt, ...domains], nextI18NextConfig, [
DEFAULT_LANGUAGE,
])
}
export default serverSideTranslations
- And finally, use this function on the pages.
import MainLayout from '../components/layouts/MainLayout'
import serverSideTranslations from '../utils/serverSideTranslations'
import HomeLayout from '../components/home/HomeLayout'
import Main from '../components/home/main/Main'
function HomePage() {
return (
<HomeLayout>
<Main />
</HomeLayout>
)
}
HomePage.getLayout = (page) => <MainLayout>{page}</MainLayout>
export const getServerSideProps = async ({ locale }) => {
// Wrapping in Promis.all is not necessary, I use it simply so that if there are any other asynchronous operations, then not to use them through await and not to block each other's work
const [translations] = await Promise.all([
serverSideTranslations(locale, ['home']),
])
return {
props: {
...translations,
},
}
}
export default HomePage
I hope this helped someone, if you have any comments, write in the comments

Related

Next.js Image component with external dynamic source

I was working on my Next.js project and while running it locally html img tag worked okay.
While building, I got a warning, to change it to Image component from Next.js
So I did, but now I get a warning:
Error: Invalid src prop (https://image.ceneostatic.pl/data/products/10813131/i-dixit.jpg) on next/image, hostname "image.ceneostatic.pl" is not configured under images in your next.config.js
See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host
I read in the documentation that solution is to add a domain to next.config.js.
But 2 problems occurs to me here:
Even if I specify a domain like this, it doesn't work
module.exports = {
images: {
domains: ['image.ceneostatic.pl'],
},
};
I have my project connected to MongoDB, where are stored links to the images. What's more, I want an user to pass a link, while creating a new entry. So I don't want to have hard-coded domains, as I don't know which img an user will use.
Is there a way to omit domains list or a walk-around to use tag?
Thanks!
You can use something called next Loader via the following code:
import Image from 'next/image'
const myLoader = ({ src, width, quality }) => {
return `https://image.ceneostatic.pl/data/products/{src}/i-dixit.jpg`
}
var photoID = 10813131
const MyImage = (props) => {
return (
<Image
loader={myLoader}
src=photoID
width={500}
height={500}
/>
)
}
Your next.config.js:
module.exports = {
images: {
loader: 'imgix',
path: 'https://image.ceneostatic.pl',
},
}
All documentation is linked here.

Says Jasmine spy is not being called, but I can see that its being called

I can't figure out why Jasmine is claiming that the function I'm spying on isn't being called, especially since it is logging in buildLinksObj when called through and not calling when I remove .and.callThrough() I feel like I've written similar code a bunch of times before without any problem. I'm using Jasmine 2.9
The error message I'm getting is:
1) addToLinks should call buildLinksObj if its given an object with children
it should add the personalized links to PageApp.meta.analytics.links
Expected spy buildLinksObj to have been called.
at UserContext.<anonymous> (http://localhost:9877webpack:///tests/specs/common/FetchPersonalContent.spec.js:854:0 <- tests/app-mcom.js:104553:48)
Here's the except of my code:
FetchPersonalContent.js
const buildLinksObj = (responseObj = {}, targetObj, PageApp) => {
console.log('it logs in buildLinksObj') // This is logging!
}
const addToLinks = (responseArr, personalizedLinks) => {
responseArr.forEach((media) => {
const type = media.type;
const typeObj = media[type];
buildLinksObj(typeObj, personalizedLinks, PageApp);
if (typeObj && typeObj.children) {
console.log('has children!')
console.log('typeObj.children is: ', typeObj.children);
typeObj.children.forEach((child) => {
console.log('has a child')
buildLinksObj(child, personalizedLinks, PageApp);
console.log('buildLinksObj was definitely called. what the heck?')
});
}
});
}
export {buildLinksObj, addToLinks, FetchPersonalContent as default,
};
FetchPersonalContent.spec.js
import * as FetchPersonalContent from '../../../src/FetchPersonalContent'; // my path is definitely correct
describe('it should add the personalized links to PageApp.meta.analytics.links', () => {
it('addToLinks should call buildLinksObj if its given an object with children ', () => {
spyOn(FetchPersonalContent, 'buildLinksObj').and.callThrough();
FetchPersonalContent.addToLinks([{
"personalId": 30718,
"type": "carousel",
"carousel": {}
}], {});
expect(FetchPersonalContent.buildLinksObj).toHaveBeenCalled();
});
});
I'd really appreciate any help!
I have a feeling FetchPersonalContent.buildLinksObj in the spec file is not pointing to the same instance as buildLinksObj in the FetchPersonalContent.js file.
Why is export {FetchPersonalContent as default} required? I am assuming you have shared the complete content of FetchPersonalContent.js in your question.
Possible solutions:
You can try removing FetchPersonalContent from the export statement.
Or
Instead of
export {buildLinksObj, addToLinks, FetchPersonalContent as default,
};
You can directly export the constants in FetchPersonalContent.js file.

BooleanField with FunctionField change number to Boolean

I have question and I'm sure it will help other developers.
I have field "is_active" which is Boolean in my API side but it return 0 or 1 and not TRUE or FALSE.
I want to use <FunctionField/> to wrap the <BooleanField/> but it didn't work. Someone can help please.
This is my code:
<FunctionField source="is_active" label="is_active" render={(record) => record.is_active ? true : false}>
<BooleanField/>
</FunctionField>
The column is still blank.
Thanks.
I think you misunderstood the FunctionField component. It renders the result of the render prop. What you are trying to achieve is:
<FunctionField source="is_active" label="is_active" render={(record,source) =>
<BooleanField record={{...record,is_active:!!record.is_active}} source={source}/>}/>
But this is not very nice. Better is to wrap your dataProvider/restClient and ensure the data is a boolean.
// In FixMyDataFeature.js
export default restClient => (type, resource, params) => restClient(type,resource,params).then(response=>
if(resource === 'Resource_with_numeric_is_active_field`){
return {
data: mutateIsActiveFieldToBoolean(response.data)
}
}
else{
return response;
}
);
And call it with Admin:
<Admin dataProvider={FixMyDataFeature(dataProvider)}... />
Here is my solution: (you can import it and use instead of BooleanField)
import React from 'react';
import { BooleanField } from "react-admin";
export const BooleanNumField = ({ record = {}, source}) => {
let theRecord = {...record};
theRecord[source + 'Num'] = !!parseInt(record[source]);
return <BooleanField record={theRecord} source={source + 'Num'} />
}
I had an issue where the in a DB table there was a field called disabled but in the Admin was a bit confusing setting disabled to false to actually enable something.
Based on 'Dennie de Lange' answer, I have created a Typescript generic BooleanOppositeField and BooleanOppositeInput. Putting here hoping may help someone:
import { BooleanField, BooleanInput, FunctionField } from 'react-admin';
interface IProps {
label: string;
source: string;
}
/**
* Usually called using:
* <BooleanOppositeField label="Enabled" source="disabled"/>
*/
export const BooleanOppositeField = (props: IProps) => {
return (
<FunctionField {...props} render={(record: any | undefined, source: string | undefined) =>
<BooleanField source="enabled" record={{ ...record, enabled: !(record![source!]) }} />}
/>
);
};
/**
* Usually called using:
* <BooleanOppositeInput label="Enabled" source="disabled" />
*/
export const BooleanOppositeInput = (props: IProps) => {
return (
<BooleanInput format={(v: boolean) => !v} parse={(v: boolean) => !v} {...props} />
)
}
And you can use it by:
<BooleanOppositeField label="Enabled" source="disabled"/>
or
<BooleanOppositeInput label="Enabled" source="disabled" />
Note: I liked more this solution, than the recommended by Dennie

JS/(X) import: to import React components?

I want to import a React component from a jsx file in a template and render it in the template with ReactDOM. Later in production I would only want to ship react and all the dependencies of the component only when a site is loaded that has that component.
I have created a React component like this:
editor.jsx
import * as React from "react";
import {Editor} from "draft-js-plugins-editor";
const plugins = [];
export class EditorComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
}
onChange(editorState) {
this.setState({
editorState,
});
}
render() {
return (<Editor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={plugins}
/>);
}
}
http://www.phoenixframework.org/docs/static-assets suggests the require syntax for accessing module exports. So I added the following to my template <script>const editor = require("web/static/js/editor").EditorComponent</script>. This does not work though, because the browser cannot interpret require (or brunch does not pick it up).
I configured brunch like so:
plugins: {
babel: {
// Do not use ES6 compiler in vendor code
ignore: [/web\/static\/vendor/],
presets: ["es2015","react"]
}
},
modules: {
autoRequire: {
"js/app.js": ["web/static/js/app"],
"js/editor.jsx": ["web/static/js/editor"]
}
},
I am a bit lost here. How can this be done?
One idea that pops to mind is to create a JS file and import it in the template you want with a <script> tag. In the same template create an empty <div id=editor>. Then, in the JS file import React and ReactDOM and the component you want and use something like this:
ReactDOM.render(
<Editor/>,
document.getElementById("editor")
)
However, I'm not sure I understand your problem correctly.

ionic 2 caching images

I am writing an ionic 2 application, and want to cache images.
After long searching on the web I found these references:
https://gist.github.com/ozexpert/d95677e1fe044e6173ef59840c9c484e
https://github.com/chrisben/imgcache.js/blob/master/js/imgcache.js
I implemented the given solution, but i see that the ImgCache module does not behave as expected - the ImgCache.isCached callback is never called.
Any idea or other good solution for caching images in ionic 2?
======== UPDATE ==========
Here is the directive code I use:
import { Directive, ElementRef, Input } from '#angular/core';
import ImgCache from 'imgcache.js';
#Directive({
selector: '[image-cache]'
})
export class ImageCacheDirective {
constructor (
private el: ElementRef
) {
// init
}
ngOnInit() {
// This message is shown in console
console.log('ImageCacheDirective *** ngOnInit: ', this.el.nativeElement.src);
this.el.nativeElement.crossOrigin = "Anonymous"; // CORS enabling
ImgCache.isCached(this.el.nativeElement.src, (path: string, success: any) => {
// These message are never printed
console.log('path - '+ path);
console.log('success - '+ success);
if (success) {
// already cached
console.log('already cached so using cached');
ImgCache.useCachedFile(this.el.nativeElement);
} else {
// not there, need to cache the image
console.log('not there, need to cache the image - ' + this.el.nativeElement.src);
ImgCache.cacheFile(this.el.nativeElement.src, () => {
console.log('cached file');
// ImgCache.useCachedFile(el.nativeElement);
});
}
});
}
}
In app.nodule.es I do:
import { ImageCacheDirective } from '../components/image-cache-directive/image-cache-directive';
and then in home.html:
<img src="http://localhost/ionic-test/img/timeimg.php" image-cache>
It's late but probably this is the solution:
1. Install cordova FileTransfer:
ionic plugin add cordova-plugin-file-transfer --save
2. Init ImgCache when the deviceready event of cordova fires. In src/app/app.component.ts add these methods (or integrate them with your initializeApp() method - this method comes up with a default project start):
initImgCache() {
// activated debug mode
ImgCache.options.debug = true;
ImgCache.options.chromeQuota = 100 * 1024 * 1024; // 100 MB
ImgCache.init(() => { },
() => { console.log('ImgCache init: error! Check the log for errors'); });
}
initializeApp() {
this.platform.ready().then(() => {
this.initImgCache();
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
StatusBar.styleDefault();
Splashscreen.hide();
});
}
Another option is to use a dedicated cache manager for ionic. instead of implementing everything on your own.
Here are 2 options :
1. A generic cache implementation :https://github.com/Nodonisko/ionic-cache
2. This one is better for images: https://github.com/BenBBear/ionic-cache-src
EDIT:
This is not a "link only" answer.. it tells the user to use a ready made implementations instead of trying to implement from scratch.

Resources