I am working on a Text component that has a prop as, this can be any valid text dom element i.e. <Text as="p" /> or <Text as="h1" />. Within component itself I want to render respective dom element and thus far I ended up with a big switch statement for this, however I am wondering if there is a better approach that is less verbose?
I tried looking at ui libs like chakra and material ui that have same patter, but quiet frankly wasn't able to figure it out form there.
I would then have an object store it somewhere outside of your component like in constants called e.g htmlOptionTag there you can have every expected props "as" :
const htmlOptionTag = {
"a": ({label, ...rest}) => <a {...rest}>{label}</a>,
"div": ({text, ...rest}) => <div {...rest}>{text}</div>,
.....
};
Then import and use it inside Text component :
return htmlOptionTag[as](props) || null;
I created a function domElement and it accepts two parameters :
tag and their attributes.
/**
* #param {*} tag "a"
* #param {*} attr {"href":"https://example.com", "class":"custom-class"}
* #returns
*/
const domElement = (tag, attr)=>{
let ele = document.createElement(tag);
if(Object.keys(attr).length > 0){
for(let i in attr){
ele.setAttribute(i,attr[i]);
}
}
return ele;
}
Now if you run domElement("a", {"href":"https://example.com", "class":"custom-class"})
It will return :
Note : You can also pass function as {"onclick" : ()=>{console.log("I am a function")}}
You can also tweak my answer to ReactJS specific. I don't have much time right now, so I just gave you a hint on how you can minimize your code without having ifelse or switch statements.
After playing with this I discovered that react has React.createElement function that takes in dom element strings like a or h1 for example, so to achieve my goals all I had to do was
return React.createElement('a', otherProps)
Related
I am wondering what is the standard way to add additional input field from onClick() on a button. A scenario might be adding a Todo-list item field on click.
My approach was having a state array that stores the actual component and concat the array.
const[ComponentList, setComponentList] = useState([<Component id={1}/>]);
And then...
function addAnotherQuestion() {
setComponentList(ComponentList.concat(generateComponent(currentID)));
}
I was told this is a very bad idea and I would end up with messed up inputs because of stale states (which I did, and then I solved by writing to Redux store directly from the child component). This is not an ideal solution, so I want to know what is the standard way to do this?
I would store only inputs data in array like so:
const [inputs, setInputs] = useState(["some_id_1", "some_id_2", "some_id_3"]);
function addAnotherQuestion() {
setInputs(inputs.concat(currentID));
}
and then render them separately:
<>
{ inputs.map((id) => <Component key={id} id={id}/>) }
</>
How about keeping an array of Id's instead of components?
const[ComponentListIds, setComponentListIds] = useState([1]);
Why do you need to generate the components? What you should probably do, is generating new Id's instead. And then render the components within the render part of you component with:
render(
...
ComponentListIds.map(id=> <Component key={id} id={id}>)
...
)
I'm building a front application with reactjs and material-ui. I have Form that call Field components.
To have more beautiful forms i use Tabs. So i follow the material-ui doc that use TabPanel function to wrap tab content. But i made a mistake, i put function inside my component Test
export default function Test(props) {
function TabPanel(props) {
const { children, value, index, ...other } = props;
return value === index && <Box p={3}>{children}</Box>;
}
}
Instead of doing this :
function TabPanel(props) {
const { children, value, index, ...other } = props;
return value === index && <Box p={3}>{children}</Box>;
}
export default function Test(props) {
}
With the first version, i lost my focus on my input field after each change. On the second version everything was ok.
Could you tell me why it's different.
Thanks and regards
The first scenario is as this:
Because you have declared your TabPanel renderer function with the keyword function, it won't be bound to the scope of your functional component Test, in order to do this, you'll have to do a lot more job -if you chose the purest JS way-, bounding a function to it's Direct Parent Scope(Test) makes it statically preserve the first copy of it(TabPanel) during the life time of the parent.
In other words, bounding your TabPanel function to the local scope of the Test function will preserve the same copy of the TabPanel function.
While, if it's not bound, a new function TabPanel will be created and used each time a render happens in the Test component, thus, it'll entirely re-render your input element and then lose it's focus.
to make the first scenario work you can use the ES6 Arrow Function, as this:
export default function Test(props) {
const TabPanel = (props) => {
const { children, value, index, ...other } = props;
return value === index && <Box p={3}>{children}</Box>;
}
}
Why would that work? but not the function keyword way? because ES6 Arrow Functions Auto bind them self to their Lexical Scope -The scope when they where created(Test() Scope)-, remember, they do it automatically, there is a bunch of stuff that happen under the hood in order for that to happen..
While the second way works just fine because JS will keep a copy of your function in the global scope, in our case window is the global scope, it'll be something like window.TabPanel, and it'll be preserved the first time JS goes into this file, so, extracting it out of the Test function into the global scope will protect it from re-creating itself over and over again whenever a re-render occurs..
I hope I was able to deliver the idea..
If you are willing to understand how binding happens, you'll need to go through the prototype nature of JS.
Following some code examples, I've found this:
<Slider ref = {c => (this.slider = c)} {...this.settings}>
{
//custom component for slider content
}
</Slider>
I don't get what's the meaning of ref = {c => (this.slider = c)} {...this.settings}. What is this doing? this.settings is an object with various properties, like arrows:false, mobilefirst:true. But I don't know this construct of ref etc. and in the example is not explained.
Is there a guide for this?
This code creates reference to the element to work with it later - it is stored on the class and can be accessed with this.slider or passed down as a prop to children. For example, it can be used to set focus just like with regular HTML element: this.slider.focus(). You can read more about callback refs here: https://reactjs.org/docs/refs-and-the-dom.html#callback-refs
I am looking for a way to pass an array of components to a prop for a tabbing component. Just wondering if that's possible.
So I need to create a component that will shorten material ui's tabbing method but I cannot find a way to pass an array of components as a prop so that it will be rendered on that component.
Here is an example of my code:
<FullWidthTab
components = [<Component1/>, <Component2/>] //this is where components renders
menuLabels = ['Menu1', 'Menu2'] //this is where title render
/>
And I map them on my code like this and I used lodash map method:
map(components, component=>{
<TabContainer>{component}</TabContainer>
}
But it returns this.
Warning: react-swipeable-view: one of the children provided is invalid: null.
We are expecting a valid React Element
And when I console.log the component it returns:
{$$typeof: Symbol(react.element), type: ƒ, key: null, ref: null, props: {…}} Object Need help
I hope it can render the components.
Component's name must be started with a capital letter, but I can see here components = [<component1/>, <component2/>] they were not. So you must convert [<component1/>, <component2/>] to [<Component1/>, <Component2/>]. Second thing I see your map syntax is strange, it must be like this:
components.map(component => (<TabContainer>{component}</TabContainer>))
Reference: https://reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized
I solved This with another implementation.
I used 'react-swipeable-views' and instead passing a component I used this
<SwipeableViews
axis={theme.direction === 'rtl' ? 'x-reverse' : 'x'}
index={this.state.value}
onChangeIndex={this.handleChangeIndex}
>
{ children }
</SwipeableViews>
And Changed:
<FullWidthTab
components = [<Component1/>, <Component2/>] //this is where components renders
menuLabels = ['Menu1', 'Menu2'] //this is where title render
/>
To
<FullWidthTab
components = [<Component1/>, <Component2/>] //this is where components renders
menuLabels = ['Menu1', 'Menu2'] //this is where title render
>
<Component1/>
<Component2/>
</FullWidthTab>
But if you can get how to pass components via props that would be great!
Cause you need return a value in map.
map(components, component=>{
return <TabContainer>{component}</TabContainer>
}
Your es6 syntax needs a subtle adjustment to return the react component.
map(components, component => <TabContainer>{component}</TabContainer> )
or, if you can use the map function from your array instead of importing from a library.
components.map( component => <TabContainer>{component}</TabContainer> )
with React v0.12 the #jsx pragma is gone which means it is no longer possible to output jsx with anything other than the React.METHODNAME syntax.
For my use case I am trying to wrap the React object in another object to provide some convenience methods thus, in my component files, I want to be able to write:
var myConvenienceObject = require('React-Wrapper');
var Component = myConvenienceObject.createSpecializedClass({
render: function () {
return <div />
}
})
However the jsx compiler automatially converts <div /> into React.createElement("div", null)
With older versions of React it was possible to handle this using the pragma at the top of the file. However, since that has been removed, I was wondering if there was any way currently to change the name of the object compiled by jsx so <div /> would be transformed into myConvenienceObject.createElement("div", null)
No, it's no longer possible to use a custom prefix for JSX. If you need to do this, you'll need to modify the JSX transform code, or create a fake React.
var React = require('react'), FakeReact = Object.assign({}, React, {
createElement: function(component, props, ...children){
// ...
// eventually call the real one
return React.createElement(component, props, ...children);
}
});
module.exports = FakeReact;
And then to use it you import the fake react and call it React.
var React = require('fake-react');
// ...
render: function(){ return <div />; }
If you would like to make some elements contains in your myConvenienceObject, you could consider the children props as shown in the doc. But this may need some changes in the myConvenienceObject too, to accept the children.
By the way, i'm not sure where is this createSpecializedClass functions comes from and what it does