I am new to React.
I used ReactIntl.FormattedPlural to format the plural.
<FormattedPlural
value={10}
one='message'
other='messages'
/>
It works when I place this component in the render() function. However, I have a scenario that I want to get the String and passed into a function.
Instead of Using {value ==1 ? 'message' : 'messages'}, can I use ReactIntl.FormattedPlural to achieve this?
In any case, in the end you need to put it into the render.
Because the FormattedPlural is a component. To display it, you need to render it. It's how React works.
You can save it as variable:
const text = (<FormattedPlural value={10} one='message' other='messages' />)
For example if you have a condition and need to decide which string to render.
But in the end, the text should passed to the render to be displayed for user.
Derived from FormattedPlural implementation:
const { formatPlural } = useIntl()
// get the plural category ("one" or "other")
const pluralCategory = formatPlural(count);
const options = {
one: "message",
other: "messages",
}
const plural = options[pluralCategory as 'one'] || options.other;
See also: https://formatjs.io/docs/react-intl/api/#formatplural
Related
Given a string with the format:
'This is a string with some ::FaIconUpload:: icons in it as ::FaIconDownload:: these two.'
I'd like to split it using a RegEx and replace the coincidences with some React components. The string would have the type of component (FaIcon) and a props in it such as the name of the icon (Upload).
The objective with this is to be able to use React components within translated strings, and the expected return value would be something like:
[
'This is a string with some ',
<FaIcon iconName="Upload" />,
' in it as ',
<FaIcon iconName="Download" />,
' these two.'
]
The method
Currently, I've got a method which returns either a string or an array. This is compatible with React render methods, since if we return an array it will be capable of rendering any components on that array.
As I'll use it to translate some strings, I've created this custom hook:
const useCustomTranslation = () => {
const { t } = useTranslation();
const tlt = (...args) => {
// const str = t(...args);
const testStr =
'This is a string with some ::FaIconUpload:: icons in it as ::FaIconDownload:: these two.';
const reg = /(?<=::FaIcon)(.*?)(?=::)/g;
const preStrAr = testStr.split(reg);
console.log(preStrAr);
};
return { tlt };
};
The problem
Currently, this method is logging this:
[
"This is a string with some ::FaIcon",
"Upload",
":: icons in it as ::FaIcon",
"Download",
":: these two."
]
As you can see, it's not including the ::FaIcon and the final ::, as I haven't been able to find the right Regex to do so. But even if I got to that point, I feel like then I should have to re-iterate through the array to replace the strings with the right component, again using Regex to see if the array item matches the right format.
I find this somehow overcomplicated, and I think there has to be a much cleaner and easy way to get it (correct me if maybe I'm wrong and this is the only way).
Is there any way where I can split a string using a Regex, using part of the matched group to replace the string by another content using that matched string?
Perhaps you meant to do this?
/::FaIcon(.*?)::/ without the look
const str = `This is a string with some ::FaIconUpload:: icons in it as ::FaIconDownload:: these two.`
const newText = str.replace(/::FaIcon(.*?)::/g,function(_,match) {
return `<FaIcon iconName="${match}" />`
})
console.log(newText)
To make an array you can do
const str = `This is a string with some ::FaIconUpload:: icons in it as ::FaIconDownload:: these two.`
const newText = str.replace(/\s?::FaIcon(.*?)::\s?/g,function(_,match) {
return `::<FaIcon iconName="${match}" />::`
}).split("::")
console.log(newText)
Finally, I've made it using (sadly) the re-iteration method, as it's the only way I can see it would work. Thanks to #mplungjan for his first answer, which gave me the hints to get it working:
export const replaceIconInStr = (str) => {
// Matches the whole icon component pattern
const regComponent = /(::FaIcon.*?::)/g;
// Matches just the component prop we need
const regIconName = /::FaIcon(.*?)::/g;
// Split the string by the component pattern
const splittedStr = str.split(regComponent);
// If there are any matches
if (splittedStr.length) {
// Match the elements in the array and get the prop to replace it by the real component
return splittedStr.map((el) => {
const matched = regIconName.exec(el)?.[1];
if (matched) {
return <FaIcon iconName={matched} />;
}
return el;
});
}
// If there is no pattern matching, return the original string
return str;
};
So I'm trying to make a screen where data from user's localstorage is used (Lets call it var1) but I'm getting Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. error. What I'm trying to do is check if the data from user's localstorage exists and if it does then it will put that data into a state but first it will grab another variable from localstorage (User auth token, lets call it var2) and put it into every object in var1 (var1 is a list which contains objects) and this is done using map then the state is set to the changed var1 with the auth token(or var2), then it returns some HTML and some logic is used in HTML, For every object in var1 it will create a new select tag with numbers ranging from 1 to 20 and this is done using mapping an array with 20 numbers (I'm doing this because I could not get for loop to work properly) and if the current number of option in select tag matches a key value pair in one of var1's object then it will
select the option tag or put selected attribute on option tag and if you change the value of select tag then it will trigger a function which will map through var1 and select the object which user requested and change the value of quantity to whatever the user selected on select tag. I tried to cut down and simplify my code as much as I could. My code is like this:
function RandomScreen() {
const [var1, setvar1] = useState([])
let localstoragevar = JSON.parse(localStorage.getItem('var'))
let newCart = []
if (localstoragevar) {
localstoragevar.map(item => {
item.authtoken = localStorage.getItem('AuthToken')
newCart.push(item)
})
}
setvar1(newCart)
let twenty = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
return (
{var1.map(newItem => {
{/* HTML code goes here */}
{twenty.map(number => {
if (number == item.quantity) {
return (
<option onChange={handleClick} selected name={newItem.id} value={newItem.quantity}>{newItem.quantity}</option>
)
} else {
return (
<option onChange={handleClick} name={newItem.id} value={number}>{number}</option>
)
}
})}
})}
)
}
Your render calls setvar1 which in turn trigger's a re-render.
You should put this whole logic inside a useEffect hook:
useEffect(() => {
let localstoragevar = JSON.parse(localStorage.getItem('var'))
let newCart = []
if (localstoragevar) {
localstoragevar.map(item => {
item.authtoken = localStorage.getItem('AuthToken')
newCart.push(item)
})
}
setvar1(newCart)
}, []);
This is what you have to do is to avoid logic in your render function. For that case, we do have useEffect function plus on top of that you may add useMemo;
I want to display my custom Tire element or simple div (depending on media queries) with different values on each call. But the ternary operator doesn't work (always displays Tire element, never div) and I'm getting "undefined" instead of string "1" that should be it's content.
Would anyone be so kind as to explain where I went wrong?
const Content = () => {
const isDesktop = window.matchMedia('(min-width: 110em)')
const displayTire = (props) => (isDesktop.matches
? <Tire className={`${props.title}`}>{`${props.content}`}</Tire>
: <div className={`${props.title}`}>{`${props.content}`}</div>)
displayTire.propTypes = {
title: PropTypes.string.isRequired,
content: PropTypes.string.isRequired
}
return (
{displayTire('pneu1', '1')}
)
export default Content
The first argument you pass is 'pneu1'.
This is assigned to the props variable.
You then read props.title.
Strings don't have title properties so the value is undefined.
Your prop types says that the first argument should be an object which contains two properties (title and content).
Pass an object that matches the definition instead of two plain strings.
You're not actually providing a title and content to the props of displayTire.
To do that, you should have done:
return (
{displayTire({ title: 'pneu1', content: '1'})}
)
I am working with a child component where an array item is passed as prop. However, that item also contains another array where there should be null and undefined check before rendering. Adding || [] to each filed didn't seem the best way. Is there a better way that I am missing?
const ChildComponent= ({ newsItem, textColor, newsItemHeadingColor }) => {
const imageFormats = newsItem?.itemImage?.formats;
const imageSrc = useProgressiveImage({ formats: imageFormats });
const history = useHistory();
const { state } = useContextState();
const { schemeMode, colorSchemes } = state;
const { itemTagColor, itemTagTextColor } = getThemeColors({
schemeMode,
colorSchemes,
colors: {
itemTagColor: newsItem?.itemTagColor?.name,
itemTagTextColor: newsItem?.itemTagTextColor?.name,
},
});
return (
<NewsItem
onClick={() => {
const url =
newsItem?.itemTag === 'Lab'
? getPageLink({
page: newsItem?.itemLink || [], <<<<<< this check
})
: newsItem?.itemLink || []; <<<<<< this check
url && history.push(url);
}}
>
<ImageContainer>
<Image src={imageSrc} alt={`${newsItem?.itemImage || []}`} /> <<<<<< this check
</ImageContainer>
<NewsItemTag color={itemTagColor} itemTagTextColor={itemTagTextColor}>
<Body4>{`${newsItem?.itemTag || []}`}</Body4> <<<<<< this check
</NewsItemTag>
<NewsItemName newsItemHeadingColor={newsItemHeadingColor}>{`${
newsItem?.itemTitle || [] <<<<<< this check
}`}</NewsItemName>
<Description textColor={textColor}>{`${
(newsItem.description &&
newsItem?.itemDescription) || [] <<<<<< this check
}`}</Description>
</NewsItem>
);
};
In fact yes, you have many many ways for improving your code and the readabilty of it.
In your <NewItem /> onClick function:
I have nothing to say for the first one, it seems correct since you want the item link or an empty array
For the second one, your are already using the optional chaining operator, the "?". This operator check if there is a value, if not, it returns null. That means that should be enough, since you check after your url variable is defined or not. So simply write that : : newsItem?.itemLink
For the third one, the one on your <Image /> component, First there is no need at all to put everything in backquote. Simply alt={newsItem?.itemImage || []} should work fine. Then, alt attribute is taking a string, so maybe change it with alt={newsItem?.itemImage || ""}
For the three last one, they are basically the same, you have content to display and you want to display nothing if there are empty. The fact here is that your are rendering some elements even if there is no content and this is not an optimized way. The cleanest way in my opinion in order to make your template more readable will be to define variable before your return function and based on this variable, you will display the appropriate section :
Before your return function:
const hasTag = !!newsItem?.itemTag;
const hasTitle = !!newsItem?.itemTitle;
const hasDescription = !!newsItem.description && !!newsItem?.itemDescription;
In your template:
{hasTag && <NewsItemTag color={itemTagColor} itemTagTextColor={itemTagTextColor}>
<Body4>{newsItem.itemTag}</Body4>
</NewsItemTag>}
{hasTitle && <NewsItemName newsItemHeadingColor={newsItemHeadingColor}>
{newsItem.itemTitle}
</NewsItemName>
{hasDescription && <Description textColor={textColor}>
{newsItem.itemDescription}
</Description>
I hope this was clear, hit me up if this wasn't.
Happy coding :)
There are multiple ways that you could handle this in javascript, assume that we have a component that receives a prop called newsItem which is an object
You could do that in the following ways
Using the logical operator or (es6)
const itemLinks = newsItem.links || []
you dont need the optional chaining operator (?.) here if you know that newsItem is an object and not undefined.
This is the shortest syntax, and it works fine as long as links is either undefined or an array
however it becomes problematic if later links become a boolean variable, which will yield incorrect results
This method is not safe, and it becomes cumbersome to handle as properties become nested deeply
Using lodash get method or some other library or a custom solution (es6)
because in es6 the only practical way was to either the logical operator and this was a common problem, custom solutions were brought it, basically any solution should have two advantages over the logical or operator
They should work fine with boolean values
They should be easier to work with deeply nested values
custom someDeepleyNested = get(newsItem, 'property1.property2.property3', 'defaultValue')
// there is no need for property1, property2, property3 to exist at all
Optional chaining operator and Nullish coalescing Operator (stage 4 finished proposals)
Because this was a common problem for all javascript developers,the language had to bring a native solution
const someNestedProperty = newsItem?.property1?.property2?.property3 ?? 'someDefaultValue'
// notice that both the Optional chaining operator and Nullish coalescing Operator, work with both undefined and null
Specific to react: use default props
const Component = ({newsLink}) => {}
Component.defaultProps = {
newsItem: {
itemLinks: []
}
}
See this gist for the complete picture.
Basically I will have this form:
When you click the plus, another row should appear with a drop down for day and a time field.
I can create the code to add inputs to the form, however I'm having trouble with the individual components (selectTimeInput is a row) actually updating their values.
The onChange in the MultipleDayTimeInput is receiving the correct data, it is just the display that isn't updating. I extremely new to react so I don't know what is causing the display to not update....
I think it is because the SelectTimeInput render function isn't being called because the passed in props aren't being updated, but I'm not sure of the correct way to achieve that.
Thinking about it, does the setState need to be called in the onChange of the MultipleDayTimeInput and the input that changed needs to be removed from the this.state.inputs and readded in order to force the render to fire... this seems a little clunky to me...
When you update the display value of the inputs in state, you need to use this.setState to change the state data and cause a re-render with the new data. Using input.key = value is not the correct way.
Using State Correctly
There are three things you should know about
setState().
Do Not Modify State Directly
For example, this will not re-render a
component:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
The only place where you
can assign this.state is the constructor.
read more from Facebook directly here
I would actually suggest a little bit of a restructure of your code though. It's not really encouraged to have components as part of your state values. I would suggest having your different inputs as data objects in your this.state.inputs, and loop through the data and build each of the displays that way in your render method. Like this:
suppose you have one input in your this.state.inputs (and suppose your inputs is an object for key access):
inputs = {
1: {
selectedTime: 0:00,
selectedValue: 2
}
}
in your render, do something like this:
render() {
let inputs = Object.keys(this.state.inputs).map((key) => {
let input = this.state.inputs[key]
return (<SelectTimeInput
key={key}
name={'option_' + key}
placeholder={this.props.placeholder}
options={this.props.options}
onChange={this.onChange.bind(this, key)}
timeValue={input.selectedTime}
selectValue={input.selectedValue}
/>)
)}
return (
<div>
<button className="button" onClick={this.onAddClick}><i className="fa fa-plus" /></button>
{ inputs }
</div>
);
}
Notice how we're binding the key on the onChange, so that we know which input to update. now, in your onChange function, you just set the correct input's value with setState:
onChange(event, key) {
this.setState({
inputs: Immutable.fromJS(this.state.inputs).setIn([`${key}`, 'selectedTime'], event.target.value).toJS()
// or
inputs: Object.assign(this.state.inputs, Object.assign(this.state.inputs[key], { timeValue: event.target.value }))
})
}
this isn't tested, but basically this Immutable statement is going to make a copy of this.state.inputs and set the selectedTime value inside of the object that matches the key, to the event.target.value. State is updated now, a re-render is triggered, and when you loop through the inputs again in the render, you'll use the new time value as the timeValue to your component.
again, with the Object.assign edit, it isn't tested, but learn more [here]. 2 Basically this statement is merging a new timeValue value in with the this.state.inputs[key] object, and then merging that new object in with the entire this.state.inputs object.
does this make sense?
I modified the onChange in the MultipleDayTimeInput:
onChange(event) {
const comparisonKey = event.target.name.substring(event.target.name.length - 1);
const input = this.getInputState(comparisonKey);
input.selected = event.target.value;
input.display = this.renderTimeInput(input);
let spliceIndex = -1;
for (let i = 0; i < this.state.inputs.length; i++) {
const matches = inputFilter(comparisonKey)(this.state.inputs[i]);
if (matches) {
spliceIndex = i;
break;
}
}
if (spliceIndex < 0) {
throw 'error updating inputs';
}
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
}
The key points are:
// re render the input
input.display = this.renderTimeInput(input);
// set the state by copying the inputs and interchanging the old input with the new input....
this.setState({
inputs: [...this.state.inputs].splice(spliceIndex, 1, input)
});
Having thought about it though, input is an object reference to the input in the this.state.inputs so actually [...this.states.inputs] would have been enough??