Problem finding string atached to json file when translating (i18n) - reactjs

I have successfully translated many of the pages on my website project but now I have moved to translate specific components that were created and it is not working.
The error message I get is as follows: "TypeError: Cannot read properties of undefined (reading 'titulo')"
**Note: I am using NextJS and TailwindCSS
This is the code for the JSON file strings where it should grab the translation, I have two files, one for ES (Spanish) and another EN (English)
"heroBanner":{
"titulo": "Ayudamos a PYMES a Captar la Atención mediante Publicidad Digital",
"subtitulo": "BIENVENIDO A COTTONMEDIA"
},
This is the component code where I have passed the props:
import React from 'react'
import Custom__Cursor from './Custom__Cursor'
function Hero__Banner(props) {
return (
<div>
<div className="relative h-screen">
<div className="h-full w-full">
<video autoPlay muted loop className="object-cover h-screen w-full ">
<source src="/AdsBgVideo.mp4" type="video/mp4">
</source>
</video>
</div>
<div className="absolute top-0 h-full w-full bg-black opacity-60 z-10"></div>
<div className="absolute top-1/4 z-20 max-w-screen-md md:max-w-screen-xl">
<div className='flex flex-col px-10 space-y-5'>
<p className='text-left text-sm md:text-base font-medium tracking-wide filter drop-shadow-md'>{props.titulo}</p>
<p className='text-left text-4xl md:text-5xl leading-normal md:leading-relaxed filter drop-shadow-md'>{props.subtitulo}</p>
</div>
</div>
</div>
</div>
)
}
export default Hero__Banner
Then where the component is rendered I have included these props with the string in order to get the translation as follows:
import Head from 'next/head'
import Image from 'next/image'
import Hero__Banner from '../components/Hero__Banner'
import ScrollToTop from '../components/ScrollToTop'
export default function Home(props) {
const { index, heroBanner } = props;
return (
<div>
<Head>
<title></title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/short-logo.svg" />
</Head>
{/*Main content */}
{/* Hero section */}
<Hero__Banner
titulo={heroBanner.titulo}
subtitulo={heroBanner.subtitulo}
/>
</div>
)
}
export async function getStaticProps({locale}) {
const response = await import(`../lang/${locale}.json`);
return {
props: {
index: response.default.index,
heroBanner: response.default.heroBanner
},
};
}
Even after doing that, I still get the same error, not sure why this is happening. The component translations are now on the index page which is in "pages", so it should work in my opinion. As you can see in the code, I already have some translated sections in the index, which work perfectly.
Error image
Any suggestions?

When you are translating with i18n, and get this error, it could be because you have altered the translation JSON file and the server needs to be restarted. That was the case for me, hope this helps.

Related

Need help getting local image to appear using Framer Motion and NEXTjs

I am using NEXTJS and framer motion but the local image is not appearing on the web page. Instead I get that little clip art thing.Below is my code to the particular component not working:
import React from 'react'
import {motion} from "framer-motion";
import aboutPic from './images/wed.jpg'
type Props = {}
function About({}: Props) {
return (
<div className=" flex flex-col relative h-screen text-center md:text-left md:flex-row max-
7xl px-10 justify-evenly mx-auto items-center">
<h3 className="absolute top-24 uppercase tracking-[20px] text-gray-500 text-2xl">
About
</h3>
<motion.img
initial={{
x:-200,
}}
transition={{
duration:1.2,
}}
whileInView={{x: 0}}
src={aboutPic}
/>
</div>
)
}
export default About
Most probably due to the location of your image.
According to your specified path, your image should be in the same folder as your 'About' component. Check your image location and path.
Note that in default image imports you can't use direct path for images stored in public folder as in urls.
Instead, you have to locate it like any exported component.
If you are in 'pages' folder and the 'images' folder is in 'public' for example:
import aboutPic from '../public/images/wed.jpg'
or
<img src='/images/wed.jpg'/>

remove file function not allowing removed file to be added again, only after selecting another file - ReactJS

I made a function in my project that allows me to add and remove a file that will be uploaded, the intended function of adding and removing is implemented, however, when I try adding the file that I previously added (for the testing of adding and removing same files because it is a possible action of the user) it is not showing up. The removed file can only be added again if I select a different file first for preview then remove that different selected file, and selecting again the first removed file; which is not a good ux.
remove file function, I turned it into a global function because different pages uses this.
`
export const handleRemoveFile = (setSelectedFile) => {
setSelectedFile({ fileName: undefined, fileUrl: undefined, isImage: false });
};
`
attachment input component, (if you need to see it), I turned it into a global function because different pages uses this.
`
import React from "react";
export default function AttachmentInput(props) {
return (
<label
className={`${
props.hasSubmitted && "hidden"
} custom-input w-full custom-flex bg-white cursor-pointer text-sm font-Poppins font-semibold hover:shadow-md
tablet:text-base
laptop-l:text-lg`}
htmlFor={props.htmlFor}
>
<input
className="hidden"
type="file"
name={props.name}
id={props.id}
onChange={(e) => {
props.onChange1(e.target); // post data for upload
props.onChange2(e); // for file preview
}}
/>
<div>{props.selectedFile.fileUrl ? props.secondaryLabel : props.primaryLabel}</div>
</label>
);
}
`
**attachment preview **component, (if you need to see it), I turned it into a global function because different pages uses this.
import React from "react";
import logo from "../../";
export default function AttachmentPreview(props) {
return (
props.selectedFile.fileUrl && (
<div className="custom-flex w-full h-fit">
<div
className={
props.className
? `${props.className}`
: "custom-flex flex-col bg-white custom-light-border py-3 px-0"
}
>
{props.selectedFile.isImage ||
props.selectedFile.fileUrl.endsWith(".jpg") ||
props.selectedFile.fileUrl.endsWith(".png") ? (
<img
className={`max-h-36 rounded-lg`}
src={props.selectedFile.fileUrl}
alt="selected file"
/>
) : (
<img className="max-h-16" src={logo} alt="holder" />
)}
<div className="custom-divider my-4 w-full" />
<div className="font-Poppins font-light">
{props.selectedFile.fileName
? props.selectedFile.fileName
: props.postData.file_name
? props.postData.file_name
: "Current Uploaded File"}
</div>
</div>
</div>
)
);
}
usage of remove file function
{selectedFile.fileUrl && (
<CancelButton
onClick={() => file_fns.handleRemoveFile(setSelectedFile)}
label={"REMOVE FILE"}
/>
)}

Why Tailwind some classes don't work in React

I want to use Tailwind to style my app in React but some classes are not working, how can I fix it? And what would affect it?
function NavBar({ title }) {
return (
<nav className="navbar mb-12 shadow-lg bg-neutral text-neutral-content">
<div className="container mx-auto">
<div className="flex-none px-2 mx-2">
<AiFillGithub />
</div>
</div>
</nav>
);
}
but on web page nothing changed.
I sorted out the problem, in file tailwind.config.js I had:
content:["./src/**/*.{html,js}", "./components/**/*.{html,js}"]
and I should change for:
content: ["./src/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"]
I posted my solution just in case if someone will face the same problem

React and Tailwind CSS: dynamically generated classes are not being applied

I'm just learning React and Tailwind CSS and had a strange experience with CSS grid using Tailwind classes. I've made the buttons for a calculator, with the last Button spanning two columns:
App.js:
export default function App() {
return (
<div className="flex min-h-screen items-center justify-center bg-blue-400">
<Calculator />
</div>
);
}
Calculator.js
import { IoBackspaceOutline } from "react-icons/io5";
export const Calculator = () => {
return (
<div className="grid grid-cols-4 grid-rows-5 gap-2">
<Button>AC</Button>
<Button>
<IoBackspaceOutline size={26} />
</Button>
<Button>%</Button>
<Button>÷</Button>
<Button>7</Button>
<Button>8</Button>
<Button>9</Button>
<Button>x</Button>
<Button>4</Button>
<Button>5</Button>
<Button>6</Button>
<Button>-</Button>
<Button>1</Button>
<Button>2</Button>
<Button>3</Button>
<Button>+</Button>
<Button>0</Button>
<Button>.</Button>
<Button colSpan={2}>=</Button>
</div>
);
};
const Button = ({ colSpan = 1, rowSpan = 1, children }) => {
return (
<div
className={`col-span-${colSpan} row-span-${rowSpan} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
This doesn't work (tested in Chrome):
Now here comes the weird part. I replaced the returned JSX from the App component with HTML from a Tailwind tutorial and deleted it again.
<div className="bg-blue-400 text-blue-400 min-h-screen flex items-center justify-center">
<div className="grid grid-cols-3 gap-2">
<div className="col-span-2 bg-white p-10 rounded">1</div>
<div className="bg-white p-10 rounded">2</div>
<div className="row-span-3 bg-white p-10 rounded">3</div>
<div className="bg-white p-10 rounded">4</div>
<div className="bg-white p-10 rounded">5</div>
<div className="bg-white p-10 rounded">6</div>
<div className="col-span-2 bg-white p-10 rounded">7</div>
<div className="bg-white p-10 rounded">8</div>
<div className="bg-white p-10 rounded">9</div>
</div>
</div>
After I Ctrl-Z'd a bunch of times, so I had only the previous code, my button suddenly spans two columns as intended:
I checked to make sure that there were no changes in the code:
My friend even cloned my repo, followed the same steps and got the same result.
He suspects that it has something to do with the variable classNames in my Button component with regards to Tailwind's JIT compiler, but none of us can pinpoint the error.
Am I using variable CSS classes wrong?
This has been a WTF moment. What could be the reason for this?
The CSS file generated by Tailwind will only include classes that it recognizes when it scans your code, which means that dynamically generated classes (e.g. col-span-${colSpan}) will not be included.
If you only need to span 2 columns, you could pass boolean values which will trigger the addition of a full col-span-2 or row-span-2 utility class to be added:
const Button = ({ colSpan = false, rowSpan = false, children }) => {
return (
<div
className={`${colSpan ? 'col-span-2' : ''} ${rowSpan ? 'row-span-2' : ''} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
Otherwise, you could pass the values as classes to the Button component:
<Button className='col-span-2 row-span-1'>=</Button>
const Button = ({ className, children }) => {
return (
<div
className={`${className} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
More information: https://tailwindcss.com/docs/content-configuration#dynamic-class-names
Another tricky solution that worked for me is to use variable with forced type of the possible className values (in typescript) like :
export type TTextSizeClass =
'text-xl' |
'text-2xl' |
'text-3xl' |
'text-4xl' |
'text-5xl' |
'text-6xl' |
'text-7xl' |
'text-8xl' |
'text-9xl'
;
...
const type : number = 6 ;
const textSizeClass : TTextSizeClass = type != 1 ? `text-${type}xl` : 'text-xl';
...
<div className={`font-semibold ${textSizeClass} ${className}`}>text</div>
As Ed Lucas said:
The CSS file generated by Tailwind will only include classes that it recognizes when it scans your code, which means that dynamically generated classes (e.g. col-span-${colSpan}) will not be included
But now could use safeListing
and
tailwind-safelist-generator package to "pregenerate" our dynamics styles.
With tailwind-safelist-generator, you can generate a safelist.txt file for your theme based on a set of patterns.
Tailwind's JIT mode scans your codebase for class names, and generates
CSS based on what it finds. If a class name is not listed explicitly,
like text-${error ? 'red' : 'green'}-500, Tailwind won't discover it.
To ensure these utilities are generated, you can maintain a file that
lists them explicitly, like a safelist.txt file in the root of your
project.

ReactJs/NextJs - Conditionally render mutually exclusive components without compromising load time

I am trying to improve loading speed of a react web app.
I have two component imports - one for mobile and one for desktop (Bad design? I think so):
import Posts from '../components/post/posts';
import PostsMobile from '../components/post/postsMobile';
This was easy for development because I did not have to try hard to make the same component compatible for desktop and mobile.
Then to check screen size and load the appropriate component, I do this:
const largeScreen = useMediaQuery(theme => theme.breakpoints.up('sm'));
...
{largeScreen? (
<Posts />
) :
(
<PostsMobile />
)
}
You can resize the browser here to see the two components load: Link to home page showing the two components
Does <PostsMobile /> get imported only when react sees that its needed OR does it automatically get imported in the beginning no matter what?
Is there a better way to conditionally render mutually exclusive components without compromising load time?
Classic conditional rendering, that's the appropriate way to do it, only one component will be added to the DOM in any case. As a side note it's not the best idea to have two different components for mobile vs desktop view, generally your html structure should be the same and you should use CSS for any layout changes (as per Google's suggestions - https://web.dev/responsive-web-design-basics/)
Check out #artsy/fresnel, they have a very straightforward example showing how to configure Next.js and Gatsby.js to achieve screen-width dependent control in SSR environments
Note: it hardly increases overhead size; I am using it in my portfolio currently in conjunction with tailwindcss and it has proven to be a fantastic tool. Easy to configure and its implementation is straightforward. Here is an example of conditionally rendering the same svg icon four times as a function of screen size to customize styles accordingly (xs (mobile), sm, md, greater than md (desktop))
(1) Create a window-width file in your components directory to configure #artsy/fresnel for global sharing
components/window-width.jsx or components/window-width.tsx
import { createMedia } from '#artsy/fresnel';
const PortfolioMedia= createMedia({
breakpoints: {
xs: 0,
sm: 768,
md: 1000,
lg: 1200,
},
})
// Generate CSS to be injected in the head using a styles tag (pages/_document.jsx or pages/_document.tsx)
export const mediaStyles = PortfolioMedia.createMediaStyle();
export const { Media, MediaContextProvider } = PortfolioMedia;
// https://github.com/artsy/fresnel/tree/master/examples/nextjs
Note, you can customize the breakpoints however you'd like; the following breakpoints are from my portfolio's configuration file. They overlap with Tailwind's breakpoints to keep them playing nicely together
// ...
const PortfolioMedia = createMedia({
breakpoints: {
xs: 0,
sm: 640,
md: 768,
lg: 1024,
xl: 1280
}
});
// ...
(2) Wrap pages/index.jsx or pages/index.tsx with the MediaContextProvider component
// ...
import { MediaContextProvider } from 'components/window-width';
interface IndexProps {
allPosts: Post[];
allAbout: AboutType[];
allBlog: BlogType[];
}
const Index = ({ allPosts, allAbout, allBlog }: IndexProps) => {
const morePosts = allPosts.slice(0);
const moreAbout = allAbout.slice(0);
const moreBlog = allBlog.slice(0);
return (
<Fragment>
<MediaContextProvider>
<Lead />
<Head>
<title>{`${CLIENT_NAME} landing page`}</title>
</Head>
<div className='max-w-cardGridMobile md:max-w-cardGrid my-portfolioH2F grid mx-auto content-center justify-center items-center text-center'>
{morePosts.length > 0 && <Cards posts={morePosts} />}
</div>
<div className='max-w-full my-portfolioH2F block mx-auto content-center justify-center items-center text-left'>
{moreAbout.length > 0 && <AboutCoalesced abouts={allAbout} />}
</div>
<div className='max-w-full my-portfolioH2F block mx-auto content-center justify-center items-center text-left'>
{moreBlog.length > 0 && <BlogCoalesced blogs={allBlog} />}
</div>
<Footer />
</MediaContextProvider>
</Fragment>
);
};
export default Index;
// ...
(3) Finally, inject generated mediaStyles CSS into a style tag of type text/css in Next's Head
pages/_document.jsx or pages/_document.tsx
import Document, {
Html,
Head,
Main,
NextScript,
DocumentContext
} from 'next/document';
import { mediaStyles } from 'components/window-width';
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html lang='en-US'>
<Head>
<meta charSet='utf-8' />
<link rel='stylesheet' href='https://use.typekit.net/cub6off.css' />
<style type='text/css' dangerouslySetInnerHTML={{ __html: mediaStyles }} />
</Head>
<body className='root'>
<script src='./noflash.js' />
<Main />
<NextScript />
</body>
</Html>
);
}
}
(4) Profit; Configuration complete
That's all there is to it. For the sake of clarity I threw in an actual example from one of my projects below.
(5) Bonus example - conditionally rendering ArIcon as a function of device-size
components/lead-arIcon.tsx
import { ArIcon } from 'components/svg-icons';
import Link from 'next/link';
import { Media } from 'components/window-width';
import { Fragment } from 'react';
import DarkMode from 'components/lead-dark-mode';
const ArIconConditional = (): JSX.Element => {
const arIconXs: JSX.Element = (
<Media at='xs'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
id='top'
aria-label='top'
>
<ArIcon width='18vw' height='18vw' />
</a>
</Link>
</Media>
);
const arIconSm: JSX.Element = (
<Media at='sm'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
id='top'
aria-label='top'
>
<ArIcon width='15vw' height='15vw' />
</a>
</Link>
</Media>
);
const arIconMd: JSX.Element = (
<Media at='md'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
id='top'
aria-label='top'
>
<ArIcon width='12.5vw' height='12.5vw' />
</a>
</Link>
</Media>
);
const arIconDesktop: JSX.Element = (
<Media greaterThan='md'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full'
id='top'
aria-label='top'
>
<ArIcon
width='10vw'
height='10vw'
classNames={[
` antialised w-svgIcon max-w-svgIcon transform transition-all`,
' stroke-current',
` fill-primary`
]}
/>
</a>
</Link>
</Media>
);
const DarkModeToggler = (): JSX.Element => (
<div className='pt-portfolio text-customTitle transition-all transform -translate-y-mdmxSocial col-span-4 text-right -translate-x-portfolioPadding'>
<DarkMode />
</div>
);
const ArIconsCoalesced = (): JSX.Element => (
<Fragment>
<div className='relative block justify-between lg:w-auto lg:static lg:block lg:justify-start transition-all w-full min-w-full col-span-2'>
{arIconXs}
{arIconSm}
{arIconMd}
{arIconDesktop}
</div>
</Fragment>
);
return (
<Fragment>
<div className='select-none relative z-1 justify-between pt-portfolioDivider navbar-expand-lg grid grid-cols-6 min-w-full w-full container overflow-y-hidden overflow-x-hidden transform'>
<ArIconsCoalesced />
<DarkModeToggler />
</div>
</Fragment>
);
};
export default ArIconConditional;
You could try to lazy load the components.
posts;
componentDidMount() {
if (largeScreen) {
import('../components/post/posts').then(({ default: Posts }) => {
// ^^^^ make sure it has a default export
this.posts = Posts;
this.forceUpdate();
});
} else {
// here load the other component for lower screens
}
}
Then, inside render:
const Posts = this.posts;
return largeScreen? (
<Posts />
) : (
<PostsMobile />
);
Note: You will have to also add a resize listener, so if the screen reaches certain width, the another component will load and will get rendered.
Note2: If you don't care about SSR - you could either try React.lazy with Suspense: https://en.reactjs.org/docs/code-splitting.html#reactlazy
In my opinion there are very valid usages of Desktop vs. Mobile components, for example, although CSS versions are definitely almost always preferred, it might not make sense for drag and drop components that don't work on mobiles, mobile-only hamburger menus, etc.
React's lazy loading is the way to go (unless you need SSR):
import React, { Suspense } from 'react';
const Posts = React.lazy(() => import('../components/post/posts');
const PostsMobile = React.lazy(() => import('../components/post/postsMobile');
function MainComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
{ largeScreen? ( <Posts />) : <PostsMobile /> }
</Suspense>
</div>
);
}
This will guarantee that the component is only loaded when it is first rendered. Bonus tip: if you change the loading div to an animated loading placeholder, the UI of your application may be more pleasant.
None of the answers were compatible with SSR using Nextjs so I ended up using Dynamic Import feature. Looks very powerful but simple.
https://nextjs.org/docs/advanced-features/dynamic-import
import dynamic from 'next/dynamic'
const Posts = dynamic(() => import('../components/post/posts'),
{ loading: () => <LinearProgress /> });
const PostsMobile = dynamic(() => import('../components/post/postsMobile'),
{ loading: () => <LinearProgress /> });
This saved me a few milliseconds
I am not sure if there is a better option so I hope people will comment.

Resources