React: How to focus on tab after creating? - reactjs

First of all, I am new to React.
Im using "easyui for React" for my App (Tab Component in my case)
My goals:
When click on a button on the LeftAccordion, a correspoding tab will be added and focus automatically.
When click on a button on the LeftAccordion, focus on the corresponding tab (if already existed)
Problems:
The tabs wouldn't be focused automatically after added. It always focus on the first one (Home) even when the prop selectedTabIndex has been changed (App's state changed).
WHAT I DID:
Click on button [PRODUCT] -> Tab PRODUCT will be added (but not focused)
Click on button [PRODUCT] again -> Tab PRODUCT still not focused
Click on button [CUSTOMER] -> Tab CUSTOMER will be added (but not focused), it still focus on tab HOME.
Now is the thing that made me get mad:
Click on button [PRODUCT] one more time -> Tab PRODUCT will be focused.
Click on button [CUSTOMER] -> Tab CUSTOMER will be focused.
Click on button [INVOICE] -> tab CUSTOMER will be added (but not focused), it still focus on the last one we selected.
Click on any button else on the LeftAccordion, the tab will be focused correctly again.
I knew that the UI will be re-renderred after state changed, but in my case only the tabs would be added, but not get the focus.
Please help! Thank you in advance!
App.js
import React, {useState} from 'react'
import {LinkButton, TabPanel, Tabs} from 'rc-easyui'
import LeftAccordion from "../../components/LeftAccordion";
const App = ({children}) => {
const [data, setData] = useState({
tabs: [{
title: 'HOME', iconCls: 'icon-store-1616', closable: false
}],
selectedTabIndex: 0
}
)
const handleAddTab = (newTab) => {
let clonedTabs = [...data.tabs] //// hoặc dùng data.tabs.slice() => React mới xác nhận có thay đổi giá trị của state
let currentTabs = []
let selectedTabIndex = 0
clonedTabs.forEach(tab => {
currentTabs.push(tab)
})
// check if tab is already exist by Tab's title
let existTab = currentTabs.find(x=>x.title === newTab.title)
if (existTab === undefined) {
currentTabs.push(newTab)
selectedTabIndex = currentTabs.length - 1 // select last tab
}else{
selectedTabIndex = currentTabs.indexOf(existTab)
}
setData(prev => ({
tabs: [...currentTabs],
selectedTabIndex: selectedTabIndex
})
)
}
return(
<>
<LeftAccordion addTabCallback={handleAddTab}></LeftAccordion>
<ContentArea>
<Tabs style={{height:'100%'}} scrollable selectedIndex={data.selectedTabIndex}
onTabClose={()=>{
console.log('close')
}
}
onTabSelect={() => {
console.log('OK')
}}
>
{
data.tabs.map((tab, index) => {
return (
<TabPanel key={index} title={tab.title} closable={tab.closable ? true: false} iconCls={tab.iconCls}>
<p>Tab {index}</p>
</TabPanel>
)
})
}
</Tabs>
</ContentArea>
</>
)
}
export default App
LeftAcordion.js
import React, {Component} from 'react';
import {LinkButton} from 'rc-easyui'
class LeftAccordion extends Component {
constructor(props) {
super(props);
this.state = {
buttons: [
{
text: 'HOME',
iconCls: 'icon-store-1616'
},
{
text: 'PRODUCT',
iconCls: 'icon-product-1616'
},
{
text: 'CUSTOMER',
iconCls: 'icon-customer-1616'
},
{
text: 'INVOICE',
iconCls: 'icon-invoice-1616'
}
]
}
}
render() {
return (
<div className={'leftaccordion-wrapper'}>
<div className={'accordion-splitter-horizontal'}></div>
{
this.state.buttons.map((button, index) => {
return(
<div key={index} className={'accordion-header panel-header f-noshrink'}>
<LinkButton iconCls={[button.iconCls]} plain
onClick={() => {
let tab = {title: button.text, iconCls: button.iconCls, closable: true}
this.props.addTabCallback(tab)
}
}>{button.text}</LinkButton>
</div>
)
})
}
</div>
);
}
}
export default LeftAccordion;

Related

React Button Multi-Select, strange style behaviour

I am trying to create a simple button multi-select in React but I'm currently getting unexpected behaviour. I'd like users to be able to toggle multiple buttons and have them colourise accordingly, however the buttons seem to act a bit randomly.
I have the following class
export default function App() {
const [value, setValue] = useState([]);
const [listButtons, setListButtons] = useState([]);
const BUTTONS = [
{ id: 123, title: 'button1' },
{ id: 456, title: 'button2' },
{ id: 789, title: 'button3' },
];
const handleButton = (button) => {
if (value.includes(button)) {
setValue(value.filter((el) => el !== button));
} else {
let tmp = value;
tmp.push(button);
setValue(tmp);
}
console.log(value);
};
const buttonList = () => {
setListButtons(
BUTTONS.map((bt) => (
<button
key={bt.id}
onClick={() => handleButton(bt.id)}
className={value.includes(bt.id) ? 'buttonPressed' : 'button'}
>
{bt.title}
</button>
))
);
};
useEffect(() => {
buttonList();
}, [value]);
return (
<div>
<h1>Hello StackBlitz!</h1>
<div>{listButtons}</div>
</div>
);
}
If you select all 3 buttons then select 1 more button the css will change.
I am trying to use these as buttons as toggle switches.
I have an example running #
Stackblitz
Any help is much appreciated.
Thanks
I think that what you want to achieve is way simpler:
You just need to store the current ID of the selected button.
Never store an array of JSX elements inside a state. It is not how react works. Decouple, only store the info. React component is always a consequence of a pattern / data, never a source.
You only need to store the necessary information, aka the button id.
Information that doesn't belong to the state of the component should be moved outside. In this case, BUTTONS shouldn't be inside your <App>.
Working code:
import React, { useState } from 'react';
import './style.css';
const BUTTONS = [
{ id: 123, title: 'button1', selected: false },
{ id: 456, title: 'button2', selected: false },
{ id: 789, title: 'button3', selected: false },
];
export default function App() {
const [buttons, setButtons] = useState(BUTTONS);
const handleButton = (buttonId) => {
const newButtons = buttons.map((btn) => {
if (btn.id !== buttonId) return btn;
btn.selected = !btn.selected;
return btn;
});
setButtons(newButtons);
};
return (
<div>
<h1>Hello StackBlitz!</h1>
<div>
{buttons.map((bt) => (
<button
key={bt.id}
onClick={() => handleButton(bt.id)}
className={bt.selected ? 'buttonPressed' : 'button'}
>
{bt.title}
</button>
))}
</div>
</div>
);
}
I hope it helps.
Edit: the BUTTONS array was modified to add a selected property. Now several buttons can be selected at the same time.

How to assign fields to each tab in WP Gutenberg TabPanel

I know it's a simple question, but I am new in the React and WP Gutenberg world.
I am trying to create a plugin settings page using Gutenberg Components.
The tabs working fine and i know how to render fields, but i can't figure out how to assign to each tab the fields i want.
Here is the sample code:
import { TabPanel } from '#wordpress/components';
const onSelect = ( tabName ) => {
console.log( 'Selecting tab', tabName );
};
const MyTabPanel = () => (
<TabPanel
className="my-tab-panel"
activeClass="active-tab"
onSelect={ onSelect }
tabs={ [
{
name: 'tab1',
title: 'Tab 1',
className: 'tab-one',
},
{
name: 'tab2',
title: 'Tab 2',
className: 'tab-two',
},
] }
>
{ ( tab ) => <p>{ tab.title }</p> }
</TabPanel>
);
The Developer guide for Tab Panel mentions that
tabs is an array of objects, which new properties can be added to. By adding a new property, eg content to each of your tab objects, additional components can be set to render for each tab. The simple example uses ( tab ) => <p>{ tab.title }</p> but this could also include your own custom/existing components defined in content, eg:
import { Button, TabPanel } from '#wordpress/components';
const onSelect = (tabName) => {
console.log('Selecting tab', tabName);
};
const MyTabPanel = () => (
<TabPanel
className="my-tab-panel"
activeClass="active-tab"
onSelect={onSelect}
tabs={[
{
name: 'tab1',
title: 'Tab 1',
className: 'tab-one',
content: <p>Some content goes here</p>
},
{
name: 'tab2',
title: 'Tab 2',
className: 'tab-two',
content: <MyCustomComponent />
},
]}
>
{({ title, content, className }) => <div className={className}><h3>{title}</h3>{content}</div>}
</TabPanel>
);
const MyCustomComponent = () => {
return <><p>Here is a button</p><Button variant="secondary">Click me!</Button></>;
}
The children function (given as anonymous function, above </TabPanel>) renders the tabviews by passing in the active tab object, which now includes the extra content for the tab. The output of the example above is:
Aside from the Developer Guide, the Gutenberg source code is a good place to look for other examples of how TabPanel component can be implemented.. Hope this helps you with adding fields to your tabs..

Changing icons for Segment In React Js

I am implementing a Segment bar for React Js Using a functional component. currently i am changing the color of the highlighted button index.
This is my code for Segment
import React, { useState } from "react";
import "./segment.css";
const buttons = [
{ name: "Bat" },
{ name: "Ball" },
{ name: "Tennis" },
{ name: "Shuttle" },
];
const Segment = () => {
const [activeIndex, setActiveIndex] = useState(0);
return (
<div>
{buttons.map((u, i) => (
<button
className={i === activeIndex ? "segment_active" : "segment_inActive"}
onClick={() => setActiveIndex(i)}
key={u.name}
>
{u.name}
</button>
))}
</div>
);
};
export default Segment;
The code above one is working as expected but now I need to put an icon instead of a name and making it highlight.
my icon names are Bat_active and Bat_InActive etc..
Can someone let me know that how to change the Active icon on the button click?

Hide an item dynamic depending by a condition

I want to create something similar to an accordeon:
https://codesandbox.io/s/suspicious-golick-xrvt5?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const items = [
{
title: "lorem1",
content: "content1"
},
{
title: "lorem2",
content: "content2"
}
];
const [hide, setHide] = useState({
title: "",
open: false
});
const hideContent = (title) => {
setHide({
title: title,
open: !hide.open
});
};
return (
<div className="App">
{items.map((i) => {
return (
<div>
<h1 onClick={() => hideContent(i.title)}>{i.title}</h1>
{hide.title === i.title && hide.open ? <p>{i.content}</p> : ""}
</div>
);
})}
</div>
);
}
Now when i click on a title its content appears, but when click on another title the first content disappear but the actual clicked does not appear. How to click on the second title and to hide the previous content and in the same time to open the actual clicked item content?
I think instead of going with open state. Go with id. You item state
const items = [
{
id: 0,
title: "lorem1",
content: "content1"
},
{
id: 1,
title: "lorem2",
content: "content2"
}
];
Pass id in hideContent:
const hideContent = (title, id) => {
setHide(
(prev) =>
({
title: title,
open: prev.open === id ? null : id
})
);
}
and condition to check inside return like this:
hide.title === i.title && hide.open === i.id ? (
<p>{i.content}</p>
) : (
""
)}
here is demo and full code: https://codesandbox.io/s/upbeat-brattain-8yfx7?file=/src/App.js

Dropdown, React Router and back button

This small component changes URL when you select something from dropdown. Everything works correctly.. Except back button. Everything else changes if I press it but dropdown doesn't change.
How exactly?
If I go to landing page, default value is All
Now I select Red
Now Blue
Red again
Finally Blue
Now, if I click back button, dropdown always shows last selected value (Blue in my example)
How to overcome this issue?
class MyComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
selected: {
// This only affects dropdown if landing to page
value: this.props.params.color, // Get param from url
label: // Some irrelevant uppercase magic here
}
}
}
static defaultProps = {
colors: [
{value: 'all', label: 'All'},
{value: 'red', label: 'Red'},
{value: 'blue', label: 'Blue'}
]
}
render() {
return (
<Dropdown
options={this.props.colors} {/* All options */}
value={this.props.selected} {/* Selected option */}
onChange={this.handleSelect.bind(this)}
/>
)
}
handleSelect(color) {
this.setState({selected: color})
browserHistory.push(`/${color.value}`)
}
}
Your issue is that you are using state to manage the selected prop of your Dropdown component, however the router does not update this when you navigate back / forward.
Instead remove state entirely from your component and use the router to detect the selected item directly:
import { find } from 'lodash';
class MyComponent extends React.Component {
static defaultProps = {
colors: [
{value: 'all', label: 'All'},
{value: 'red', label: 'Red'},
{value: 'blue', label: 'Blue'}
]
}
getSelected() {
const colours = this.props.colours
const selectedColour = this.props.params.colour
// Find the option matching the route param, or
// return a default value when the colour is not found
return find(colours, { value: selectedColour }) || colours[0];
}
render() {
const selectedOption = this.getSelected();
return (
<div>
<Dropdown
options={ this.props.colors } {/* All options */}
value={ selectedOption } {/* Selected option */}
onChange={ this.handleSelect.bind(this) }
/>
<p>You selected: { selectedOption.label }</p>
</div>
)
}
handleSelect(color) {
browserHistory.push(`/${color.value}`)
}
}

Resources