I would like to load the tab content only on the first time it becomes active, after that the content stays in the DOM
This is what I have
<Tabs defaultActiveKey={1} animation={false} id="my-tabs" mountOnEnter unmountOnExit>
<Tab eventKey={1}>
<div>content1</div>
</Tab>
<Tab eventKey={2}>
<div>content1</div>
</Tab>
</Tabs>
it works fine, but there is a lag between switching tabs, since the content I have is quite large and I would like to render it only once, on the first time the tab becomes active.
Is there a way to achieve that? I'm using react-bootstrap 0.30.10
UPDATE:
apparently mountOnEnter must be used with animation, otherwise it will not work as intended. I made the change and it works fine now
Old answer:
so I have come up with this wrapping component as follow
class TabsLazyLoad extends Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
this.handleSelect = this.handleSelect.bind(this);
}
getInitialState() {
return {
key: this.props.key || this.props.defaultActiveKey,
rendered: [],
};
}
addRenderedTab(key) {
const newState = _.cloneDeep(this.state);
newState.rendered.push(key);
this.setState(newState);
}
handleSelect(key) {
this.setState({ key });
}
render() {
return (
<Tabs activeKey={this.state.key} onSelect={this.handleSelect} {...this.props}>
{_.map(this.props.children, (tabComponent) => {
if (_.includes(this.state.rendered, tabComponent.props.eventKey)) {
return tabComponent;
}
if (tabComponent.props.eventKey === this.state.key) {
this.addRenderedTab(this.state.key);
}
// if it's not rendered, return an empty tab
const emptyTab = _.cloneDeep(tabComponent);
emptyTab.props.children = null;
return emptyTab;
})}
</Tabs>
);
}
}
TabsLazyLoad.propTypes = Tabs.propTypes;
It seems to be working fine, but I reckon this is a bit hacky, but it's the best I can come up with for now.
It sounds like a good use case for the "Avoid Reconciliation" option that React provides.
Here's a link to the relevant section in the documentation.
Essentially, there's a lifecycle event called shouldComponentUpdate that defaults to true. When you change it to false, it tells React not to run the component through the standard Reconciliation process (i.e. the "diff" checks).
Like with any lifecycle method, you can create a conditional statement for it.
For a component that should be made completely static after its first render, this is really all you need:
class YourComponent extends React.Component {
...
shouldComponentUpdate() {
return false;
}
...
}
However, for a more general use case, you'd want to write a conditional statement based on the props and/or the state of the component:
class YourComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
// Your state
};
}
shouldComponentUpdate(nextProps, nextState) {
// A conditional statement to determine whether
// this component should check for updates or not
}
render () {
return (
<div>
{/* Your JSX*/}
</div>
)
}
I don't use React Boostrap but I guess it's based on the Component design,
example, the rendered content used TabIndex state. Take a closer look at this sample code:
renderActiveTabContent() {
const { children } = this.props
const { activeTabIndex } = this.state
if (children[activeTabIndex]) {
return children[activeTabIndex].props.children
}
}
So the content component render every time Tab state is indexed.
You could use https://github.com/reactjs/react-tabs for your solution other wise take a look of those codes to write a simple one, the Component is rendered once and show/hide state via display: style attribute.
Hope it's help.
Related
I am making a react app that has a navbar as pictured above. The navbar is a component called "TopButtonsBar". Rendered inside this TopButtonsBar component are a component for every button picture above. These components from left to right are InfoButton, NotificationsButton, and so on. Each of the button components manage their own state which dictates whether a dialog box of a given button should be shown or not. Here is what the buttons component look like individually, using the InfoButton component as an example.
export default class InfoButton extends React.Component{
constructor(props){
super(props);
this.state = {
isDialogueOpen:false,
isHoveringOver:false
};
this.handleOpenDialogue = this.handleOpenDialogue.bind(this);
this.handleHoverOver = this.handleHoverOver.bind(this);
}
**handleOpenDialogue = (e) => {
e.preventDefault();
this.setState((prevState) => ({
isDialogueOpen:!prevState.isDialogueOpen
}));
}**
handleHoverOver = (e) => {
e.preventDefault();
if(!this.state.isDialogueOpen){
this.setState((prevState) => ({
isHoveringOver:!prevState.isHoveringOver
}));
}
}
render(){
return(
<div className="navbar-button">
<img
onMouseOver={this.handleHoverOver}
onMouseLeave={this.handleHoverOver}
onClick={this.handleOpenDialogue}
src={this.state.isHoveringOver?infoButtonHovered:infoButtonNotHovered} alt="Info"
/>
{this.state.isHoveringOver && !this.state.isDialogueOpen && <InfoRollover />}
**{this.state.isDialogueOpen && <InfoDialogue />}**
</div>
)
}
}
The important bits are * enclosed by asterisks *. This logic works fine on a buttons individual level. What I am trying to do is the following: If, as picture above, the message notifications button is selected, if I click on the on the info button, I would like the message notifications button to close, simultaneously as the info button opens. However I have been unsuccessful in conceptualizing how I should re-configure the state. Should the TopButtonsBar component hold the information on the state if any of the buttons are closed? If so, how would I go about re-approaching how the buttons open (and if an individual button component should control that state or not). Also, I am not using any state manager such as Redux, Hooks, etc.
Thanks!
One way to solve this is to have the flags (as in isDialogueOpen) for all the child components (InfoButton, NotificationButton and so on) in the parent component's state (TopButtonsBar).
TopButtonsBar.js
I would start off with adding a few constants identifying each dialogue boxes. After that, we can declare a state, which would point to the diaogue box which is open.
Just follow along the comments in the code below to understand better.
// adding some constants here
const INFO_BUTTON = 'INFO_BUTTON';
const NOTIFICATION_BUTTON = 'NOTIFICATION_BUTTON';
export default class TopButtonsBar extends React.Component {
constructor(props) {
super(props);
this.state = {
...
// adding this state to point out which dialogue is open
selectedDialogue: null
}
}
handleOpenDialogue = (e, selectedDialogue) => {
e.preventDefault();
if (selectedDialogue === this.state.selectedDialogue) {
// close dialogue if already open
this.setState({selectedDialogue: null});
} else {
// else open this dialogue
this.setState({selectedDialogue});
}
}
....
render() {
return (
....
<InfoButton
isDialogueOpen={this.state.selectedDialogue === INFO_BUTTON}
handleOpenDialogue={(e) => handleOpenDialogue(e, INFO_BUTTON)}
...
/>
<NotificationButton
isDialogueOpen={this.state.selectedDialogue === NOTIFICATION_BUTTON}
handleOpenDialogue={(e) => handleOpenDialogue(e, NOTIFICATION_BUTTON)}
...
/>
)
}
}
InfoButton.js
Now that we are passing the state and its handling function from the TopButtonsBar component as props, we can call them directly in InfoButton and NotificationButton, without any related local states required.
export default class InfoButton extends React.Component{
constructor(props){
super(props);
this.state = {
// removing the state from here
isHoveringOver:false
};
this.handleHoverOver = this.handleHoverOver.bind(this);
}
// removing the handleOpenDialogue function here
...
render(){
return(
<div className="navbar-button">
<img
onMouseOver={this.handleHoverOver}
onMouseLeave={this.handleHoverOver}
// calling handleOpenDialogue from props
onClick={this.props.handleOpenDialogue}
...
/>
// using isDialogueOpen from props now
{this.state.isHoveringOver && !this.props.isDialogueOpen && <InfoRollover />}
{this.props.isDialogueOpen && <InfoDialogue />}
</div>
)
}
}
My app has multiple Popover components, I know how to handle the state of one Popover component, using something like this:
class App extends Component {
constructor(props) {
super(props);
this.state = { pop_open: false };
}
handleProfileDropDown(e) {
e.preventDefault();
this.setState({
pop_open: !this.state.pop_open,
anchorEl: e.currentTarget,
});
}
handleRequestClose() {
this.setState({
pop_open: false,
});
};
render() {
return (
<div>
<button type="submit" onClick={this.handleProfileDropDown.bind(this)} >My Customized PopOver</button>
<Popover
open={this.state.pop_open}
anchorEl={this.state.anchorEl}
onRequestClose={this.handleRequestClose.bind(this)}
>
{"content"}
</Popover>
</div>
);
}
}
But for more than one Popover, I do not know how to do that, should I create a state for each Popover? Sorry for the question but I am new to the frontend world.
note: kindly do not use hooks in your answer.
An internal state is a good option when only the Component is going to modify it. It keeps the logic simple and inside the same block of code. On the other hand managing the state from outside of the Component lets other components read its values and modify them. This is a common approach when using Redux or Context, where there is a global app state. This state is meant for properties that several Components need to read/write to.
Which to use when is a design decision and depends on each situation. In my opinion each Component should handle its own state when possible. For example, when values are only going to be modified by it, or a children Component. Having an external state makes sense when multiple Components are going to read or modify it, or when the state values need to be passed several levels deep in the hierarchy.
In the example you propose I can see that the Popover is working with an internal state. This can work and you can use the Component several times and it will carry all the logic inside. If you rename the Components you can see more easily what I mean. I dont know exactly how the Component with the button works but this is to make the explanation clear:
class Popover extends Component {
constructor(props) {
super(props);
this.state = { is_open: false };
}
open = () => {
this.setState({
is_open: true
});
}
close = () => {
this.setState({
is_open: false
});
}
toggle = () => {
this.setState(prevState => ({
is_open: !prevState.is_open
}));
}
render() {
return (
<div>
<button onClick={this.toggle}>
Open
</button>
{this.state.is_open && <PopoverContent />}
</div>
);
}
}
If you need further explanation or something is not clear, let me know.
Is it possible to use react to show and hide an existing div element by id?
For example, I want use react to have this <div id="example">this is example</div> show and hide by button click and this element is not created by react.
First you need to understand React's philosophy. I strongly suggest you read the official React tutorial: https://reactjs.org/tutorial/tutorial.html.
In this case you would like to appear and disappear a div, this can be accomplished by changing internal state of a React Component. Then you need to create a function that is bound to the context of the instance of the Component.
For example:
class Example extends Component {
constructor(props) {
super(props);
this.state = {
open: true,
};
}
toggle() {
const { open } = this.state;
this.setState({
open: !open,
});
}
render() {
const { open } = this.state;
return (
<main>
<button onClick={this.toggle.bind(this)}>{open ? 'Hide' : 'Show'}</button>
{open && <div id="example"><h1>this is example</h1></div>}
</main>
);
}
}
Here's a codepen: https://codepen.io/anon/pen/PxWdZK?editors=1010
I want to build a select input component with React.
The select should be dumb component as it's only a UI Component,
but it also have it's own state (Whether to show the options list, or not)
How should I manage this state?
return (
const Select = (props) => {
<div>
<label>{placeholder}</label>
{/*some toggle state*/ && <div>props.children</div>}
</div>
}
)
thanks!
You should not get too confused by the fact that "it's only a UI component". Anything that has an internal state should be a class.
Your code, a dropdown, is my go-to example of when you should use internal state.
Manage your state with setState().
Now your component is stateless, but you need a stateful.
For example:
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {value: '', toggle: false};
}
render() {
return (
<div>
<label>{placeholder}</label>
{this.state.toggle && <div>this.props.children</div>}
</div>
);
}
}
And you should change state with setState function.
For more information, check this article.
According to your code, what you are rendering is a stateless component, so it will not have any state.
What you can do is pass the state from the parent to this component like so:
constructor(props) {
this.state = { showDumbComponent:true }
}
render() {
<DumbComponent show={this.state.showDumbComponent} />
}
I am developing a simple browser app to get some specific data from the user.
I have several components to simplify the proccess of collecting that data, but I am currently rethinking my way of rendering this component.
Basically, i have my main component, which uses state to keep track of which component to render.
I am doing this for almost all of my components.
Also, i also have a function inside the parent component, that i pass to the child component via props, and that is used as a callback to pass the child state to its parent, when that component is no longer useful.
class Main extends React.Component {
constructor(props){
this.state = {
renderA: true,
renderB: false,
childState: null
}
}
collectState(state){
this.setState({
childState: state
});
}
render() {
let content = null;
if(this.state.renderA === true){
content = <ComponentA />
} else {
content = <ComponentB />
}
return(
<div>
{content}
</div>);
}
}
So, using the above example, the child would be something like this
class ComponentA extends React.Component {
constructor(props){
super(props);
this.state = {
stop: false,
usefullInfo: null
}
destroy() {
this.props.collectstate(this.state.usefullInfo)
}
render(){
render something (like a Form) untill this.state.usefullInfo is set;
in that case, set this.state.stop true which will call destroy, passing the usefull information to parent
}
}
So, this method works for me, but i can see clearly that most probably this is not the way to do this.
At this point my question are:
1) how can I stop rendering a component without having to track it with some property like this.state.stop ?
2) if i want to render 2 different components, like in the main component, do I always have to keep a renderA and renderB property on state, to render one or another?
3) is there a better way to pass information from child to parent? i am currently using a callback function passed via props from parent to child, and i am invoking that callback when the component has done its purpose
4) any general suggestions on how to improve the quality of the above code?
Thank you for you help :)!
Your example works fine, but in React it is recommended to lift state up when handling data from multiple children (source). So I would recommend to keep the sate of every children in the parent, and pass props with values and handlers to the children.
Here's a sample app you can check. The form components handle the case you want to implement.
To answer your questions:
The parent component should decide, based on its own state, whether to render a child component or not.
It's not needed to keep variables on state about what component to render. that should be computed in render() based on the parent's state
Yes, callback are the recommended way to pass information to parents
Code quality looks good. You can always do good with tools like prettier or ESlint.
Here's an example:
class Main extends React.Component {
constructor(props) {
this.state = {
stateA: '',
stateB: '',
};
}
handleStateChange(name, value) {
this.setState({
[name]: value,
});
}
render() {
const { stateA, stateB } = this.statel;
const shouldRenderA = !stateA;
if (shouldRenderA) {
return <ComponentA value={stateA} onChange={value => this.handleStateChange('stateA', value)} />;
}
return <ComponentB value={stateA} onChange={value => this.handleStateChange('stateB', value)} />;
}
}
class ComponentA extends React.Component {
render() {
const { value, onChange } = this.props;
return <input type="text" value="value" onChange={onChange} />;
}
}