Localize React & React-Native components keeping them as reusable as possible - reactjs

Recently I've been trying to keep my code as reusable as possible following the Container-Component design pattern in my React and React-Native applications. I usually try to make all the components dummy and let the container do the work and pass down the data using props.
Now I'm trying to localize these components but I want to keep them reusable for further projects. So far, I have come up with to solutions:
1.- Pass every single displayed string into the component as an individual prop. Like this.
<MyAwesomeComponent
...props
string1="String1"
string2="String2"
string3="String3"
/>
2.- Pass the translation as a single object
<MyAwesomeComponent
...props
translation={translation}
/>
I personally find a better solution the first one because it becomes easier to manage default props in the component.
Which one do you think is the best approach and why? Have you find a better approach?

My final approach if it is useful for someone:
I followed #LucasOliveira approach but went a little bit further and this is what I did using ex-react-native-i18n but you can use whatever plugin you feel most comfortable with:
First I declared a helper method inside my component to return a single object with the complete translation
Pass the translation object down to the "Dummy" component
ContaninerComponent.js
class ContainerComponent extends React.Component {
...
// Load translation outside context.
loadTranslation() {
return {
string1: I18n.t('key1'),
string2: I18n.t('key2'),
string3: I18n.t('key3')
}
}
...
render() {
return(
<MyAwesomeComponent
...props
translation={this.loadTranslation()}
/>
);
}
}
Then, in the dummy component I set up a default translation, to fit the case in which the translation is not set and then I created a helper method to handle the possible not handled strings and avoid undefined values:
MyAwesomeComponent.js
const MyAwesomeComponent = ({ ..., translation }) => {
const strings = handleTranslation(translation);
return (
.... some JSX here ....
);
};
const DEFAULT_TRANSLATION = {
string1: 'Your Default String',
string2: 'Your Default String',
string3: 'Your Default String'
}
const handleTranslation = (translation) => {
if (translation === undefined) return DEFAULT_TRANSLATION;
return {
string1: (translation.string1 !== undefined) ?
translation.string1 : DEFAULT_TRANSLATION.string1;
string2: (translation.string2 !== undefined) ?
translation.string2 : DEFAULT_TRANSLATION.string2;
string3: (translation.string3 !== undefined) ?
translation.string3 : DEFAULT_TRANSLATION.string3;
}
};
And now the whole translation is safe to use from the "strings" variable.
Hope it helps!

I would go for the second approach, cause the ability to define your object outside the component declaration, gonna make your component accepts an object, a string, a date, etc... allowing you to treat then later.
Doing that :
<MyAwesomeComponent
...props
translation={translation}
/>
means our code doesn't need to know that it is being rendered , as this will be your component responsibility

Related

How to map render components that come from a JS Object without a temporary variable to hold them in?

So I came across some interesting code and I was wondering if its possible to "sugarize" it.
The components come as:
const sections = {
Home: dynamic(() => import("./Sections/Home")),
Example: dynamic(() => import("./Sections/Example")),
};
And then they are being rendered as:
export default function Page({ page }) {
return (
<main>
{page.sections.map((section, index) => {
const SectionComponent = sections[section.type] || null;
return (
<SectionComponent
key={`${pageKey}-${index}`}
id={index}
fields={section.fields}
/>
);
})}
</main>
);
}
So what would be the syntax to directly render
sections[section.type]({id: index, fields: section.fields})
to do something like this within the map, to reduce the code, without the SectionComponent variable in between?
sections[section.type]({id: index, fields: section.fields})
There is not much to gain from what you suggest, on the contrary, in the above code now you are calling the component as a function vs rendering it as an element as it was before, those are different things.
In most of the cases you want to render it as element, otherwise you may get some unexpected behavior especially with hooks.
The code you came across is the recommended approach to do it.

React conditional tag name not working - all letters downsized

I am trying to conditionally render tag name based on prop value.
Example
const SimpleTagName = `Ball${size === 'large' ? 'Large' : 'Small'}`;
return (<SimpleTagName />
but the problem is that I get rendered 'balllarge' tag with all lower case letters. What I am doing wrong ?
Try with this method:
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
Official doc ref to handle this pattern: https://reactjs.org/docs/jsx-in-depth.html#choosing-the-type-at-runtime
JSX gets converted to a React.createElement() call, so what you're doing effectively turns into:
React.createElement('balllarge')
Which is not what you want. You need to pass it an component instead of a string, but you can still determine that dynamically, like so:
import { BallLarge, BallSmall } from './Balls' // or whatever
const Component = ({ size }) => {
const BallComponent = size === 'large' ? BallLarge : BallSmall
return <BallComponent />
}
(If you have more than two options, you may need a different way to handle the mapping between your props and variable types, but the principle remains the same: assign a component to a variable, and then use when rendering.)

React High Order Components

Lets says I have an array of objects where each object have structure like below
obj = {
name:"Name1",
description:"Description1",
activeState:1,
rating:5
}
const User = (obj) => {
let userActiveState = (obj.activeState === 1) ? 'Active' : 'Non Active';
return (
<tr>
<td>{obj.name}</td>
<td>{obj.description}</td>
<td>{userActiveState}</td>
</tr>
);
}
User.propTypes = {
name: PropTypes.string
description: PropTypes.string
activeState: PropTypes.number
}
User.defaultProps = {
name: "Not Available"
description:""
activeState: 0
}
I use this array to create UI using User(a stateless functional react component), but before the stateless functional react component spits out the UI I want to make some modification to the object properties which are required by the
UI (example using text instead of number for activeState) and not all the object properties are required too.
Where would I remove unwanted properties so that I can have defaultProps and proptypes defined only for the required properties and would I use a high order component which transforms and filters the obj properties?
You don't need a HoC here - a simple composition is enough:
const Component = ...;
const ComponentButDifferentFormats = ({propToSkip, propToRename, ...props}) => (
<Component
{...props}
propRenamed={propToRename}
propX={parseFloat(props.propX)}
propY={'' + props.propY}
/>
);
With this approach, you'll decouple the transformation logic from the real UI. It's really useful for example with an API response. Creating a HoC is also an option: it might be parametrized with formats, filters or even the component itself. One more function in the above example: Component => ....

Getting component propTypes as string

I will like to make a wrapper component that will automatically display its children's proptypes as a string.
Example
For example, my component may be:
export class abc extends Component {
static propTypes = {
disabled: PropTypes.bool,
text: PropTypes.oneOf(['a','b'])
}
}
The wrapper will take in the component:
<Wrapper>
<abc />
</Wrapper>
And output HTML that looks like:
<pre>
disabled: PropTypes.bool
text: PropTypes.oneOf(['a', 'b'])
</pre>
What I have tried
I have tried extracting props directly from the actual children, and ended up with a wrapper component that looks like this:
const wrapper = props => (<pre>
{Object.entries(props.children.type.propTypes).map((entry) => (
<div key={entry[0]}>{entry[0]}: {getPropTypeFromFunction(entry[1])}<br/></div>
))}
</pre>)
where the getPropTypeFromFunction method is:
const getPropTypeFromFunction = func => {
for (const k in PropTypes) {
switch (func) {
case PropTypes[k]:
return k
case PropTypes[k].isRequired:
return `${k}.isRequired`
default:
break;
}
}
return "Unknown PropType"
}
This however does not work when we get non-primitive (?) proptypes (eg PropTypes.onOf(['a', 'b']). Also it feels kind of hacky to have to deal with something seemingly so simple this way.
Is there some kind of elegant solution to this (ideally without any external libraries?)
Ended up using react-docgen, which uses recast internally. This works by parsing the entire source file as a string into an Abstract Syntax Tree (AST) which is then used to generate the doc. There are some libraries using this to implement documentation, such as storybook.js and their info add-on, which I ended up using.

When to use React createFragment?

I'm rendering a ListView in React Native, managed to fix this React warning but I don't understand how and why it works:
Warning: Any use of a keyed object should be wrapped in React.addons.createFragment(object) before being passed as a child.
// This array generates React Fragment warning.
var data1 = [{name: "bob"}, {name:"john"}]
// This array works, no warnings.
var data2 = [React.addons.createFragment({name: "bob"}),
React.addons.createFragment({name: "john"})]
// This also works, no warnings.
var data3 = ["bob", "john"]
class Listings extends React.Component {
constructor(props) {
super(props)
ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})
this.state = {
dataSource: ds.cloneWithRows(data),
}
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{rowData}</Text>} />
)
}
}
What is a React Fragment? When is it needed in React? Why does a keyed object cause this warning?
I am dividing my answer in 2 sections to cover the 2 main points I see in your question.
Section 1: What is a React Fragment?
In order to explain what a React Fragment is, we need to take step back and talk about React Keys.
Keys help React identify which items have changed, are added, or are removed. They should be given to the elements inside an array to give the elements a stable identity (uniqueness).
The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys. Here's a practical example:
render() {
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
return todoItems;
}
It's important to note that the todoItems is actually an array of <li> elements.
Knowing that, let's move on to why you receive a warning in your use-case.
In most cases, you can use the key prop to specify keys on the elements you're returning from render, just like the example above. However, this breaks down in one situation: if you have two sets of children that you need to reorder, there's no way to put a key on each set without adding a wrapper element.
Here's the classical example from the recently updated React docs:
function Swapper(props) {
let children;
if (props.swapped) {
children = [props.rightChildren, props.leftChildren];
} else {
children = [props.leftChildren, props.rightChildren];
}
return <div>{children}</div>;
}
The children will unmount and remount as you change the swapped prop because there aren't any keys marked on the two sets of children.
To solve this problem, you can use the createFragment add-on to give keys to the sets of children. Follow the enhanced example, using the createFragment here.
Section 2: But why you are getting this warning?
Anyways, the error you're getting error happens simply because you try to interpolate a JavaScript object (rather than a JSX element or string) into some JSX.
Although the error message suggests using createFragment to fix this, the reason is because you're interpolating a variable into some JSX that is not a string or JSX element, but is in fact some other kind of object!
Such a misleading warning in your use-case, isn't it? :-)
You can import Fragment from 'react' as:
import React, { Fragment } from "react";
Then update your render function as:
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => {
<Fragment key={passsKey}>
<Text>{rowData}</Text>
</Fragment>
}}
/>
)}

Resources