How to close accordion on second click? - javascript

I'm having accordion component which is revealed on click. I have an issue with closing it on second click. Currently I'm clicking once and it expands but after second click nothing changes. Thank you for help
const [activeTab, setActiveTab] = React.useState(props.tabIndex);
const tabs = props.tabs.map((tab) => {
return (
<>
<TabContainer>
<Tab
key={tab.index}
onClick={() => setActiveTab(tab.index)}. //here it opens
className={typeof tab.content === 'string' ? '' : 'unactive-tab'}
>
{tab.name}
</Tab>
</TabContainer>
{tab.index === activeTab ?
<div
id="content"
style={{ width: '100%', margin: '2rem 0' }}
dangerouslySetInnerHTML={{ __html: tab.content as string }}
/>
: null}
</>
);
});

Maybe try changing your onClick method to this
onClick= {() => {
activTab == tab.index ? setActiveTab(-1) : setActiveTab(tab.index)
})
In the case where active tab matches the current index of the tab clicked, we set it to -1, hence matching no tab ( showing no tab ); else we set the active tab to the current tab clicked.

Related

Apply 'selected' class to first element in React component on page load

I have a 'RadioItem' component which applys a 'selected' class via an 'isChecked' boolean prop when it's clicked:
{categoryFilter.map((item) => {
return (
<RadioItem
isChecked={selectedItem.id === item.id}
key={item.id}
itemName={item.name}
itemPrice={item.price}
onClick={() => clickHandler(item)}
/>
);
})}
const RadioItem = (props) => {
const clickHandler = () => {
props.onClick();
};
return (
<li
className={props.isChecked ? "item selected" : "item"}
onClick={clickHandler}
>
<div className="item__body">
<div className="item__radio"></div>
<h3 className="item__name">{props.itemName}</h3>
</div>
{props.itemPrice !== 0 && (
<p className="item__price">£{props.itemPrice}</p>
)}
</li>
);
};
I'd like to apply this 'selected' class to the first item (i.e. where the index is 0) when the page first loads but for it to then be deselected when another item is clicked. I have tried, for example, applying an OR condition to the isChecked, like this:
isChecked={selectedItem.id === item.id || index === 0}
but then of course the selected class persists on that first item regardless of me clicking other items.
Any help greatly appreciated, thanks.

How to make certain event happen on a specific item when mapping through an array?

I am mapping through an array and displaying its data. I have edit button to modify that data. When I click edit button, dropdown shows and I am able to edit as shown in screenshot I shared. Open this screenshot
Problem is all of edit buttons work even when I click any one, I want only clicked edit button to work rather than all. How can I achieve this functionality?
This is my code:
const [show, setShow] = useState(false);
const handleShow = () => { setShow(!show); };
<p onClick={handleShow}>
Edit
{show === true ? (
<IoIosArrowUp style={{ color: "#F1BB0F" }} />
) : (
<IoIosArrowDown style={{ color: "#F1BB0F" }} />
)}
</p>
{show && (
<div>
<p>
Unlocks: {lockParams[index]?.endDateString}
</p>
<p>
Unlocker : <span>{lockParams[index]?.unlocker}</span>
</p>
</div>
)}
If you have N different buttons, and you want the view to be different depending on which of those buttons are toggled on or off, you should have state corresponding to those N buttons. An array of booleans would make sense here. That way, if, say, the 4th button is toggled, you can update the 4th element in the state array, and then when the component renders, while iterating over the 4th index, it can look at that 4th boolean to see what should be shown there.
You haven't shown the necessary code in the question, but, for example, if you have:
someArray.map((item) => (
// some JSX
<p onClick={handleShow}>
// etc
Then you need to initialize a state array containing the same number of elements as someArray, like this:
const [shows, setShows] = useState(() => someArray.map(() => false));
const handleShow = (i) => setShows((show, j) => j === i ? !show : show);
And then use index when rendering:
someArray.map((item, i) => (
// some JSX
<p onClick={() => handleShow(i)}>
Edit
{shows[i] ? (
<IoIosArrowUp style={{ color: "#F1BB0F" }} />
) : (
<IoIosArrowDown style={{ color: "#F1BB0F" }} />
)}
</p>
{shows[i] && (
<div>
<p>
Unlocks: {lockParams[index]?.endDateString}

Text editor value disappears in React Js

I am making a simple accordion and inside each accordion, there is a text editor.
Accordion.js
<div className="wrapper">
{accordionData.map((item, index) => (
<Accordion>
<Heading>
<div
style={{ padding: "10px", cursor: "pointer" }}
className="heading"
onClick={() => toggleHandler(index)}
>
{toggleValue !== index ? `Expand` : `Shrink`}
</div>
</Heading>
<Text> {toggleValue === index && item.content && <EditorContainer />} </Text>
</Accordion>
))}
</div>
Here accordion is made up of as a component. This line {toggleValue === index && item.content && <EditorContainer />} is made to check the accordion clicked and then it loads the content and text editor accordingly.
Complete working example:
https://codesandbox.io/s/react-accordion-forked-dcqbo
Steps to reproduce the issue:
-> Open the above link
-> There will be three accordion
-> Click on any of the accordion, that will change the text from Expand to Shrink
-> Now fill some random text inside the editor then click on the text Shrink
-> Again open the same accordion by clicking Expand
-> Now already entered value is missing
I doubt it happens because every time we expand/shrink, the text_editor.js component gets called and that has the state value like,
this.state = {
editorState: EditorState.createEmpty()
};
Here instead of EditorState.createEmpty(), Should I need to give any other thing?
Requirement:
How can I store the already entered value in the text editor. Even though user clicks expand/shrink, the entered text needs to be remain there in the editor.
Any help is much appreciated.
You are correct, the entered value is missing because you are unmounting the EditorContainer component when its shrinked — that when you expand it again it creates a new editorState which is empty.
2 Possible solutions I could think of.
Move editorState and onEditorStateChange to the Parent component and pass that to EditorContainer. This way, when we unmount the EditorContainer we won't lose the previous editorState because it's on the Parent.
We wrap our EditorContainer inside a div and we'll apply a display style when we toggle between shrink/expand. This way, we are only hiding the EditorContainer not unmounting so its states will retain.
I would choose to implement the 2nd solution because we only have to make changes to our Accordion.js file. In either ways, I would create a new component that would handle the current item. I call it NormalAccordionItem.
const NormalAccordionItem = ({ data }) => {
const [show, setShow] = useState(false);
function toggle() {
setShow((prev) => !prev);
}
return (
<Accordion>
<Heading>
<div
style={{ padding: "10px", cursor: "pointer" }}
className="heading"
onClick={toggle}
>
{show ? "Shrink" : "Expand"}
</div>
</Heading>
<Text>
<div style={{ display: show ? "block" : "none" }}> // notice this
<EditorContainer />
</div>
</Text>
</Accordion>
);
};
Then on our NormalAccordion we'll use NormalAccordionItem.
const NormalAccordion = () => {
return (
<div className="wrapper">
{accordionData.map((data) => (
<NormalAccordionItem data={data} key={data.id} />
))}
</div>
);
};
That's it, check the demo below.
Edit Updated demo to expand NormalAccordionItem one at a time.

Map of dropdown buttons, check if id is clicked

I am rendering a map of buttons in react to toggle the is-active class. But if i click one of them, all of them are opened. How can i check if only the clicked button toggles the class?
In plain javascript something like this feels much easier. thanks guys!
const [open, setOpen] = useState(false)
<div
id={"id" + node.id}
key={node.id}
className={`dropdown ${open ? "is-active" : ""}`}
onClick={() => setOpen(!open)}
>
Of course all will be active. You need to place an id to state in order to check which button is clicked.
<div
className="dropdown"
id={"id" + node.id}
key={node.id}
className={`dropdown ${open === node.id ? "is-active" : ""}`}
onClick={() => setOpen(node.id)}
>
You can extract your button to component and change it local state when button is clicked.
function Button(props) {
const [open, setOpen] = useState(false)
return (
<div
className="dropdown"
id={"id" + node.id}
key={node.id}
className={`dropdown ${open ? "is-active" : ""}`}
onClick={() => setOpen(!open)}
>MyButton</div>
)
}

Why isn't the icon color changing based on selection?

I have a sliding tabView with two tabs. The title color changes based on whether the tab is selected. It flicks between #9B9B9B' (lightGrey) for unselected and '#666768' (darkGrey) for selected.
I'm also rendering an icon next to the title. I want the icon color to also change based on which tab is selected.
For some reason, this is not happening and the color of the icon stays at unselected regardless of whether the tab is selected or not.
How can I fix this this? My code is below. Thank you.
_renderLabel = (props: any) => ({ route, index }) => {
const inputRange = props.navigationState.routes.map((x, i) => i);
const outputRange = inputRange.map(
inputIndex => inputIndex === index ? '#666768' : '#9B9B9B');
const color = props.position.interpolate({
inputRange,
outputRange,
});
return (
<View style={styles.labelContainer}>
<FontAwesome
name={route.title === 'New' ? 'clock-o' : 'fire'}
size={14}
color={color === '#666768' ? '#666768' : '#9B9B9B'}
/>
<Animated.Text style={[styles.label, { color }]}>
{route.title}
</Animated.Text>
</View>
);
};
In react (web), you'd use style, not color. I searched around a bit, and I think it may be the same in react-native.
So, instead of:
color={color === '#666768' ? '#666768' : '#9B9B9B'}
try...
style={{ color: color === '#666768' ? '#666768' : '#9B9B9B' }}
Turns out I had to change it to this:
const AnimatedFontAwesome = Animated.createAnimatedComponent(FontAwesome);
...
<AnimatedFontAwesome
name={route.title === 'New' ? 'clock-o' : 'fire'}
size={14}
style={{ color }}
/>

Categories