I want to automatically open Tab 1. Making Tab 1 as default Tab that opens as soon as we run the code. I have used Ant Design library for creating this tab section. There is something called defaultActiveTab in Ant-tabs that would work as initial active tab on running.
Even though I have mentioned Tab 1 as defaultActiveTab, it isn't working.
I want Tab 1 to be default and all the other tabs should pop only after clicking the Add button. I have attached the link below
const { Tabs, Button } = antd
const { TabPane } = Tabs
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
focusingPaneKey: '',
openingPaneKeys: [],
}
}
openPane = (paneKey) => {
this.setState(({ ...state }) => {
if (!state.openingPaneKeys.includes(paneKey)) {
state.openingPaneKeys = [...state.openingPaneKeys, paneKey]
}
state.focusingPaneKey = paneKey
return state
})
}
closePane = (paneKey) => {
this.setState(({ ...state }) => {
if (paneKey === state.focusingPaneKey) {
const paneKeyIndex = state.openingPaneKeys.indexOf(paneKey)
state.focusingPaneKey = state.openingPaneKeys[paneKeyIndex - 1]
}
state.openingPaneKeys = state.openingPaneKeys.filter((openingPaneKey) => openingPaneKey !== paneKey)
return state
})
}
handleTabsEdit = (key, action) => {
if (action === 'remove') {
this.closePane(key)
}
}
render() {
const { panes } = this.props
const keysOfPane = Object.keys(panes)
return (
<div className="tab-section">
<div style={{ marginBottom: 16 }}>
{keysOfPane.map((key) => (
<Button key={key} onClick={() => this.openPane(key)}>
ADD Tab-{key}
</Button>
))}
</div>
<Tabs
hideAdd
onChange={this.openPane}
activeKey={this.state.focusingPaneKey}
type="editable-card"
onEdit={this.handleTabsEdit}
defaultActiveKey="1"
>
{this.state.openingPaneKeys
.map((key) => panes[key])
.map((pane) => (
<TabPane tab={pane.title} key={pane.key}>
{pane.content}
</TabPane>
))}
</Tabs>
</div>
)
}
}
const panes = {
1: { key: '1', title: 'Tab 1', content: 'Content of Tab Pane 1' },
2: { key: '2', title: 'Tab 2', content: 'Content of Tab Pane 2' },
3: { key: '3', title: 'Tab 3', content: 'Content of Tab Pane 3' },
}
https://jsfiddle.net/6719phr3/1/
Related
Hello guys I was hoping to find help here since I'm very new to coding in reactjs and electron.
Im trying to put a searchbar between the tabs and content like on chrome I use Antd
The code of the Tabs, I don't know where I need to add my searchbar to get it under the tabs and over the content as shown in the picture:
Help much appreciated :)
import { Tabs, Input, Space } from 'antd';
import React from 'react';
import 'antd/dist/antd.css';
import '../../index.css';
import App from '../../App';
import { AudioOutlined } from '#ant-design/icons';
const { Search } = Input;
const suffix = (
<AudioOutlined
style={{
fontSize: 16,
color: '#1890ff',
}}
/>
);
const onSearch = value => console.log(value);
const { TabPane } = Tabs;
const initialPanes = [
{ title: 'Tab 1', content: 'Content of Tab 1', key: '1' },
// { title: 'Tab 2', content: 'Content of Tab 2', key: '2' },
// {
// title: 'Tab 3',
// content: 'Content of Tab 3',
// key: '3',
// closable: false,
// },
];
class Demo extends React.Component {
newTabIndex = 0;
state = {
activeKey: initialPanes[0].key,
panes: initialPanes,
};
onChange = activeKey => {
this.setState({ activeKey });
};
onEdit = (targetKey, action) => {
this[action](targetKey);
};
add = () => {
const { panes } = this.state;
const activeKey = `newTab${this.newTabIndex++}`;
const newPanes = [...panes];
newPanes.push({ title: 'New Tab', content: 'Content of new Tab', key: activeKey });
this.setState({
panes: newPanes,
activeKey,
});
};
remove = targetKey => {
const { panes, activeKey } = this.state;
let newActiveKey = activeKey;
let lastIndex;
panes.forEach((pane, i) => {
if (pane.key === targetKey) {
lastIndex = i - 1;
}
});
const newPanes = panes.filter(pane => pane.key !== targetKey);
if (newPanes.length && newActiveKey === targetKey) {
if (lastIndex >= 0) {
newActiveKey = newPanes[lastIndex].key;
} else {
newActiveKey = newPanes[0].key;
}
}
this.setState({
panes: newPanes,
activeKey: newActiveKey,
});
};
render() {
const { panes, activeKey } = this.state;
return (
<Tabs
type="editable-card"
onChange={this.onChange}
activeKey={activeKey}
onEdit={this.onEdit}
>
{panes.map(pane => (
<TabPane tab={pane.title} key={pane.key} closable={pane.closable}>
{pane.content}
</TabPane>
))}
</Tabs>
);
}
}
export { Demo };
I have 2 problems, firstly On clicking add button, 2 same tabs are being opened. Secondly on closing the tab all the tabs are being closed and shows this error that I have attached in the image. I have problem with closing the tab. I want to close a particular tab that I close instead of closing all tabs. please look into this problem.
import React from 'react';
import { Tabs, Button } from 'antd';
import 'antd/dist/antd.css';
const { TabPane } = Tabs;
class App extends React.Component {
constructor(props) {
super(props);
this.newTabIndex = 0;
const panes = [
];
this.state = {
activeKey: 0,
panes,
};
this.onChange = this.onChange.bind(this);
this.onEdit = this.onEdit.bind(this);
this.add = this.add.bind(this);
this.remove = this.remove.bind(this);
}
onChange(activeKey) {
this.setState({ activeKey });
console.log(activeKey);
};
onEdit(targetKey, action){
this[action](targetKey);
};
add = targetKey => {
console.log('targetKey', targetKey)
let { activeKey } = this.state;
//if (targetKey !== activeKey){
this.setState(prevState => {
const newState = {...prevState};
newState.panes.push({title:'Tab '+ targetKey, content:'Content of tab pane '+ targetKey,key: activeKey+1});
return newState;
});
//}
};
remove = targetKey => {
let { activeKey } = this.state;
let lastIndex;
this.setState(prevState => {
const newState = {...prevState};
newState.forEach((pane, i) => {
if (pane.key === targetKey) {
lastIndex = i - 1;
}
});
const panes = newState.filter(pane => pane.key !== targetKey);
if (panes.length && activeKey === targetKey) {
if (lastIndex >= 0) {
activeKey = panes[lastIndex].key;
} else {
activeKey = panes[0].key;
}
}
return ({panes, activeKey});
});
};
render() {
return (
<div className="tab-section">
<div style={{ marginBottom: 16 }}>
{
['1', '2', '3'].map(item =>
<Button key={item} onClick={() => this.add(item)}>ADD Tab-{item}</Button>
)
}
</div>
<Tabs
hideAdd
onChange={this.onChange}
activeKey={this.state.activeKey}
type="editable-card"
onEdit={this.onEdit}
>
{this.state.panes.map(pane => (
<TabPane tab={pane.title} key={pane.key}>
{pane.content}
</TabPane>
))}
</Tabs>
</div>
);
}
}
export default App;
Snaps of the outputs:
https://i.stack.imgur.com/m55Bx.png
Initially panes were defined this way, which shows the tabs even before button click. But my actual requirement is tabs should pop-up on button click. So I have removed the panes made it empty.
{ title: 'Tab 1', content: 'Content of Tab Pane 1', key: '1' },
{ title: 'Tab 2', content: 'Content of Tab Pane 2', key: '2' },
{ title: 'Tab 3', content: 'Content of Tab Pane 3', key: '3' },
Note- "antd": "^4.16.13" "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", are the packages used.
I refactored the code (https://jsfiddle.net/6719phr3/1/):
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
focusingPaneKey: '',
openingPaneKeys: [],
}
}
openPane = (paneKey) => {
this.setState(({ ...state }) => {
if (!state.openingPaneKeys.includes(paneKey)) {
state.openingPaneKeys = [...state.openingPaneKeys, paneKey]
}
state.focusingPaneKey = paneKey
return state
})
}
closePane = (paneKey) => {
this.setState(({ ...state }) => {
if (paneKey === state.focusingPaneKey) {
const paneKeyIndex = state.openingPaneKeys.indexOf(paneKey)
state.focusingPaneKey = state.openingPaneKeys[paneKeyIndex - 1]
}
state.openingPaneKeys = state.openingPaneKeys.filter((openingPaneKey) => openingPaneKey !== paneKey)
return state
})
}
handleTabsEdit = (key, action) => {
if (action === 'remove') {
this.closePane(key)
}
}
render() {
const { panes } = this.props
const keysOfPane = Object.keys(panes)
return (
<div className="tab-section">
<div style={{ marginBottom: 16 }}>
{keysOfPane.map((key) => (
<Button key={key} onClick={() => this.openPane(key)}>
ADD Tab-{key}
</Button>
))}
</div>
<Tabs
hideAdd
onChange={this.openPane}
activeKey={this.state.focusingPaneKey}
type="editable-card"
onEdit={this.handleTabsEdit}
>
{this.state.openingPaneKeys
.map((key) => panes[key])
.map((pane) => (
<TabPane tab={pane.title} key={pane.key}>
{pane.content}
</TabPane>
))}
</Tabs>
</div>
)
}
}
You can pass panes data by props.
I am creating a dashboard application, and I want to use multiple tabs so that multiple dashboards can be used at the same time and navigated via the tabs with an initial welcome screen. I currently have it so that when adding additional tabs, they render the component with a different ID for each tab. When switching between tabs, I am having to force the reload of the page via window.location.reload(false) within the handleSelectTab handler in order to update the tab contents. While this works, it is very clunky and takes a second to reload the page - is there a better way of doing this that does not mean I have to force the reload of the page everytime the tab changes?
Code for my Dashboard page:
const id = uuid()
const username = localStorage.getItem('username')
const userDashboards = () => {
if (JSON.parse(JSON.stringify(getUserDash(username+1))) === null) {
return ({
tabs: [{id:'Welcome Page', name:'Guide to Use', content:'Welcome Dashboard'}],
currentTab: {id:'Welcome Page', name:'Guide to Use', content:'Welcome Dashboard'},
editTabNameMode: false
})
} else {
return JSON.parse(JSON.stringify(getUserDash(username+1)))
}
}
const [state, setState] = React.useState(userDashboards);
saveUserDash(username+1, state)
const handleDoubleClick = () => {
setState({
...state,
editTabNameMode: true
});
};
const handleEditTabName = e => {
const updatedTabs = state.tabs.map(tab => {
if (tab.id === state.currentTab.id) {
return {
...tab,
name: e.target.value
};
} else {
return tab;
}
});
setState({
...state,
tabs: updatedTabs,
currentTab: {
...state.currentTab,
name: e.target.value
}
});
};
const handleOnBlur = () => {
setState({
...state,
editTabNameMode: false
});
};
const createTabs = () => {
const allTabs = state.tabs.map(tab => {
return (
<li>
{state.editTabNameMode && state.currentTab.id === tab.id ? (
<input
value={tab.name}
onBlur={handleOnBlur}
onChange={handleEditTabName}
/>
) : (
<button
className={state.currentTab.id === tab.id ? "tab active" : "tab"}
onClick={() => handleSelectTab(tab)}
onDoubleClick={() => handleDoubleClick(tab)}
>
{tab.name}
</button>
)}
</li>
);
});
return <ul>{allTabs}</ul>;
};
const handleSelectTab = (tab) => {
setState({
...state,
currentTab: {
id: tab.id,
name: tab.name,
content: tab.content
},
editTabNameMode: false
});
saveUserDash(username+1, state)
window.location.reload(false)
};
const handleAddTab = () => {
const newTabObject= ({
id: `${id}`,
name: `Dashboard ${state.tabs.length + 1}`
})
setState({
...state,
tabs: [...state.tabs, newTabObject],
currentTab: newTabObject,
editTabNameMode: false
});
};
const handleDeleteTab = (tabToDelete) => {
const tabToDeleteIndex = state.tabs.findIndex(tab => tab.id === tabToDelete.id);
const updatedTabs = state.tabs.filter((tab, index) => {
return index !== tabToDeleteIndex;
});
const previousTab = state.tabs[tabToDeleteIndex-1] || state.tabs[tabToDeleteIndex+1] || {};
setState({
tabs: updatedTabs,
currentTab: previousTab,
editTabNameMode: false,
});
localStorage.removeItem(tabToDelete.id)
};
const handleContentChange = (e) => {
const updatedTabs = state.tabs.map((tab) => {
if (tab.name === state.currentTab.name) {
return {
...tab,
content: e.target.value
};
} else {
return tab
}
});
setState({
...state,
tabs:updatedTabs,
currentTab: {
...state.currentTab,
content: e.target.value
}
})
}
const tabContent = () => {
if ( state.currentTab.content === undefined ){
return (
<div>
<div>
<Dash id={state.currentTab.id} />
</div>
</div>
)
} else{
return state.currentTab.content
}
}
return(
<div className=".container">
<div className="well">
<button className=".add-tab-button" onClick={handleAddTab}>
<i className="text-primary fas fa-plus-square" /> Add Dashboard
</button>
{createTabs()}
<h3 style={{ textAlign: "center"}}>{state.currentTab.name}</h3>
<div value={state.currentTab.content} className="tab-content" onChange={handleContentChange}>
{tabContent()}
{state.currentTab.id ? (
<div>
<br/>
<button onClick={() => handleDeleteTab(state.currentTab)}>
Delete
</button>
</div>
) : (
""
)}
</div>
</div>
</div>
);
};
export default Dashboard;
getUserDash and saveUserDash functions;
//function to get user layout from LS
export const getUserDash = (key) => {
try {
const serialisedState = localStorage.getItem(key);
if (serialisedState === null) {
return ({
tabs: [{id:'Welcome Page', name:'Guide to Use', content:'Welcome Dashboard'}],
currentTab: {id:'Welcome Page', name:'Guide to Use', content:'Welcome Dashboard'},
editTabNameMode: false
});
}
return JSON.parse(serialisedState);
} catch (error) {
return ({
tabs: [{id:'Welcome Page', name:'Guide to Use', content:'Welcome Dashboard'}],
currentTab: {id:'Welcome Page', name:'Guide to Use', content:'Welcome Dashboard'},
editTabNameMode: false
});
}
}
//function to save user layout to LS
export const saveUserDash = (layoutName, state) => {
try {
const serialisedState = JSON.stringify(state);
localStorage.setItem(layoutName, serialisedState);
} catch (error) {
console.log("local storage error")
}
}
I'm working on a React.js based outliner (in similar vein to workflowy). I'm stuck at a point. When I press enter on any one item, I want to insert another item just below it.
So I have used setState function to insert an item just below the current item as shown by the code snippet below:
this.setState({
items: this.state.items.splice(this.state.items.map((item) => item._id).indexOf(itemID) + 1, 0, {_id: (new Date().getTime()), content: 'Some Note'})
})
However, the app is re-rendering as blank without any error showing up.
My full source so far:
import './App.css';
import React, { Component } from 'react';
import ContentEditable from 'react-contenteditable';
const items = [
{
_id: 0,
content: 'Item 1 something',
note: 'Some note for item 1'
},
{
_id: 5,
content: 'Item 1.1 something',
note: 'Some note for item 1.1'
},
{
_id: 1,
content: 'Item 2 something',
note: 'Some note for item 2',
subItems: [
{
_id: 2,
content: 'Sub Item 1 something',
subItems: [{
_id: 3,
content: 'Sub Sub Item 4'
}]
}
]
}
];
class App extends Component {
render() {
return (
<div className="App">
<Page items={items} />
</div>
);
}
}
class Page extends Component {
constructor(props) {
super(props);
this.state = {
items: this.props.items
};
this.insertItem = this.insertItem.bind(this);
}
render() {
return (
<div className="page">
{
this.state.items.map(item =>
<Item _id={item._id} content={item.content} note={item.note} subItems={item.subItems} insertItem={this.insertItem} />
)
}
</div>
);
}
insertItem(itemID) {
console.log(this.state.items);
this.setState({
items: this.state.items.splice(this.state.items.map((item) => item._id).indexOf(itemID) + 1, 0, {_id: (new Date().getTime()), content: 'Some Note'})
})
console.log(this.state.items);
}
}
class Item extends Component {
constructor(props) {
super(props);
this.state = {
content: this.props.content,
note: this.props.note
};
this.saveItem = this.saveItem.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
}
render() {
return (
<div key={this.props._id} className="item">
<ContentEditable html={this.state.content} disabled={false} onChange={this.saveItem} onKeyPress={(event) => this.handleKeyPress(this.props._id, event)} />
<div className="note">{this.state.note}</div>
{
this.props.subItems &&
this.props.subItems.map(item =>
<Item _id={item._id} content={item.content} note={item.note} subItems={item.subItems} insertItem={this.insertItem} />
)
}
</div>
);
}
saveItem(event) {
this.setState({
content: event.target.value
});
}
handleKeyPress(itemID, event) {
if (event.key === 'Enter') {
event.preventDefault();
console.log(itemID);
console.log(event.key);
this.props.insertItem(itemID);
}
}
}
export default App;
The github repo: https://github.com/Hirvesh/mneme
Can anybody help me understand as to why it's not rendering again after I update the items?
Edit:
I have update the code to add keys, as suggested below, still rendering as blank, despite the state updating:
import './App.css';
import React, { Component } from 'react';
import ContentEditable from 'react-contenteditable';
const items = [
{
_id: 0,
content: 'Item 1 something',
note: 'Some note for item 1'
},
{
_id: 5,
content: 'Item 1.1 something',
note: 'Some note for item 1.1'
},
{
_id: 1,
content: 'Item 2 something',
note: 'Some note for item 2',
subItems: [
{
_id: 2,
content: 'Sub Item 1 something',
subItems: [{
_id: 3,
content: 'Sub Sub Item 4'
}]
}
]
}
];
class App extends Component {
render() {
return (
<div className="App">
<Page items={items} />
</div>
);
}
}
class Page extends Component {
constructor(props) {
super(props);
this.state = {
items: this.props.items
};
this.insertItem = this.insertItem.bind(this);
}
render() {
return (
<div className="page">
{
this.state.items.map(item =>
<Item _id={item._id} key={item._id} content={item.content} note={item.note} subItems={item.subItems} insertItem={this.insertItem} />
)
}
</div>
);
}
insertItem(itemID) {
console.log(this.state.items);
this.setState({
items: this.state.items.splice(this.state.items.map((item) => item._id).indexOf(itemID) + 1, 0, {_id: (new Date().getTime()), content: 'Some Note'})
})
console.log(this.state.items);
}
}
class Item extends Component {
constructor(props) {
super(props);
this.state = {
content: this.props.content,
note: this.props.note
};
this.saveItem = this.saveItem.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
}
render() {
return (
<div className="item">
<ContentEditable html={this.state.content} disabled={false} onChange={this.saveItem} onKeyPress={(event) => this.handleKeyPress(this.props._id, event)} />
<div className="note">{this.state.note}</div>
{
this.props.subItems &&
this.props.subItems.map(item =>
<Item _id={item._id} key={item._id} content={item.content} note={item.note} subItems={item.subItems} insertItem={this.insertItem} />
)
}
</div>
);
}
saveItem(event) {
this.setState({
content: event.target.value
});
}
handleKeyPress(itemID, event) {
if (event.key === 'Enter') {
event.preventDefault();
console.log(itemID);
console.log(event.key);
this.props.insertItem(itemID);
}
}
}
export default App;
Edit 2:
As per the suggestions below, added key={i._id}, still not working, pushed latest code to github if anybody wants to take a look.
this.state.items.map((item, i) =>
<Item _id={item._id} key={i._id} content={item.content} note={item.note} subItems={item.subItems} insertItem={this.insertItem} />
)
You miss to put a key on Item, because of that React could not identify if your state change.
<div className="page">
{
this.state.items.map((item, i) =>
<Item _id={item._id}
key={i} // <---- HERE!!
content={item.content}
note={item.note}
subItems={item.subItems}
insertItem={this.insertItem} />
)}
</div>
There are multiple issues with your code:
in you are passing this.insertItem into SubItems (which does not exists)
your this.state.items.splice, changes this.state.items, but returns []. So your new State is []
try this:
this.state.items.splice(/* your splice argument here */)
this.setState({ items: this.state.items }, () => {
// this.setState is async! so this is the proper way to check it.
console.log(this.state.items);
});
To accomplish what you actually want, this will not be enough.. But at least, when you have fixed it, you can see some results.
I am writing a autocomplete feature using React, and when the user selects an item on the list I want the input to update. My problem is when the input loses focus I want the menu to disappear unless the user selects the in the menu. Currently, showing the menu is based on a property called showDropDown. In the render method I have if showDropDown it builds the menu components. It seems that the render method is being called before the click listeners on the menu items, and is removing them before onClick is called.
handleOnBlur = () => {
this.setState({ showDropDown: false });
};
handleOnFocus = () => {
this.setState({ showDropDown: true });
};
handleRenderSubComponents = (options) => {
...
return options.map((option) => {
...
return (<li key={displayString} className={className}>
<span onClick={() => {this.handleOnItemSelect(option, fieldInput);}} style={{ cursor: 'pointer' }}>
{displayString}
</span>
</li>);
});
};
render() {
...
return (
<div className={className}>
<div>
<input
style={{ position: 'relative' }}
disabled={disabled}
ref={(inputElem) => {this.inputElem = inputElem;}}
valueLink={{ value: this.state.value, requestChange: (value) => this.handleOnChange(value) }}
onBlur={this.handleOnBlur}
onFocus={this.handleOnFocus}
onKeyUp={this.handleOnKeyUp}
/>
</div>
<ul className="dropdown-menu dropdown-menu-justify" style={dropDownStyle} >
{showDropDown && this.handleRenderSubComponents(options)}
</ul>
</div>
);
}
What I need to do is only hide menu if the input loses focus, but the focus is not in the menu
I think this does what you need. The key is that the onMouseDown event on the container is fired before the onBlur on the input. So we set a class variable that can be check in the onBlur and if the container was clicked we force focus back to the input. The setTimeout clears this variable again, so the order of execution will be onMouseDown, onBlur, setTimeout callback.
jsfiddle
class App extends React.Component {
constructor(props) {
super(props);
this.onInputChange = this.onInputChange.bind(this);
this.onInputBlur = this.onInputBlur.bind(this);
this.onContainerMouseDown = this.onContainerMouseDown.bind(this);
this.onOptionClick = this.onOptionClick.bind(this);
this.state = {
input: '',
showList: false
};
this.options = ['Option 1', 'Option 2', 'Option 3', 'Option 4'];
}
componentWillUnmount() {
clearTimeout(this.containerMouseDownTimeout);
}
onInputChange(e) {
this.setState({input: e.target.value, showList: e.target.value.length > 2});
}
onInputBlur() {
const showList = this.clickedInContainer && this.state.input.length > 2;
this.setState({
showList
});
if (showList) {
this.input.getDOMNode().focus();
}
}
onContainerMouseDown() {
this.clickedInContainer = true;
this.containerMouseDownTimeout = setTimeout(() => {
this.clickedInContainer = false;
});
}
onOptionClick(option) {
this.setState({
input: option,
showList: false
})
}
renderList() {
return (
<ol style={{cursor: 'pointer'}}>
{this.options.map((o, i) => <li key={i} onClick={() => this.onOptionClick(o)}>{o}</li>)}
</ol>);
}
render() {
return (
<div onMouseDown={this.onContainerMouseDown}>
<input ref={ref => this.input = ref} value={this.state.input}
onChange={this.onInputChange} onBlur={this.onInputBlur}/>
{this.state.showList && this.renderList()}
</div>
)
}
}
React.render(<App />, document.getElementById('container'));