Passing an array of Components as a Props in React - reactjs

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

Related

How to render dom element based on value of passed prop?

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)

React Context always returns EMPTY

I have a Search parent component and a SideBar child component, I am trying to get context in SideBar, but everytime it returns empty.
I followed the tutorial exactly like: https://itnext.io/manage-react-state-without-redux-a1d03403d360
but it never worked, anyone know what I did wrong?
Here is the codesandbox link to the project: https://codesandbox.io/s/vigilant-elion-3li7v
I wrote that article.
To solve your specific problem:
When using the HOC withStore you're injecting the prop store into the wrapped component: <WrappedComponent store={context}.
The value of the prop store is an object that contains 3 functions: get, set, and remove.
So, instead of printing it, you should use it. For example this.props.store.get("currentAlbums") or this.props.store.set("currentAlbums", [album1, album2]).
This example is forked by your code: https://codesandbox.io/s/nameless-wood-ycps6
However
Don't rewrite the article code, but use the library: https://www.npmjs.com/package/#spyna/react-store which is already packed, tested, and has more features.
An event better solution is to use this library: https://www.npmjs.com/package/react-context-hook. That is the new version of the one in that article.
This is an example of a sidebar that updates another component content: https://codesandbox.io/s/react-context-hook-sidebar-xxwkm
Be careful when using react context API
Using the React Context API to manage the global state of an application has some performance issues, because each time the context changes, every child component is updated.
So, I don't recommend using it for large projects.
The library https://www.npmjs.com/package/#spyna/react-store has this issue.
The library https://www.npmjs.com/package/react-context-hook does not.
You pass the store as a prop, so to access it, you need this.props.store in your SideBar.
Not this.state.store
Create a wrapping App component around Search and Sidebar:
const App = props => (
<div>
<Search />
<SideBar />
</div>
);
export default createStore(App);
Now you can manipulate state with set and get that you have available in child components Search and Sidebar.
In Search component you can have something like:
componentDidMount() {
this.props.store.set("showModal", this.state.showModal);
}
also wrapped with withStore(Search) ofc.
and in SideBar you can now call:
render() {
return (
<div>
{"Sidebar: this.state.store: ---> " +
JSON.stringify(this.props.store.get("showModal"))}
}
</div>
);
}
and you will get the output.

Although React doesn't allow objects as children, my code clearly says otherwise

So I came across this bizarre behavior — when I try to setState using objects, it only fails in SOME cases.
For example this works,
getUserClaimAmount().then(
userClaimAmount => {
this.setState({ userClaimAmount: userClaimAmount.toString()})
}
);
But the following does not. It will throw an error saying that React children are not allowed to be objects.
getUserClaimAmount().then(
userClaimAmount => {
this.setState({ userClaimAmount: userClaimAmount})
}
);
However, the following works for some reason. "bettingPoolTotal" is the same type as "userClaimAmount" above.
getBettingPoolTotal().then(
bettingPoolTotal => {
this.setState({ total: bettingPoolTotal });
}
);
Below is a screenshot of what the state looks like. It's clear that there's obviously React children that are indeed objects.
Example of React state
You've mixed up the children property and the state of the component.
children property is populated by React when you create the component. Considering the following JSX:
<Parent>
<Child1 />
text here
<Child2 />
</Parent>
The children property of Parent will be an array with three entries: the element created from Child1, string "text here" and the element created from Child2. If you try to pass the plain JavaScript object into the final markup (not into the React component, since it can use it as it want without error, but into native HTML tag), it won't work:
<div>
{{key: value}}
</div>
That's exactly the error you're getting. It seems that there is some element which has this.state.userClaimAmount passed as children entity, and so errors when it gets a plain object instead of string or JSX.
As for the state - it's entirely possible to have plain objects inside it; however, this should be used very cautiously. Consider the following case inside some component which has an obj field in its state:
const obj = this.state.obj;
obj.key = 'value';
this.setState({obj: obj});
The last setState here surprisingly become a no-op, since the state is already changed by this moment - through the reference copied to obj, - and then React, knowing that this is a no-op, doesn't trigger rendering, leaving component the same as before.

How do properly use Onsen's Navigator in React?

I'm trying to implement a simple Onsen Navigator in React.
So far I'm receiving an error 'route is not defined' and I was looking through the examples & docs but I only saw the initialRoute prop was provided, how & where does the route prop generated or something? Cause it seems like its not specified.
Here is my the code of my component:
import React, {PropTypes} from 'react';
import ons from 'onsenui';
import * as Ons from 'react-onsenui';
class SignUp extends React.Component {
constructor(props) {
super(props);
this.state = {
index : 0
};
this.renderPage = this.renderPage.bind(this);
this.pushPage = this.pushPage.bind(this);
}
pushPage(navigator) {
navigator.pushPage({
title: `Another page ${this.state.index}`,
hasBackButton: true
});
this.setState({index: this.state.index++});
}
renderPage(route, navigator) {
return (
<Ons.Page key={route.title}>
<section style={{margin: '16px', textAlign: 'center'}}>
<Ons.Button onClick={this.pushPage}>
Push Page
</Ons.Button>
</section>
</Ons.Page>
);
}
render() {
return (
<Ons.Page key={route.title}>
<Ons.Navigator
renderPage={this.renderPage}
initialRoute={{
title: 'First page',
hasBackButton: false
}}
/>
</Ons.Page>
);
}
};
SignUp.propTypes = {
'data-pageName': PropTypes.string.isRequired
};
export default SignUp;
Is this the right syntax in ES6? Have I missed something?
When using Ons.Navigator in react the two required properties are:
initialRoute - it should be an object.
renderPage - method which receives 2 arguments - route and navigator. The route should be an object similar to the initialRoute one. You provide that object when you are calling pushPage and similar methods.
It seems that you already know these 2, but there still 2 other things which you need to be careful about. They are not directly onsen related, but come up a lot when using react in general.
Whenever you have a list of dom elements (for example an array of Ons.Page tags) each of those should have a unique key property.
Whenever you use a method you need to make sure you are binding it if you need some extra arguments.
It seems you also know these two. So the only thing left is to make sure you follow them.
Your syntax is correct - the only thing missing is the route variable in SignUp.render. Maybe you originally copied the renderPage method and that is how you have a leftover Ons.Page.
If you're not putting the SignUp component inside some other navigator, tabbar or splitter then you don't actually need the Ons.Page in its render method. Those are the only cases when they are needed. If you it happens to have one of those components as a parent then you can just specify the key.
PS: I think there should be a React Component Inspector (something like this) which you can install - then I think you may be able to see the place where the error occurs. I think if you knew on which line the problem was you would have been able to solve it. :)
For me, with the object I was passing to initialRoute(), it needed a props property, which itself was an object with a key property. See the before and after below.
Before fixing
render() {
return (
<Navigator
initialRoute={{component: DataEntryPage}}
renderPage={this.renderPage}
/>
);
}
}
This was causing the following console warning:
Warning: Each child in an array or iterator should have a unique "key" prop.
Check the render method of `Navigator`.
After fixing
render() {
return (
<Navigator
initialRoute={{component: DataEntryPage, props: {key: 'DataEntryPage'}}}
renderPage={this.renderPage}
/>
);
}
}
Notice that the difference I needed to make was the addition of , props: {key: 'DataEntryPage'}.
Feel free to check out this medium article for more information.

ReactJS Why can't I modify children passed into a component

I figured I could do this, but I am getting this error:
TypeError: child.constructor.ConvenienceConstructor is not a function
I have a component in a page, ala:
// this content is in an html page. My component reads in this child, but I can't seem to modify any part of it.. Just diplay it.
<MyComponent prop1="somevalue">
<div className="myclass1"> some child content that is dynamic </div>
</MyComponent>
Now, in my component since that inner child(ren) is dynamic, I need to change that class depending on some condition. But I can't. I get that error I noted above.
I tried this:
var childContent = React.Children.map(this.props.children,
function(child) {
return React.cloneWithProps(child,
{ className: 'myNEWClass' } );
});
I tried cloneElement too, that didn't work either.
Doesn't work. I tried accessing the child directly, ala:
child._store.props.className // but can't seem to change it, seems immutable.
So, how can I change that class up?
thanks,
Props are supposed to be immutable, so instead of passing className as a prop, you make the parent pass in a prop named defaultClass. In your map method, you can add an extra prop, say, overrideClass. Finally, in the render() method of the actual child component, you need some logic to set the className to either overrideClass, or defaultClass. In this way, you don't have to mutate props.

Resources