React conditional tag name not working - all letters downsized - reactjs

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.)

Related

Why React.Memo() keeps rendering my component

I made an example to get to know more deeply the React.Memo().
I tried to pass a string from the parent to the child. then the component wasn't rendred except the first default rerending.
But When I passed an array from the parent to the child the components get re-render.
So why the component keeps re-render when passing arrays and objects and not re-render when passing string.
Is that related to the reference values and primitive values of javascript?
here's the code for what was I trying to do.
the parent:
import Child from "./Child";
export default function App() {
const name = {
firstName: "Amr"
};
return <Child name={name} />;
}
The Child
import React from "react";
const Child = ({ name }) => {
const name1 = "Amr";
const name2 = "Amr";
// console.log(name1 === name2);
const obj1 = {
name: "Mohamed"
};
const obj2 = {
name: "Mohamed"
};
const arr1 = ["Amr"];
const arr2 = ["Amr"];
console.log(arr1 === arr2);
// console.log(obj1 === obj2); => obj1 => refrence number 1
// obj2 => refrence number 2
// 1 === 2;
// false
areEqual(name, {
firstName: "Amr"
});
return <h2>{name.firstName}</h2>;
};
// javascript [] {} => refrence value
// javascript '' or anythinng primtive value;
// obj1 = 1, obj2 = 2;
// obj1 === obj2 result false 1 === 2
function areEqual(prevProps, nextPops) {
if (prevProps === nextPops) {
console.log("equal");
} else {
console.log("not equal");
}
}
export default React.memo(Child, areEqual);
I used a function also areEqual that returns true or false based on if the prevProps and nextProps are equal or not.
This function already returns for me true when I pass a String and false when I pass an array.
So, am I right or there's something missing.
Thanks
UPDATE:
If what I mentioned is right, so the React.memo() will be useless.
Is that Right?
By default React.memo() does a shallow comparison of props and objects of props - and this is why you were experiencing rerenders when props were of type 'object', because reference recreates on each rerender.
React.memo receives two argument(2nd one is optional), and second one is custom equality function which you can use in order to stabilize rerenders when having props as non primitive values. So, for example, you can do something like this: React.memo(SomeComponent, (prevProps, currentProps) => isDeepEqual(prevProps.someObject, currentProps.someObject)).
Meaning that React.memo is not useless, it just uses shallow comparison by default, but also offers you posibillity to provide it with custom equality checker which you might wanna use in your example. Either you shall use deep equality check within React.memo, or you shall stabilize props in parent component by wrapping their value with useMemo.
It depends on their parent components some times it is necessary React.memo
But in your case, as you said, its reference changes every time you define an array or object. I recommend you to use React.useMemo. For example:
const user = React.useMemo(() => {
return {
firstName: "Amr"
}
}, []); // Second parameter works same as useCallback
React.memo wont re-render the component because user props didn't change

I am trying to dynamically create a Mui icon using React.createElement. However, React lowercases the element. Is there a way to keep case?

I am getting service.icon from JSON, so it looks like so
[
{
"icon": "AdminPanelSettingsIcon",
}
]
I am using React.createElement() like so,
data.map((service) => {
let icon = React.createElement(
service.icon,
{ key: service.icon },
null);
});
my output is <adminpanelsettingsicon></adminpanelsettingsicon>
is there anyway to keep case, or convert to PascalCase so it renders like so <AdminPanelSettingsIcon></AdminPanelSettingsIcon>
Note: Is there anyway to dynamically display mui icons based on specified JSON aka api call.
The docs specify:
React.createElement(
type,
[props],
[...children]
)
The type argument can be either a tag name string (such as 'div' or
'span'), a React component type (a class or a function), or a React
fragment type.
You're attempting to create an element using the string "AdminPanelSettingsIcon". Instead you should be attempting this by passing in the class:
React.createElement(AdminPanelSettingsIcon)
You can do this by converting your list to use classes as the value:
[
{
// The value is a class now, not a string
"icon": AdminPanelSettingsIcon,
}
]
Update:
If you are using https://www.npmjs.com/package/#material-ui/icons (or any other library), you can dynamically import all of the components like this:
//Preparing, it should run inside async function
let maps = {}
let listIcon = [];
await import('#material-ui/icons').then(res => {
listIcon = res
})
for(let icon of listIcon){
maps[icon.constructor.name.toLowerCase()] = icon;
}
//Render
data.map((service) => {
const component = maps[service.icon] || DefaultComponent;
let icon = React.createElement(
component,
{ key: component },
null);
});
Original answer:
If the API output is predictable (service.icon is just a component of the components you defined)
You can do this:
//Import...
//Defined maps
const maps = {
"adminpanelsettingsicon": AdminPanelSettingsIcon,
"adminpanelsettingsicon2": AdminPanelSettingsIcon2,
"adminpanelsettingsicon3": AdminPanelSettingsIcon3,
}
//Render
data.map((service) => {
const component = maps[service.icon] || DefaultComponent;
let icon = React.createElement(
component,
{ key: component },
null);
});
It'll work as you wish
Simple way to do this,
In your .jsx file :
import Icon from '#mui/material/Icon';
export default function MyComponent() {
const iconName = `home`
return (<Icon>{iconName}</Icon>);
}
As a prerequisite, you must include this line in yout index.html
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
Voilà !
Sources :
Mui DOC

How can I desctructure items out of an object that is returned from a React hook using Typescript?

I have a component that needs to tap into the React Router query params, and I am using the use-react-router hook package to access them.
Here is what I am wanting to do:
import React from "react;
import useReactRouter from "use-react-router";
const Foo = () => {
const { id } = useReactRouter().match.params;
return (
<Bar id={id}/>
)
}
The issue is that this throws the following error in VS Code, and at compile time:
Property 'id' does not exist on type '{}'.ts(2339)
I have found that if I refactor my code like so:
const id = match.params["id"], I do not get the error, but I feel like this is not the correct approach for some reason. If someone could point me in the right direction, I would appreciate it.
I figured it out. The solution was to include angle brackets between the hook's name and the parenthesis, like so:
const { match } = useRouter<{ id: string }>();
const { id } = useRouter<{ id: string }>();
Or if you prefer nested destructuring:
const { match: { params: id } } = useRouter<{ id: string }>();
You can try to give default value to params
const { id } = useReactRouter().match.params || {id: ""};
It may be possible that params to be null at initial level
The code is insufficient.
However, at first glance,
// right way
const { history, location, match } = useReactRouter()
// in your case
const { match: { params : { id } } } = useReactRouter()
// or
const { match } = useReactRouter()
const { id } = match.params
now, try to console the value first.
Also, please try to pass the props to a functional component from it's container, since it's more logical.
From your comment below, i can only assume you solved it. Also, it's recommended to handle possible undefined values when you use it.
{ id && id }
However, the first step should've been consoling whether it has value in it,
console.log('value xyz', useReactRouter())

React wrapper: React does not recognize the `staticContext` prop on a DOM element

I'm trying to create a wrapper component around the react-router-dom NavLink component.
I would like my custom component to accept all of NavLinks props, and proxy them down to NavLink.
However when I do this, I'm getting:
Warning: React does not recognize the staticContext prop on a DOM
element. If you intentionally want it to appear in the DOM as a custom
attribute, spell it as lowercase staticcontext instead. If you
accidentally passed it from a parent component, remove it from the DOM
element.
A working demo of the issue can be found here:
https://codesandbox.io/s/w0n49rw7kw
There is a way to overcome that is using:
const { to, staticContext, ...rest } = this.props;
So your ...rest will never contain staticContext
This is a common problem with a simple solution as documented in the React documentation:
The unknown-prop warning will fire if you attempt to render a DOM
element with a prop that is not recognized by React as a legal DOM
attribute/property. You should ensure that your DOM elements do not
have spurious props floating around.
The spread operator can be used to pull variables off props, and put
the remaining props into a variable.
function MyDiv(props) {
const { layout, ...rest } = props
if (layout === 'horizontal') {
return <div {...rest} style={getHorizontalStyle()} />
} else {
return <div {...rest} style={getVerticalStyle()} />
}
}
You can also assign the props to a new object and delete the keys that
you’re using from the new object. Be sure not to delete the props from
the original this.props object, since that object should be considered
immutable.
function MyDiv(props) {
const divProps = Object.assign({}, props);
delete divProps.layout;
if (props.layout === 'horizontal') {
return <div {...divProps} style={getHorizontalStyle()} />
} else {
return <div {...divProps} style={getVerticalStyle()} />
}
}
This happens because you probably used {...props} somewhere in your component.
Example from React:
function MyDiv(props) {
const { layout, ...rest } = props
if (layout === 'horizontal') {
return <div {...rest} style={getHorizontalStyle()} />
} else {
return <div {...rest} style={getVerticalStyle()} />
}
}
We grab layout separately so that it won't be contained in {...rest}.
The given answer by the React docs was not quite good enough for my situation, so I found/developed one which isn't perfect, but is at least not so much of a hassle.
You can see the Q/A in which it arose here:
What is Reacts function for checking if a property applies?
The gist is, use a function to pick the bad props out for you.
const SPECIAL_PROPS = [
"key",
"children",
"dangerouslySetInnerHTML",
];
const defaultTester = document.createElement("div")
function filterBadProps(props: any, tester: HTMLElement = defaultTester) {
if(process.env.NODE_ENV !== 'development') { return props; }
// filter out any keys which don't exist in reacts special props, or the tester.
const out: any = {};
Object.keys(props).filter((propName) =>
(propName in tester) || (propName.toLowerCase() in tester) || SPECIAL_PROPS.includes(propName)
).forEach((key) => out[key] = props[key]);
return out;
}
Personally, I felt that the warning was completely useless in the first place, so I added a line which skips the check entirely when not in development mode (and warnings are suppressed). If you feel that the warnings have merit, just remove the line:
if(process.env.NODE_ENV !== 'development') { return props; }
You can use it like this:
public render() {
const tooManyProps = this.props;
const justTheRightPropsForDiv = filterBadProps(tooManyProps);
const justTheRightPropsForSpan = filterBadProps(tooManyProps, document.createElement("span"));
return (<div {...justTheRightPropsForDiv}>
<span {...justTheRightPropsForSpan} />
</div>)
}
If someone has this issue with react-admin, check if you don't have a Link as a child of Admin. Like this:
<Admin layout={props => <Layout/>}>
<Link to="/something">something</Link> <-- causing issue
</Admin>
Just move it to another component. For instance, inside the Layout.
I got the same issue when passing data in child component with camelCase property.
Warning: React does not recognize the moreInfo prop on a DOM element.
If you intentionally want it to appear in the DOM as a custom attribute,
spell it as lowercase moreinfo instead. If you accidentally passed it
from a parent component, remove it from the DOM element.
<CenteredModal
moreInfo={viewType}
/>
To fix that error, I used all lowercase letters for property.
<CenteredModal
moreinfo={viewType}
/>

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

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

Resources