Is there a way to split a i18n translation? - reactjs

OK, so I got this component that animating my titles. But know, I want to translate my application with i18n, but problem is, I was using .split() function to make an array of words of my titles, I know that .split() is taking only string, and all I tested return me a JSX Element. So I can't split my pages title.
Is there another way to do it, to keep my translation ?
Here is an exemple of my pages with the component title and what I tried (I also tried with Translation from react-i18next, but same result)
About.tsx
import { useEffect, useState } from "react";
import AnimatedLetters from "../AnimatedLetters/AnimatedLetters"
import { Div } from "../Layout/Layout.elements";
import { useTranslation, Trans } from "react-i18next";
const About = () => {
const [letterClass, setLetterClass] = useState<string>('text-animate');
const { t, i18n } = useTranslation();
useEffect(() => {
setTimeout(() => {
setLetterClass('text-animate-hover')
}, 3000)
}, [])
const getTranslation = (value: string) => {
return <Trans t={t}>{value}</Trans>;
}
return (
<Div>
<div className="container about-page">
<div className="text-zone">
<h1>
<AnimatedLetters
strArray={getTranslation('About.Title').split("")}
idx={15}
letterClass={letterClass}
/>
</h1>
</div>
</Div>
)
}
export default About
Before decide to translate, I was making like that :
<AnimatedLetters
strArray={"About us".split("")}
idx={15}
letterClass={letterClass}
/>
AnimatedLetters.tsx
import { FunctionComponent } from "react"
import { Span } from "./AnimatedLetters.elements"
type Props = {
letterClass: string,
strArray: any[],
idx: number
}
const AnimatedLetters: FunctionComponent<Props> = ({ letterClass, strArray, idx }) => {
return (
<Span>
{
strArray.map((char: string, i: number) => (
<span key={char + i} className={`${letterClass} _${i + idx}`} >
{char}
</span>
))
}
</Span>
)
}
export default AnimatedLetters

OK I found it! I put the solution here in the case of someone else needs it!
In fact there is two ways, don't forget that I needed an array to transmet to my component, so the first was to put directly an array into my translations json files, like:
common.json
{
"Title": ["A","b","o","u","t","","u","s"]
}
But i did not thought that was very clean.
So the second way was to create a method that tooks the key of the json file, to return it directly, like this :
const getTranslate = (value: string) => {
return (`${t(value)}`)
}
Then I stringify it to can use .split() to make an array
const getTranslate = (value: string) => {
return JSON.stringify(`${t(value)}`).split("")
}
The translate and the array worked nicely, but it returned with double quotes. The last thing was to erase it, with a replace and a little regex, and now : everything works like a charm 😁
All the component looks like it now :
About.tsx
import { useEffect, useState } from "react";
import AnimatedLetters from "../AnimatedLetters/AnimatedLetters"
import { Div } from "../Layout/Layout.elements";
import { useTranslation } from 'react-i18next';
const About = () => {
const [letterClass, setLetterClass] = useState('text-animate');
const { t } = useTranslation();
useEffect(() => {
setTimeout(() => {
setLetterClass('text-animate-hover')
}, 3000)
}, [])
const getTranslate = (value: string) => {
return JSON.stringify(`${t(value)}`).replace(/\"/g, "").split("")
}
return (
<Div>
<div className="container about-page">
<div className="text-zone">
<h1>
<AnimatedLetters
strArray={getTranslate('Title')} // <<--- Called here
idx={15}
letterClass={letterClass}
/>
</h1>
</div>
</div>
</Div>
)
}
export default About

Related

How to use state on one element of map on typescript?

I want to use onClick on one element of my map and set "favorite" for it. Basically, I'm trying to change the SVG of a Icon to the filled version, but with the map, all of items are changing too.
I already try to pass this to onClick, but doesn't work.
My code:
import React, { Component, useState, useEffect } from "react";
import Slider from "react-slick";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import { ForwardArrow } from "../../../assets/images/ForwardArrow";
import { BackArrow } from "../../../assets/images/BackArrow";
import * as S from "./styled";
import { IconFavoriteOffer } from "../../../assets/images/IconFavoriteOffer";
import { Rating } from "../../../assets/images/Rating";
import { TruckFill } from "../../../assets/images/TruckFill";
import { OpenBox } from "../../../assets/images/OpenBox";
import { IconCartWht } from "../../../assets/images/IconCartWht";
import axios from "axios";
import { off } from "process";
import SwitcherFavorite from "../SwitcherFavorite";
export default function Carousel() {
const [offers, setOffers] = useState<any[]>([]);
useEffect(() => {
axios.get("http://localhost:5000/offers").then((response) => {
setOffers(response.data);
});
}, []);
const [favorite, setFavorite] = useState(true);
const toggleFavorite = () => {
setFavorite((favorite) => !favorite);
};
return (
<>
<Slider {...settings}>
{offers.map((offer, index) => {
return (
<S.Offer key={index}>
<>
<S.OfferCard>
<S.OfferCardTop>
<S.OfferContentTop>
<S.OfferFavorite>
<S.OfferFavoriteButton onClick={toggleFavorite}> // Want to get this element of mapping
<SwitcherFavorite favorite={favorite} />
</S.OfferFavoriteButton>
</S.OfferFavorite>
<S.OfferStars>
<Rating />
</S.OfferStars>
</S.OfferContentTop>
</S.OfferCardTop>
</S.OfferCard>
</>
</S.Offer>
);
})}
</Slider>
</>
);
}
So, how can I do it?
Instead of using a single boolean flag with your current [favorite, setFavorite] = useState(false) for all the offers, which wouldn't work, you can store the list of offer IDs in an array. In this way you can also have multiple favourited offers.
Assuming your offer item has a unique id property or similar:
// This will store an array of IDs of faved offers
const [favorite, setFavorite] = useState([]);
const toggleFavorite = (id) => {
setFavorite((previousFav) => {
if (previousFav.includes(id)) {
// remove the id from the list
// if it already existed
return previousFav.filter(favedId => favedId !== id);
}
// add the id to the list
// if it has not been here yet
return [...previousFav, id]);
}
};
And then in your JSX:
/* ... */
<S.OfferFavoriteButton onClick={() => toggleFavorite(offer.id) }>
<SwitcherFavorite favorite={favorite.includes(offer.id)} />
// Similar to your original boolean flag to switch icons
</S.OfferFavoriteButton>
/* ... */

Mapping An Array In React JS not rendering

I have a nested dictionary object called teams which I preprocess into an array of arrays.
Initially, my teams data (the nested array) looks like this:
and then it is processed into a teamCards array which looks like this:
However even once processed, my map function is still not mapping my array into a component like I would like. Does anyone know why not? Here is my react code:
import React, {useEffect} from 'react'
import { Grid } from '#material-ui/core'
import TeamCard from './TeamCard'
import loader from '../images/loader.gif'
export default function Teams({teamsLoading, teams}) {
console.log(teams)
const teamCards = []
function populateTeamCards() {
Object.keys(teams).forEach(function(key) {
Object.keys(teams).forEach(function(key) {
Object.keys(teams[key]).forEach(function(t) {
teamCards.push([t, teams[key][t]])
})
})
})
}
useEffect(() => {
if (teamsLoading == false) {
populateTeamCards()
}
}, [teamsLoading])
return (
teamsLoading ?
<img src={loader} alt="loading..." /> :
<Grid>
{teamCards.map((element, index) => {
return (
<TeamCard
key={index}
teamName={element[0]}
/>
)
})}
</Grid>
)
}
You can try this
import React, { useEffect, useState } from "react";
import { Grid } from "#material-ui/core";
import TeamCard from "./TeamCard";
import loader from "../images/loader.gif";
export default function Teams({ teamsLoading, teams }) {
const [teamCards, setTeamCards] = useState([]);
function populateTeamCards() {
let newArray = [];
Object.keys(teams).forEach(function (key) {
Object.keys(teams[key]).forEach(function (t) {
newArray.push([t, teams[key][t]]);
});
});
setTeamCards(newArray);
}
useEffect(() => {
if (teamsLoading == false) {
populateTeamCards();
}
}, [teamsLoading]);
return teamsLoading ? (
<img src={loader} alt="loading..." />
) : (
<Grid>
{teamCards.map((element, index) => {
return <TeamCard key={index} teamName={element[0]} />;
})}
</Grid>
);
}
You're setting an instance variable's value and this does not trigger a component re-render, which means even after teamCards is updated, the UI still stays the same as when it was empty.
What you need is to use a React state like this:
const [teamCards, setTeamCards] = useState([]);
...
const cards = [];
// ... push to cards ...
setTeamCards(cards);

Could'nt return maped item inside jsx component

Hey Guys I am trying to display a list of items according to categories,
this is my json structure. I want to display objects according to itemCategory. for e.g if there are two or more pizza they should come under one category heading.
{
"itemid": 3,
"itemName": "Veg OverLoaded",
"itemPrice": 300.0,
"itemDescription": "chessy, tasty, covered with fresh veggies",
"itemCategory": "pizza"
},
for this i created a map of objects and passed the data according to category as key.
import React, { forwardRef,useState } from 'react';
import MenuItem from './MenuItem';
import './styles.css';
import Category from '../../Home/Category'
const NewMap = new Map()
const Menu = forwardRef(({ list }, ref) => (
<>
<main ref={ref} >
{Object.values(list).map((k) => {
if (NewMap.has(k.itemCategory)){
const itemList = NewMap.get(k.itemCategory);
const newItemList = [...itemList, k];
NewMap.set(k.itemCategory, newItemList);
}else{
NewMap.set(k.itemCategory , [k]);
}
})}
<MenuItem itemMap = {NewMap}/>
</main>
</>
));
i am passing the map to MenuItem as props and trying to display objects here
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import {
cartAddItem,
cartRemoveItem,
} from '../../../../redux/cart/cart.action';
import {
selectCartItems,
selectCartItemsCount,
} from '../../../../redux/cart/cart.selector';
import ButtonAddRemoveItem from '../../ButtonAddRemoveItem';
import './styles.css';
import Accordion from 'react-bootstrap/Accordion'
import useFetchData from './newData'
const MenuItem = ({
itemMap,
cartCount,
cartList,
cartAddItem,
cartRemoveItem,
}) => {
const {
data,
loading,
} = useFetchData();
const handleQuantity = () => {
let quantity = 0;
if (cartCount !== 0) {
const foundItemInCart = cartList.find((item) => item.itemid === 1);
if (foundItemInCart) {
quantity = foundItemInCart.quantity;
}
}
return quantity;
};
return (
<>
{itemMap.forEach((key, value) => {
{console.log(value)}
<div className='container-menu' >
{console.log(value)}
<ul>
{Object.values(key).map((blah) => {
<li>
<h1>{blah.itemName}</h1>
<div className='item-contents'>
{blah.itemName}
<p className='item-contents' style={{ float: "right", fontSize: "12" }}> ₹ {blah.itemPrice}</p>
<div>
<p className='description'>{blah.itemDescription}</p>
<ButtonAddRemoveItem
quantity={handleQuantity()}
handleRemoveItem={() => cartRemoveItem(blah)}
handleAddItem={() => cartAddItem(blah)}
/>
</div>
</div>
</li>
})}
</ul>
</div>
})}
</>
);
};
const mapStateToProps = createStructuredSelector({
cartCount: selectCartItemsCount,
cartList: selectCartItems,
});
const mapDispatchToProps = (dispatch) => ({
cartAddItem: (item) => dispatch(cartAddItem(item)),
cartRemoveItem: (item) => dispatch(cartRemoveItem(item)),
});
export default connect(mapStateToProps, mapDispatchToProps)(MenuItem);
export default Menu;
i am able to console.log itemName but i am unable to display it inside jsx component. Any reason why
? what am i missing here
The foreach loop should return JSX. In your case there is no return. I suggest removing the curly brackets.
WRONG:
itemMap.forEach((key, value) => {
<>
</>
})
CORRECT:
itemMap.forEach((key, value) => (
<>
</>
))
That's valid for all the loops inside your JSX.
To return an array of elements, you should use map instead of forEach like below code, because forEach loop returns undefined, while map always returns an array.
{itemMap.map((key, value) => {
return (<div className='container-menu'>
...
</div>)
}
}
or
{itemMap.map((key, value) => (<div className='container-menu'>
...
</div>)
}

Correct item in array not always deleted. React context

I have a delete function in my Context that I'm passing ids to so I can delete components from the array however it doesn't always work correctly. If I add 3 note components to the board for example, it will always delete the last item on the board. If I add a to do list in between 2 notes, they'll delete correctly. There are 2 console logs and the deleted one shows the correct deleted item, and components shows the 2 that are left. Again, if there are 3 notes, it deletes the last item everytime, but if I do one note, one to do, then one note again, the correct item on the board is deleted.
import React, { createContext, useReducer, useState } from "react";
import ComponentReducer from "./ComponentReducer";
const NewComponentState: NewComponentsState = {
components: [],
addComponent: () => {},
deleteComponent: () => {},
};
export const NewComponentContext =
React.createContext<NewComponentsState>(NewComponentState);
export const NewComponentProvider: React.FC = ({ children }) => {
const [components, setComponents] = useState(NewComponentState.components);
const deleteComponent = (id: any) => {
for (let i = 0; i < components.length; i++) {
if(components[i].id === id) {
let deleted = components.splice(i, 1)
console.log(deleted)
setComponents([...components])
console.log(components)
}
}
}
const addComponent = (newComponent: any) => {
setComponents([...components, newComponent])
}
return (
<NewComponentContext.Provider
value={{ components, deleteComponent, addComponent }}
>
{children}
</NewComponentContext.Provider>
);
};
Board component
import React, { useContext } from "react";
import { NewComponentContext } from "../Context/NewComponentContext";
import NewComponentMenu from "./NewComponents/NewComponentMenu";
import Note from "./NewComponents/Note/Note";
import Photo from "./NewComponents/Photo/Photo";
import TodoList from "./NewComponents/Todo/TodoList";
const newComponents: any = {
1: TodoList,
2: Photo,
3: Note
}
const Board = () => {
const { components } = useContext(NewComponentContext);
const componentList = components.map((component, i) => {
const id: number = component.componentType
const NewComponent = newComponents[id]
for (const property in newComponents) {
const value = parseInt(property)
if (value == id) {
return (
<div key={i}>
<NewComponent id={component.id}/>
</div>
)
}
}
});
return (
<div className="flex space-x-10 mt-8">
<NewComponentMenu />
<div className="grid grid-cols-6 gap-8">{componentList}</div>
</div>
);
};
export default Board;

Invalid Hook Call - useState function

I am new to react js and running into below error:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
and fix this problem
The line where it fails:
const [scale, setScale] = useState(initialScale);
Code snippet is here:
import React, {
useMemo,
useState,
useEffect,
useCallback,
useImperativeHandle,
forwardRef,
Ref,
} from 'react';
import usePDF from './hooks/usePDF';
import useAnnotations from './hooks/useAnnotations';
import useTextMap from './hooks/useTextMap';
import Page from './components/Page';
import Error from './components/Error';
import ButtonGroup from './components/ButtonGroup';
import { Entity } from './interfaces/entity';
import { Annotation } from './interfaces/annotation';
import { TextLayer, TextLayerItem } from './interfaces/textLayer';
import { debug } from 'console';
interface Props {
url?: string;
data?: Uint8Array | BufferSource | string;
httpHeaders?: {
[key: string]: string;
};
initialScale?: number;
tokenizer?: RegExp;
disableOCR?: boolean;
entity?: Entity;
initialTextMap?: Array<TextLayer>;
defaultAnnotations?: Array<Annotation>,
getAnnotations(annotations: Array<Annotation>): void
getTextMaps?(textMaps: Array<TextLayer>): void;
}
const Annotator = forwardRef(({
url,
data,
httpHeaders,
initialScale = 1.5,
tokenizer = new RegExp(/\w+([,.\-/]\w+)+|\w+|\W/g),
disableOCR = false,
entity,
initialTextMap,
defaultAnnotations = [],
getAnnotations,
getTextMaps,
}: Props, ref?: Ref<any>) => {
const [scale, setScale] = useState(initialScale);
const { pages, error, fetchPage } = usePDF({ url, data, httpHeaders });
const {
annotations,
getAnnotationsForPage,
addAnnotation,
removeAnnotation: deleteAnnotation
} = useAnnotations(defaultAnnotations);
const { textMap, addPageToTextMap } = useTextMap(annotations);
useImperativeHandle(ref, () => ({ removeAnnotation }));
const removeAnnotation = (id: string) => {
deleteAnnotation(id);
};
useEffect(() => {
if (getAnnotations) {
getAnnotations(annotations);
}
if (getTextMaps) {
getTextMaps(initialTextMap || textMap);
}
}, [annotations, textMap, initialTextMap, getAnnotations, getTextMaps]);
const getTextLayerForPage = useCallback((page: number): Array<TextLayerItem> | undefined => {
if (initialTextMap) {
const found = initialTextMap.find((layer) => layer.page === page);
return found ? found.textMapItems : undefined;
}
return undefined;
}, [initialTextMap]);
const renderPages = useMemo(() => {
if (!url && !data) {
return (
<Error
message="You need to provide either valid PDF data or a URL to a PDF"
/>
);
}
console.log('i am here')
debugger
// if (error) {
// return <Error />;
// }
return (
Array(pages).fill(0).map((_, index) => {
const key = `pdf-page-${index}`;
const pageNumber = index + 1;
const page = fetchPage(pageNumber);
return (
<Page
page={page}
scale={scale}
key={key}
tokenizer={tokenizer}
disableOCR={disableOCR}
pageNumber={pageNumber}
annotations={getAnnotationsForPage(pageNumber)}
addAnnotation={addAnnotation}
removeAnnotation={deleteAnnotation}
addPageToTextMap={addPageToTextMap}
entity={entity}
initialTextLayer={getTextLayerForPage(pageNumber)}
/>
);
})
);
}, [
url, data, pages, error, scale, tokenizer, disableOCR, entity,
fetchPage, getAnnotationsForPage, addAnnotation, deleteAnnotation, addPageToTextMap, getTextLayerForPage,
]);
return (
<div className="annotator-container">
<div className="annotator-pages-container">
<div className="annotator-pages">
{ renderPages }
</div>
</div>
<ButtonGroup scale={scale} setScale={setScale} />
</div>
);
});
export default Annotator;

Resources