Let's assume i have a react component class that displays a modal dialog on a click of a button.
it can be created like this (in jsx):
<Modal text={"some text"}/>
Now, I have a bunch of component classes (let's call them Panels) that all have a function called getMessage, and i'd like the same behavior in all of these components: the modal dialog should show the string that returns from the call to getMessage.
the straight forward way to do this would be to include
<Modal text={this.getMessage()}/>
in the render() function for each such component.
Now, let's say that there is a bit more logic involved. for example, i would only like to render this component if getMessage is defined and does not return null.
Now this is starting to look like this:
var Panel1 = React.createClass({
getMessage: function() {return 'wow';},
render: function() {
var modal = null;
if (this.hasOwnProperty('getMessage' && this.getMessage() !== null) {
modal = <Modal text={this.getMessage()}/>
}
return (
<div>
{modal}
...all other stuff done in panel
</div>
);
}
});
This is starting to become cumbersome because I need to have this logic for each and every component class I define.
How can I achieve DRYness in this scenario so that i don't have to repeat this?
One way would be to define a utility function that contains this logic, let's call it displayModalIfNeeded and the call it from render. this now looks like this:
return (
<div>
{displayModalIfNeeded.call(this)}
....all other stuff needed in Panel
</div>
);
And now for my actual question (sorry for the long exposition):
Let's say that i have a parent component called <Dashboard> which has all panels as its childern:
<Dashboard>
<Panel1>
<Panel2>
<Panel3>
</Dashboard>
Is there something i can write in the implementation of Dashboard that will entirely remove the need to specify anything about these modal components in each Panel?
meaning the the Panel1 implementation can now just be
<div>
...all other stuff done in panel
</div>
and when it's rendered as a child of Dashboard it will have that modal dialog and accompanying logic.
I suggest using a wrapper component with the children prop. Your parent component would look like this:
<Dashboard>
<ModalWrapper text={msg1}>
<Panel1 />
</ModalWrapper>
<ModalWrapper text={msg2}>
<Panel2 />
</ModalWrapper>
<ModalWrapper text={msg3}>
<Panel3 />
</ModalWrapper>
</Dashboard>
Now all your conditional logic can be placed in ModalWrapper. Where your question has "....all other stuff needed in Panel", use this.props.children. e.g.
var ModalWrapper = React.createClass({
render: function () {
var text = this.props.text;
return (
<div>
{text ? <Modal text={text} /> : null}
{this.props.children}
</div>
);
}
});
Related
I have 2 components: ModalForm and BookCard. The ModalForm is supposed to be put inside of the BookCard. When you put the ModalForm - it creates new button and functionates as it has to. But what if instead of creating new button I want to assign ModalForm's functionality to already existing button?
BookCard.js
const BookCard = ({id, name, cover, description, price, count, loadResponse}) => {
return (
<Card>
//some code
<ModalForm /*some props*/ />
<button className="edit-button"> // <- the button, I want to be assigned to be opening modal
<box-icon name='edit-alt'/>
</button>
</Card>
);
}
ModalForm.js
const ModalForm = ({/*some props*/}) => {
//some code
return (
<div>
<Button onClick={handleClickOpen} /> // <- the button that is being shown instead of the wanted one
//some code
</div>
);
}
Its looks like you need to handle the logic in the most up component in your case BookCard.
So ModalForm has some props like isOpen in BookCard you need state like isModalOpened and then add callback onClick that change state (isModalOpened) and then react pass that prop(isOpen) to your ModalForm.
Imagine I have a page "Parent" which conditionally renders a div "Child".
On the click of a button, "Child" opens. To close "Child" one has to click in a X button inside it.
This is how I would do it and in my opinion it looks clean.
const Parent = (props) => {
const [childVisible, setChildVisible] = useState(false);
return (
<>
{childVisible && <Child close={setChildVisible.bind(false)} />}
<button onClick={setChildVisible.bind(true)}>
Open Child
</button>
</>
)
}
const Child = (props) => {
return (
<div>
<p>Im Child</p>
<button onClick={props.close()}> X </button>
</div>
)
}
Since react v16.13.0 react has introduced a warning Warning: Cannot update a component from inside the function body of a different component. and it seems I can't do this anymore.
What's the correct pattern now? I would rather not have a state in both components stating the same thing.
Call back was not properly added .You could do like this onClick={props.close}
While use onClick={props.close()} like this. close() function run on child mount instead of click event
const Child = (props) => {
return (
<div>
<p>Im Child</p>
<button onClick={props.close}> X </button>
</div>
)
}
I'm trying to update the state of a checkbox within a modal that is mounted via button on the UI. I'm loading the settings when AppWrapper mounts so I can pass them around as needed. Right now i'm just passing the settings as props to SettingsList component, which then renders a series of child nodes as checkboxes. I'm able to click the checkboxes when the modal is open, and the settings successfully save to the database. However when the modal is closed and reopened the settings are refreshed to the initially set state from the owner. Refreshing the page though shows the accurately checked boxes. That makes sense to me, but i'm unsure they best way to resolve it.
Should I/Can I update the state of the parent from the child setting so when the modal is reopened that passed props reflect the user changes?
My react structure looks like this:
<AppWrapper>
getInitialState {settings:[]}
<Modal>
<SettingList settings={this.state.settings}>
<Setting/>
<SettingList/>
<Modal/>
<AppWrapper/>
It's not direct one to one code, bust just a representation of the hierarchy.
My Modal component looks like this:
var Modal = React.createClass({
render: function() {
if(this.props.isOpen){
return (
<ReactCSSTransitionGroup transitionName={this.props.transitionName} transitionEnterTimeout={500} transitionLeaveTimeout={500}>
<div className="mymodal">
{this.props.children}
</div>
</ReactCSSTransitionGroup>
);
} else {
return <ReactCSSTransitionGroup transitionName={this.props.transitionName} transitionName={this.props.transitionName} transitionEnterTimeout={500} transitionLeaveTimeout={500} />;
}
}
});
My SettingList component looks like this:
var SettingsList = React.createClass({
render: function() {
var settingNodes = this.props.settings.map(function(setting, i){
return (
<Setting data={setting}
key={i}>
</Setting>
)
}.bind(this));
return (
<div className="settings-block">
<h2>Notifications</h2>
<ul className="account-settings">
{settingNodes}
</ul>
</div>
)
}
});
And the Setting component looks like this:
var Setting = React.createClass({
saveSetting: function(one) {
core.setAccountSettings(this.refs.setting_checkbox.id, this.refs.setting_checkbox.checked).done(function(response){
this.setState({
defaultChecked: this.refs.setting_checkbox.checked
};
console.log(response)
}.bind(this));
},
render: function() {
//get values from settings object
for (var k in this.props.data) {
this.settingId = k
this.settingName = String(k.split(/_(.+)?/)[1]).replace(/_/g, " ");
this.settingValue = (this.props.data[k].toLowerCase() == "true")
}
return (
<li className="checkbox">
<input onChange={this.saveSetting} ref="setting_checkbox" id={this.settingId} className="settings_checkbox" type="checkbox" defaultChecked={this.settingValue}></input>
<label htmlFor={this.settingName}>{this.settingName}</label>
</li>
)
}
});
As pointed out in the comments above there is a number of ways to pass data between components.
http://andrewhfarmer.com/component-communication/
Following the article regarding callbacks was the solution for me.
I am bulding a web application that talks with some APIs in reactJS.
My app has 4 roles, admin, master, slave, baby.
Every role can see something more than the others, so the the menus, and the view are slightly different between them.
e.g.
The navbar has:
for the admin: 5 buttons
for the master: 4 buttons
for the salve: 3 buttons
for the baby: 2 buttons
I wanted to know what is the best practice for structuring this application:
Should I have 4 differents apps? This way the component would be clear, but everytime that I need to implement a new function I have to modify 4 apps.
If this is the only way, how can I call the right app based on the role after the login?
Having four separate apps with a lot of overlapping code/functionality would surely not be the best approach; that would result in a lot of unnecessary repetition.
If the different roles just add/remove small features and functionality, I would just wrap rendering of these elements in conditionals. For example:
renderAdminButton: function() {
( if this.props.user.role === 'admin' ) {
return (
<button />
);
}
}
This function would then be called inside the parent app/component's render function.
However, if you REALLY want to have a separate app for each role, you could conditionally render the app with a function similar to this:
renderApp: function() {
var apps = {
admin: AdminApp,
master: MasterApp,
slave: SlaveApp,
baby: BabyApp
};
return apps[this.props.user.role];
}
In your button component check for this.props.hide and return null.
render() {
if (this.props.hide) {return null;}
return (
<button>
{this.props.btn.text}
</button>
);
}
In your parent component include logic for each buttons hide prop.
render() {
var hideAdminBtn = this.props.appState.userRole != 'admin';
return (
<JButton hide={hideAdminBtn} />
);
}
What you'll want to do is make each access type as a prop so you can render the application based on the type of prop received, ideally at componentDidMount(). You can then have a MasterView that will receive the prop and decide accordingly which view to render. The code will follow along like this:
var MasterView = React.createClass({
var roleType = this.props.role;
render: function() {
if(roleType === 'admin') {
return (
<AdminView />
);
}
});
As for your concern about implementing new features, you should break down you view and logic into simple and reusable components. For example all your access type can share a common <Header /> and <Footer /> component as well as <Profile /> <Settings /> etc. Mind you these can be further broken down so that when you make a small change in any of the components it will propagate to all your views. Similar principles will apply to your logic as well. Remember to stay DRY.
Edit:
Let's assume that the app is being made for a store. All the data displayed via this portal(indicted by the component's name) are:
<EmployeeProfile />
<EmployeeSalary />
<BuyerRecords />
<BalanceSheet />
<Inventory />
<Billing />
Now let's define the access level of each roles:
Admin has access to all the functionalities of the app.
Master can view all but the employee's salary details.
Slave has access to buyer's record, inventory and billing.
Child can only view the inventory and billing sections.
Now, these 6 broad components will be defined and will definitely comprise of multiple components. Just make sure that whenever you feel like a specific part of the view will be used elsewhere, go ahead and make it into a separate component.
Finally, the roles should render like this:
var AdminView = React.createClass({
render: function() {
return (
<div>
<EmployeeProfile />
<EmployeeSalary />
<BuyerRecords />
<BalanceSheet />
<Inventory />
<Billing />
</div>
);
}
});
var MasterView = React.createClass({
render: function() {
return (
<div>
<EmployeeProfile />
<BuyerRecords />
<BalanceSheet />
<Inventory />
<Billing />
</div>
);
}
});
var SlaveView = React.createClass({
render: function() {
return (
<div>
<BuyerRecords />
<BalanceSheet />
<Billing />
</div>
);
}
});
var ChildView = React.createClass({
render: function() {
return (
<div>
<Inventory />
<Billing />
</div>
);
}
});
This way you have 4 parent components handling all the views. They comprise of multiple components and in case a change needs to made, it needs to be done only once and it will reflect in all the roles.
If you decide to have a slightly modified <Inventory /> for each role, you can choose to render different child components of <Inventory /> as determined by the role.
Go ahead and have a look at this official blog post as well. Should help clear any doubts :)
Using React 0.12.2 and given a layout component, e.g. a tray:
<div className="tray">
<div className="tray__item tray__item--left" data-width="260px">
Load a component in the left tray
</div>
<div className="tray__item tray__item--center">
Load a component in the center tray
</div>
<div className="tray__item tray__item--right" data-width="100%">
Load a component in the right tray
</div>
</div>
I would like to be able to insert arbitrary components into each of the contents, passing them as args to this component.
Perhaps something like:
<Tray left={Component1} center={Component2} right={Component3}/>
I would also like to know how to pass an unknown amount of components e.g:
<Carousel items={Component1,Component2,Component3,Component4}/>
Just to be clear - these container components are "dumb" - they only care about sliding content - you should be able to pass whatever content (components) you want to them.
How can I do that and then render them? Thanks.
In the render method of Tray you can do
render: function() {
return (
<div className="tray">
{this.props.children}
</div>
);
}
Then in the component where your Tray lives you can do
<Tray>
<TrayItem position="left"/>
<TrayItem position="center"/>
<TrayItem position="right"/>
</Tray>
You should be able to keep nesting this pattern, i.e.
<Tray>
<TrayItem position="left">
<SomeComponent/>
</TrayItem>
<TrayItem position="center">
<div>
<AnotherComponent/>
</div>
</TrayItem>
<TrayItem position="right"/>
</Tray>
In this case TrayItem's render should also include {this.props.children}
The general principle is, you can put arbitrary components inside other components as long as the container component's render includes {this.props.children}.
Thanks for the answer Adam Stone + SimpleJ.
var Tray = React.createClass({
render: function () {
return (
<div className="tray">
{this.props.children}
</div>
);
}
});
var TrayItem = React.createClass({
render: function () {
return (
<div className="tray__item">
{this.props.children}
</div>
);
}
});
<Tray>
<TrayItem>
<ComponentA/>
<ComponentAB/>
</TrayItem>
<TrayItem>
<ComponentB/>
</TrayItem>
<TrayItem>
<ComponentC/>
</TrayItem>
</Tray>
You should just create a container component that has multiple child components in its render function. You never want to pass a component in as a prop