I want to create a reusable component where the DOM structure can be different each time the component is rendered. Let's say I have this
class Comp extends React.Component {
constructor() {
super()
this.state = {
click: null,
}
}
render() {
return(
<div>
{this.props.chidren}
</div>
)
}
handleButton1() {
this.setState({click: 'button1'});
}
handleButton2() {
this.setState({click: 'button2'});
}
}
class SubComp1 extends React.Component {
render() {
return(
<button onClick={() => this.props.handleButton1()}>Button 1</button>
)
}
}
class SubComp2 extends React.Component {
render() {
return (
<button onClick={() => this.props.handleButton2()}>Button 2</button>
)
}
}
ReactDOM.render((
<Comp>
<div id="somediv">
<div id="andanother">
<SubComp1 />
</div>
</div>
<div id="andanotherother">
<SubComp2 />
</div>
</Comp>), document.getElementById('app'))
Currently, the two subcomponents do not have access to their respective handler functions. What's the best way of passing the functions handleButton1 and handleButton2 to the subcomponents assuming that their position in the DOM is dynamic and might change depending on the layout of the page.
I have thought of 2 solutions so far:
Iterating inside the props.children until I find the element of interest then clone it with the property
Using ref and somehow render the subcomponents after the main component has been rendered through the componentDidMount callback.
What are your thoughts on this?
This is a place where using React's Context would be the most straightforward solution.
Another solution would be to use Redux actions, but that would make your component less reusable and more tightly coupled with your application, which you may or may not care about.
Why not do something like this:
class Comp extends React.Component {
constructor() {
super()
this.state = {
click: null,
}
}
render() {
return(
<div>
{this.props.chidren}
</div>
)
}
handleButton(button) {
this.setState({click: button});
}
}
Then in the subcomponents you can do something like
class SubComp1 extends React.Component {
render() {
return(
<button onClick={() => this.props.handleButton('button1')}>Button 1</button>
)
}
}
class SubComp2 extends React.Component {
render() {
return (
<button onClick={() => this.props.handleButton('button2')}>Button 2</button>
)
}
}
One Alternative option which might fit your needs is to build a higher order component, which decorates another component with some additional functionality, below is a quick example of how this may work for you,
The higher order component:
const Comp = ComposedComponent =>
class Comp extends React.Component {
constructor(props) {
super(props)
this.handleButton = this.handleButton.bind(this);
this.state = {
click: null,
}
}
handleButton(button) {
this.setState({click: button});
}
render() {
return(
<ComposedComponent
onClick={this.handleButton}
/>
)
}
}
export default Comp;
The child component:
class SubComp1 extends React.Component {
render() {
return(
<button onClick={() => this.props.onClick('button1')}>Button 1</button>
)
}
}
How to use it:
const ExtendedComp = Comp(SubComp1);
<ExtendedComp />
would this be suitable for your task?
Related
I'm building an app with React, and getting a TypeError that my function doesn't exist. My function starts in here where I pass it to a child component:
class InvoiceScreen extends Component {
state = {
numberOfInvoices: InvoiceData.length,
currentDisplay: <InvoiceList
openInvoice={this.openInvoice}
/>
};
checkInvoiceLength = () => {
var isEmpty = document.getElementById("InvoiceList").innerHTML === "";
if (isEmpty == false) {
this.setState({display: "untoggled"})
}
else if (isEmpty == true) {
this.setState({hasInvoices: "toggled"})
}
}
openInvoice = (int) => {
this.setState({currentDisplay:
<InvoiceDetails
idNumber={InvoiceData[int].id}
description={InvoiceData[int].description}
street={InvoiceData[int].senderAddress.street}
city={InvoiceData[int].senderAddress.city}
postCode={InvoiceData[int].senderAddress.postCode}
country={InvoiceData[int].senderAddress.country}
createdAt={InvoiceData[int].createdAt}
paymentDue={InvoiceData[int].paymentDue}
clientStreet={InvoiceData[int].clientAddress.street}
clientCity={InvoiceData[int].clientAddress.city}
clientPostCode={InvoiceData[int].clientAddress.postCode}
clientCountry={InvoiceData[int].clientAddress.country}
clientEmail={InvoiceData[int].clientEmail}
items={InvoiceData[int].items}
total={InvoiceData[int].total}
/>})
}
render() {
return(
<div className="InvoiceScreen">
<IconBar />
<div className="DisplayArea">
{this.state.currentDisplay}
</div>
</div>
)
}
}
Then from this component, I pass it down to multiple components created through the map function
class InvoiceList extends Component {
constructor(props) {
super(props)
}
render() {
return(
<div className="InvoiceListScreen">
<InvoiceOptions numberOfInvoices={this.props.numberOfInvoices} />
<div id="InvoiceList">
{InvoiceData.map((invoice, index,) =>
<InvoiceBar
openInvoice={this.props.openInvoice}
key={index}
position={index}
idNumber={invoice.id}
clientName={invoice.clientName}
paymentDue={invoice.paymentDue}
price={Formatter.format(invoice.total)}
status={invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1).toLowerCase()}
/>
)}
</div>
</div>
)
}
}
And then finally inside of the mapped components, I call it as an onClick
class InvoiceBar extends Component {
constructor(props) {
super(props)
}
render() {
return(
<div className="InvoiceBar" onClick={() => this.props.openInvoice(this.props.position)}>
<h4 className="idNumber"><span className="Hashtag">#</span>{this.props.idNumber}</h4>
<p className="clientName">{this.props.clientName}</p>
<div className="DueAndPrice">
<p className="paymentDue">Due {this.props.paymentDue}</p>
<h3 className="price">{this.props.price}</h3>
</div>
<PaymentStatus status={this.props.status} />
</div>
)
}
}
And then like I said, I'm given a TypeError saying that it isn't a function. I'm wondering if it has something to do with the function being passed as props from outside of the map function in the second component. Can someone please enlighten me on what it is I'm doing wrong?
The problem is that class fields run in order in which they're listed. They're not like normal methods, which get defined on the prototype ahead of time. For a simplified version:
class InvoiceScreen extends Component {
state = {
openInvoice: this.openInvoice
};
openInvoice = () => {
// some function
}
desugars to:
class InvoiceScreen extends Component {
constructor(props) {
super(props);
this.state = {
openInvoice: this.openInvoice
};
this.openInvoice = () => {
// some function
}
See the problem? You're defining this.state before you're defining this.openInvoice.
Easiest solution would be to move the definition of this.state to the bottom:
class InvoiceScreen extends Component {
// PUT ALL OTHER METHOD DEFINITIONS HERE
// then just before the end of the component:
state = {
numberOfInvoices: InvoiceData.length,
currentDisplay: <InvoiceList
openInvoice={this.openInvoice}
/>
};
}
That said, putting a React component into state is really, really weird. Consider a different approach if at all possible.
I need to implement a kind of Master/Detail View for a Web Application in React. Since the app should be integrated into a CakePHP app I can't use React Router for handling the routes (since CakePHP would process them).
I have a List of Items and want to navigate through them, showing a Detail View. Items are nested, so there're SubItems to navigate to.
For now I got a ItemList Component, showing a list of Cards with a clickhandler. How can I change the View without changing the url?
ItemList Component looks like:
class ItemList extends React.Component {
constructor(props) {
super(props);
this.state = {
itemList: []
}
}
componentDidMount() {
fetchItems(...)
}
render() {
return(
<div>
{this.state.itemList.map(item => (
<Item key={item.id} item={item} />
))}
</div>
);
}
}
Item Component looks like:
class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
item: props.item,
}
}
handleClick = () => {
// How to navigate to another Component?
}
render() {
return(
<div>
<div className="card my-2" onClick={this.handleClick}>
<div className="card-body">
<h5 className="card-title">{this.state.item.title}</h5>
<p className="card-text">{this.state.item.description}</p>
</div>
</div>
</div>
);
}
}
Thanks in advance!
You should have a parent component (let's say MainView) that has a state (let's say selectedItemId).
class MainView extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedItemId: [null]
}
}
componentDidMount() {
}
render() {
return(
{!selectedItemId && (<ItemList />)}
{selectedItemId && (
<ItemDetail id={selectedItemId} />
)}
);
}
}
As you can see, it renders different components based on the selectedItemId state value.
Inside the ItemList handleClick you call the setState of the parent MainView to set the selected item ID.
So using conditional rendering inside the render() function of MainView you can render the ItemList when no item is selected and ItemDetail when you have selected one.
I'm not really used to ES6 syntax components so my code can be wrong somewhere, but you can get the message ;)
I'm trying to update a child based on the props provided by it's parent. The way it works right now is that the parent's state contains a variable called 'paused' which is provided to the child like this:
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
history: this.props.history,
paused: false,
}
}
render() {
let paused = this.state.paused
return (
<ChildContainer
graph={
<Child
paused={paused}
/>
}
/>
)
}
Child then uses it like this:
render() {
return (
<div>
{'paused:' + this.props.paused}
</div>
)
}
Paused is a boolean, the usage above is just for testing, since I couldn't get it to update where I want, the behaviour is the same like this.
Paused is being updated in the parent, but not the child.
I've read a lot of questions like this, but I'm at a loss.
Any help would be greatly appreciated.
I can't seem to find a problem with this based on the code you've provided. Here is working proof.
If ChildContainer has any logic that could interfere then I could be wrong, but as is, this works:
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
history: this.props.history,
paused: false,
}
}
render() {
let paused = this.state.paused // I agree with #Kuo-hsuan Hsu this is unnecessary
return (
<ChildContainer
toggle={() => this.setState({ paused: !this.state.paused })}
graph={<Child paused={paused}/>}
/>
)
}
}
class ChildContainer extends React.Component {
render() {
return (
<div>
{this.props.graph}
<button onClick={this.props.toggle}>Toggle</button>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>{'paused:' + this.props.paused}</div>
)
}
}
ReactDOM.render(<Parent />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
I'm experimenting with ReactJS and I'm trying to understand how child component rendering is triggered. In ReactJS, if I set up an example like this:
var externalCounterVar = 10
class Counter extends React.Component {
constructor(props){
super(props);
this.state = props;
}
render() {
console.log('rendering counter')
return (
<div> {externalCounterVar} </div>
)
}
}
class Main extends React.Component {
constructor(props){
super(props);
}
handleClick() {
externalCounterVar += 1;
}
rerender(){
this.render();
}
render() {
console.log('rendering');
return (
<div>
<button onClick={this.rerender.bind(this)} />
<Counter counter={externalCounterVar} />
</div>
)
}
}
ReactDOM.render(<Main />, document.getElementById('root'));
I'm not sure I understand why when you "rerender" it calls the render method of Main but not Counter? It seems like it should call both render methods since it's rendering Main and Counter is a child of Main.
So when rerender is called, 'rendering' will print but 'rendering counter' will not.
It looks like you're overlooking one of the main benefits of using React, namely how state works.
You never, ever need to call this.render within a React component
You should never set state dynamically, ie: this.state = ...
You should always use this.setState to set your state.
Rewritten, your code should look something like the following:
const externalCounterVar = 10
class Counter extends React.Component {
render() {
console.log('rendering counter')
return (
<div> {this.props.counter} </div>
)
}
}
class Main extends React.Component {
state = {
counter: externalCounterVar
}
handleClick() {
this.setState({counter: this.state.counter + 1});
}
render() {
console.log('rendering');
return (
<div>
<button onClick={this.handleClick.bind(this)} />
<Counter counter={this.state.counter} />
</div>
)
}
}
By calling this.setState, React automatically knows it needs to rerender your component, and as a result, all child components will also be rerendered.
Hope this helps!
In this case you don't have to use rerender method, also with purpose re-render all child components you need update state with method setState. And also accordingly to this you have to "move state up".
Here my example:
class Counter extends React.Component {
render() {
console.log('rendering counter');
return (<div> {this.props.counter} </div>);
}
}
class Main extends React.Component {
constructor(props){
super(props);
this.state = {counter: props.counter};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({counter: ++prevState.counter}));
}
render() {
console.log('rendering');
return (
<div>
<button onClick={this.handleClick} />
<Counter counter={this.state.counter} />
</div>
);
}
}
var externalCounterVar = 10;
ReactDOM.render(
<Main counter={externalCounterVar} />,
document.getElementById('root')
);
In some situations you can use this.forceUpdate() to call re-render.
But, if you can not do this, do not do.
https://facebook.github.io/react/docs/react-component.html#forceupdate
I have a header component that contain a button and I want this button to display another component(modal page) when it's clicked.
Can I do something like this:
Here's my header component:
import ComponentToDisplay from './components/ComponentToDisplay/index'
class Header extends React.Component {
constructor(props) {
super(props)
}
props : {
user: User
}
_handleInvitePlayerClick = () => {
this.refs.simpleDialog.show();
}
render(){
return(
<Button onClick={this._handleInvitePlayerClick} ><myButton/></Button>
<ComponentToDisplay />
)
}
}
Here is my component for the modal page that should be displayed when the button on the other component gets clicked:
class ComponentToDisplay extends React.Component {
componentDidMount() {
}
render() {
return (
<div>
<SkyLight
ref="simpleDialog"
title={"Title for the modal"}>
{"Text inside the modal."}
<Button onClick={() => this.refs.simpleDialog.hide()}>{"Close modal"}</Button>
</SkyLight>
</div>
)
}
}
Library being used for the modal : https://github.com/marcio/react-skylight
More like this:
class Header extends React.Component {
constructor(props) {
super(props)
}
props: {
user: User
}
render() {
return (
<Button onClick={() => this.refs.componentToDisplay.showMe()}><myButton /></Button>
<ComponentToDisplay ref="componentToDisplay" />
)
}
}
Being sure to expose a showMe() method on your child component:
class ComponentToDisplay extends React.Component {
showMe() {
this.refs.simpleDialog.show();
}
render() {
return (
<div>
<SkyLight
ref="simpleDialog"
title={"Title for the modal"}>
{"Text inside the modal."}
<Button onClick={() => this.refs.simpleDialog.hide()}>{"Close modal"}</Button>
</SkyLight>
</div>
)
}
}
Basically, what's going on here is you wrap the SkyLight's show() method in your child component's own method (in this case, showMe()). Then, in your parent component you add a ref to your included child component so you can reference it and call that method.