How to pass a component to another component in React? - reactjs

I want to write a tab component like below,it's ok when I click tab1 to show the content of test,but when I define a class component like test2, it seems wrong.And the erro is 'Uncaught TypeError: Cannot call a class as a function'.Thanks so much.
// use of component
const tabs = [
{tabName: 'tab1', content: () => <Tab.Pane component={test} /> },
{tabName: 'tab2', content: () => <Tab.Pane component={test2} /> }
];
const test = () => {
return (<HGroup>tetetetetetet</HGroup>)
};
class test2 extends Component {
render() {
return(<HGroup>123213</HGroup>)
}
}
#observer
export default class EditLabels extends Component {
render() {
return (<Tab panes={tabs} />)
}
}
// component
#observer
class Tab extends Component {
#observable activeIndex = 0;
static Pane = ({children, component}) => {
console.log(typeof component);
return (
<VGroup>
{children && <HGroup>{children}</HGroup>}
{component && component.call(this)}
</VGroup>
)
};
render() {
const { panes } = this.props;
return (
<VGroup>
<HGroup>{ panes.map((item, index) => {
return (<HGroup key={index}
onClick={() => this.activeIndex = index}
style={this.activeIndex === index && {color: 'red'}}>{item.tabName}</HGroup>)
})}
</HGroup>
{panes[this.activeIndex].content()}
</VGroup>
)
}
}

Related

How to access refs with react-sortable-hoc, withref

I tried using the "withref" in react-sortable-hoc and I need to have my parent component access the children components for some calls I need to invoke on the parent side. I'm not sure where to even call the getWrappedInstance, which seems to be providing access to the children component.
I'm aware of forwarding but it seems like react-sortable-hoc have a different implementation.
To be more specific, I have something like this:
const SortableItem = SortableElement((props) => (
<div className="sortable">
<MyElement {...props}/>
</div>
), {withRef: true});
const MidasSortableContainer = SortableContainer(({ children }: { children: any }) => {
return <div>{children}</div>;
}, {withRef: true});
<MySortableContainer
axis="xy"
onSortEnd={this.onSortEnd}
useDragHandle
>{chartDivs}</MySortableContainer>
Before I wrapped in HOC, I was able to do the following
const chartDivs = elements.map(({childName}, index) => {
return <MyElement
ref={r => this.refsCollection[childName] = r}
...
Does anyone have any ideas how to achieve the same after wrapping with HOC? Thanks.
The key is from source code: https://github.com/clauderic/react-sortable-hoc/blob/master/src/SortableElement/index.js#L82
getWrappedInstance() function.
I guess, after is your origin code:
// this is my fake MyElement Component
class MyElement extends React.Component {
render () {
return (
<div className='my-element-example'>This is test my element</div>
)
}
}
// this is your origin ListContainer Component
class OriginListContainer extends React.Component {
render () {
const elements = [
{ childName: 'David' },
{ childName: 'Tom' }
]
return (
<div className='origin-list-container'>
{
elements.map(({ childName }, index) => {
return <MyElement key={index} ref={r => this.refsCollection[childName] = r} />
})
}
</div>
)
}
}
Now you import react-sortable-hoc
import { SortableElement, SortableContainer } from 'react-sortable-hoc'
First you create new Container Component:
const MySortableContainer = SortableContainer(({ children }) => {
return <div>{children}</div>;
})
Then make MyElement be sortable
/**
* Now you have new MyElement wrapped by SortableElement
*/
const SortableMyElement = SortableElement(MyElement, {
withRef: true
})
Here is import:
You should use SortableElement(MyElement, ... to SortableElement((props) => <MyElement {...props}/>, second plan will make ref prop be null
{ withRef: true } make your can get ref by getWrappedInstance
OK, now you can get your before ref like after ref={r => this.refsCollection[childName] = r.getWrappedInstance()} />
Here is full code:
const MySortableContainer = SortableContainer(({ children }) => {
return <div>{children}</div>;
})
/**
* Now you have new MyElement wrapped by SortableElement
*/
const SortableMyElement = SortableElement(MyElement, {
withRef: true
})
class ListContainer extends React.Component {
refsCollection = {}
componentDidMount () {
console.log(this.refsCollection)
}
render () {
const elements = [
{ childName: 'David' },
{ childName: 'Tom' }
]
return (
<MySortableContainer
axis="xy"
useDragHandle
>
{
elements.map(({ childName }, index) => {
return (
<SortableMyElement
index={index}
key={index}
ref={r => this.refsCollection[childName] = r.getWrappedInstance()} />
)
})
}
</MySortableContainer>
)
}
}
Append
Ehh...
before I said: You should use SortableElement(MyElement, ... to SortableElement((props) => <MyElement {...props}/>, second plan will make ref prop be null
if you really wanna use callback function, you can use like after:
const SortableMyElement = SortableElement(forwardRef((props, ref) => <MyElement ref={ref} {...props} />), {
withRef: true
})
But here NOT the true use of forwardRef
Ehh... choose your wanna.

Nested Function Returns Unexpected Result

I have a menu component in my header component. The header component passes a function to the menu component => default menu component. It's working but the function returns unwanted data.
the path my function is traveling through is:
homepage => header => menu => defaultMenu
The function is:
changeBodyHandler = (newBody) => {
console.log(newBody)
this.setState({
body: newBody
})
}
I pass the function from homepage => header like this:
<HeaderDiv headerMenuClick={() => this.changeBodyHandler}/>
then through header => menu => defaultMenu using:
<Menu MenuClick={this.props.headerMenuClick} />
//==================== COMPONENT CODES ==========================//
homepage:
class Homepage extends Component {
constructor(props){
super(props);
this.state = {
body: "Homepage"
}
this.changeBodyHandler = this.changeBodyHandler.bind(this)
}
changeBodyHandler = (newBody) => {
console.log(newBody)
this.setState({
body: newBody
})
}
render() {
return (
<div>
<HeaderDiv headerMenuClick={() => this.changeBodyHandler}/>
{ this.state.body === "Homepage" ?
<HomepageBody />
: (<div> </div>)}
</div>
);
}
}
header:
class HeaderDiv extends Component {
constructor(props) {
super(props);
this.state = {
showMenu: 'Default',
}
}
render(){
return (
<Menu MenuClick={this.props.headerMenuClick}/>
);
}
}
menu:
import React, { Component } from 'react';
import DefaultMenu from './SubCompMenu/DefaultMenu';
import LoginCom from './SubCompMenu/LoginCom';
import SingupCom from './SubCompMenu/SingupCom';
class Menu extends Component {
//==================================================================
constructor(props){
super(props);
this.state = {
show: this.props.shows
};
this.getBackCancelLoginForm = this.getBackCancelLoginForm.bind(this);
}
//===============================================================
//getBackCancelLoginForm use to hindle click event singin & singup childs
//===============================================================
getBackCancelLoginForm(e){
console.log("Hi")
this.setState({
show : "Default"
})
}
//=================================================================
// getDerivedStateFromProps changes state show when props.shows changes
//===============================================================
componentWillReceiveProps(nextProps) {
if(this.props.show != this.nextProps){
this.setState({ show: nextProps.shows });
}
}
//======================================================================
render() {
return (
<div>
{ this.state.show === "Singin" ?
<LoginCom
cencelLogin={this.getBackCancelLoginForm.bind(this)}
/>
: (<div> </div>)}
{ this.state.show === "Singup" ?
<SingupCom
cencelLogin={this.getBackCancelLoginForm.bind(this)}
/>
: (<div> </div>)}
{ this.state.show === "Default" ?
<DefaultMenu MenuClicks={this.props.MenuClick}/> : (<div> </div>)}
</div>
);
}
}
Default menu:
class DefaultMenu extends Component {
render() {
return (
<div className="box11" onClick={this.props.MenuClicks("Homepage")}>
<h3 className="boxh3" onClick={this.props.MenuClicks("Homepage")}>HOME</h3>
);
}
}
//================ Describe expected and actual results. ================//
I'm expecting the string "Homepage" to be assigned to my state "body"
but console.log shows:
Class {dispatchConfig: {…}, _targetInst: FiberNode, nativeEvent: MouseEvent, type: "click", target: div.box11, …}
instead of "Homepage"
Use arrow functions in onClick listener, in above question Change DefaultMenu as:
class DefualtMenu extends Component {
render() {
return (
<div className="box11" onClick={() => this.props.MenuClicks("Homepage")}>
<h3 className="boxh3">HOME</h3>
</div>
);
} }
For arrow functions learn from mozilla Arrow Functions
I hope this helps.

React function - is not defined no-undef

I get the following error when trying to compile my app 'handleProgress' is not defined no-undef.
I'm having trouble tracking down why handleProgress is not defined.
Here is the main react component
class App extends Component {
constructor(props) {
super(props);
this.state = {
progressValue: 0,
};
this.handleProgress = this.handleProgress.bind(this);
}
render() {
const { questions } = this.props;
const { progressValue } = this.state;
const groupByList = groupBy(questions.questions, 'type');
const objectToArray = Object.entries(groupByList);
handleProgress = () => {
console.log('hello');
};
return (
<>
<Progress value={progressValue} />
<div>
<ul>
{questionListItem && questionListItem.length > 0 ?
(
<Wizard
onChange={this.handleProgress}
initialValues={{ employed: true }}
onSubmit={() => {
window.alert('Hello');
}}
>
{questionListItem}
</Wizard>
) : null
}
</ul>
</div>
</>
);
}
}
Your render method is wrong it should not contain the handlePress inside:
You are calling handlePress on this so you should keep it in the class.
class App extends Component {
constructor(props) {
super(props);
this.state = {
progressValue: 0,
};
this.handleProgress = this.handleProgress.bind(this);
}
handleProgress = () => {
console.log('hello');
};
render() {
const { questions } = this.props;
const { progressValue } = this.state;
const groupByList = groupBy(questions.questions, 'type');
const objectToArray = Object.entries(groupByList);
return (
<>
<Progress value={progressValue} />
<div>
<ul>
{questionListItem && questionListItem.length > 0 ?
(
<Wizard
onChange={this.handleProgress}
initialValues={{ employed: true }}
onSubmit={() => {
window.alert('Hello');
}}
>
{questionListItem}
</Wizard>
) : null
}
</ul>
</div>
</>
);
}
}
If you are using handleProgress inside render you have to define it follows.
const handleProgress = () => {
console.log('hello');
};
if it is outside render and inside component then use as follows:
handleProgress = () => {
console.log('hello');
};
If you are using arrow function no need to bind the function in constructor it will automatically bind this scope.
handleProgress should not be in the render function, Please keep functions in you component itself, also if you are using ES6 arrow function syntax, you no need to bind it on your constructor.
Please refer the below code block.
class App extends Component {
constructor(props) {
super(props);
this.state = {
progressValue: 0,
};
// no need to use bind in the constructor while using ES6 arrow function.
// this.handleProgress = this.handleProgress.bind(this);
}
// move ES6 arrow function here.
handleProgress = () => {
console.log('hello');
};
render() {
const { questions } = this.props;
const { progressValue } = this.state;
const groupByList = groupBy(questions.questions, 'type');
const objectToArray = Object.entries(groupByList);
return (
<>
<Progress value={progressValue} />
<div>
<ul>
{questionListItem && questionListItem.length > 0 ?
(
<Wizard
onChange={this.handleProgress}
initialValues={{ employed: true }}
onSubmit={() => {
window.alert('Hello');
}}
>
{questionListItem}
</Wizard>
) : null
}
</ul>
</div>
</>
);
}
}
Try this one, I have check it on react version 16.8.6
We don't need to bind in new version using arrow head functions. Here is the full implementation of binding argument method and non argument method.
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0
};
constructor() {
super();
}
render() {
return (
<div>
<button onClick={this.updateCounter}>NoArgCounter</button>
<button onClick={() => this.updateCounterByArg(this.state.count)}>ArgCounter</button>
<span>{this.state.count}</span>
</div>
);
}
updateCounter = () => {
let { count } = this.state;
this.setState({ count: ++count });
};
updateCounterByArg = counter => {
this.setState({ count: ++counter });
};
}
export default Counter;

Pass state value to component

I am really new in React.js. I wanna pass a state (that i set from api data before) to a component so value of selectable list can dynamically fill from my api data. Here is my code for fetching data :
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
From that code, i set a state called item. And i want to pass this state to a component. Here is my code :
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
But i get an error that say
TypeError: Cannot read property 'item' of undefined
I am sorry for my bad explanation. But if you get my point, i am really looking forward for your solution.
Here is my full code for additional info :
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {List, ListItem, makeSelectable} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';
let SelectableList = makeSelectable(List);
function wrapState(ComposedComponent) {
return class SelectableList extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
componentWillMount() {
this.setState({
selectedIndex: this.props.defaultValue,
});
this.getListSiswa();
}
handleRequestChange = (event, index) => {
this.setState({
selectedIndex: index,
});
};
render() {
console.log(this.state.item);
return (
<ComposedComponent
value={this.state.selectedIndex}
onChange={this.handleRequestChange}
>
{this.props.children}
</ComposedComponent>
);
}
};
}
SelectableList = wrapState(SelectableList);
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
export default ListSiswa;
One way to do it is by having the state defined in the parent component instead and pass it down to the child via props:
let SelectableList = makeSelectable(List);
function wrapState(ComposedComponent) {
return class SelectableList extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};
componentWillMount() {
this.setState({
selectedIndex: this.props.defaultValue,
});
this.props.fetchItem();
}
handleRequestChange = (event, index) => {
this.setState({
selectedIndex: index,
});
};
render() {
console.log(this.state.item);
return (
<ComposedComponent
value={this.state.selectedIndex}
onChange={this.handleRequestChange}
>
{this.props.children}
{this.props.item}
</ComposedComponent>
);
}
};
}
SelectableList = wrapState(SelectableList);
class ListSiswa extends Component {
state = {
item: {}
}
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
render() {
return (
<SelectableList item={this.state.item} fetchItem={this.getListSiswa}>
<Subheader>Daftar Siswa</Subheader>
</SelectableList>
);
}
}
export default ListSiswa;
Notice that in wrapState now I'm accessing the state using this.props.item and this.props.fetchItem. This practice is also known as prop drilling in React and it will be an issue once your app scales and multiple nested components. For scaling up you might want to consider using Redux or the Context API. Hope that helps!
The error is in this component.
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
This component is referred as Stateless Functional Components (Read)
It is simply a pure function which receives some data and returns the jsx.
you do not have the access this here.

how to pass props down using map

I'm trying to understand how to pass props down using the map function. I pass down the fruit type in my renderFruits function and in my Fruits sub-component I render the fruit type. I do not understand what is wrong with this code.
import React, { Component } from 'react';
import { render } from 'react-dom';
import Fruits from'./Fruits';
class App extends Component {
constructor(props) {
super(props);
this.state = {
fruits: [
{
type:'apple',
},
{
type:'tomato',
}
]
};
}
renderFruits = () => {
const { fruits } = this.state;
return fruits.map(item =>
<Fruits
type={item.type}
/>
);
}
render() {
return (
<div>
{this.renderFruits}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Fruits component where it should render two divs with the text apple and tomato.
class Fruits extends Component {
render() {
const { type } = this.props;
return(
<div>
{type}
</div>
);
}
}
export default Fruits;
You have two problems in you code
- you should call renderFruits in your render function: this.renderFruits()
- should use "key", when you try to render array
renderFruits = () => {
const { fruits } = this.state;
return fruits.map( (item, index) =>
<Fruits
key={index}
type={item.type}
/>
);
}
render() {
return (
<div>
{this.renderFruits()}
</div>
);
}

Resources