React Mui Autocomplete resets scroll after selecting values - reactjs

So I'm trying to set up a mui-autocomplete component with additional buttons (Clear all (clear all values and close dropdown) + Apply (set value and close dropdown)) using ListboxComponent.
Issues:
when selecting options from the bottom of the list, the scroll position is reset to the top
cannot close the dropdown programmatically
Here is the ListboxComponent
ListboxComponent={(listBoxProps) => {
return (
<div>
<ul {...listBoxProps} />
<div>
<button
onMouseDown={(event) => {
// Disable blur
event.preventDefault();
}}
onClick={() => {
// clear values
setSelected([]);
}}
>
Clear All
</button>
<button
onMouseDown={(event) => {
// Disable blur
event.preventDefault();
}}
onClick={() => {
// apply value
}}
>
Apply
</button>
</div>
</div>
);
The options are rendered as follows:
renderOption={(optionProps, option, optionState) => {
return (
<li {...optionProps}>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
checked={optionState.selected}
/>
{option}
</li>
);
}}
So I'm using state to keep track of saving the selected values:
const [selectedResult, setSelected] = useState([]);
And when the option is selected - the state is updated
onChange={(event, selectedOptions) => {
setSelected(selectedOptions);
}}
But when the state changes, the component is re-rendered and the scroll is reset. It also seems that I can't use local variables to store the intermediate result, as the state won't update and the checkbox won't update.
StackBlitz link
Is there anything I can do to achieve this?

Related

Highlight clicked button with React/Tailwind

I want to change the appearence of an button after clicking it.
I have tried adding focus: / active:, but react is re-rendering the component on-click, so it doesn't work.
In Javascript I would add an event-handler to every button that would automatically change the appearence after being clicked, how am I able to recreate that in react/tailwind-css, since tailwind is not working with dynamic className changes?
Is there a good practice, or should I add if statements in order to make it work?
This is what I am trying to recreate.
HTML:
<button>MY BUTTON</button>
<button>MY BUTTON</button>
<button>MY BUTTON</button>
JS:
let button_list = document.querySelectorAll("button");
button_list.forEach(element=>{
element.addEventListener("click",function(){
button_list.forEach(remove_background=>{
remove_background.style.background="white";
});
this.style.background="black";
});
});
I would recommend a state variable to store the active button. Here's a simple example:
const ButtonList = () => {
const [activeButtonIndex, setActiveButtonIndex] = useState(0);
return (
<>
<button
className={activeButtonIndex === 0 ? "bg-white" : "bg-black"}
onClick={() => setActiveButtonIndex(0)}
>
My Button
</button>
<button
className={activeButtonIndex === 1 ? "bg-white" : "bg-black"}
onClick={() => setActiveButtonIndex(1)}
>
My Button
</button>
<button
className={activeButtonIndex === 2 ? "bg-white" : "bg-black"}
onClick={() => setActiveButtonIndex(2)}
>
My Button
</button>
</>
);
};

Conditioning a specific item from a mapped dynamic array in React JS

I want to have an edit mode to each field in a div that is mapped out from an array that I fetch from firbase. I succeeded doing that by conditioning the rendered field to the value of a boolean (editField) which I then manipulate using useState, like so:
in the functions seen up there I can manipulate the value of editTitle, so as to switch between the two functions by double clicking or clicking a button, and also update the field value in Firebase. as such:
this all works fine. HOWEVER,
if there are more that one divs rendered from the tasks[], then thay are obviously all effected to the flipping of editTitle's value from false to true, and by double clicking one field, all fields of same name in all divs swithc to edit mode. as such:
what can I do to target only the field in the task I want to edit? I've tried using the elemnt.id and index in some way bat can't seem to come up with the correct method...
const ifEditTitleIsTrue = (element, index) => {
return (
<div>
<input
type="text"
defaultValue={element.Title}
onChange={(e) => setUpdatedTitle(e.target.value)}
/>
<button className="exit__editmode-btn btn" onClick={exitEditMode}>
X
</button>
<button
className="update__edit-btn btn"
id="updateTitle"
onClick={(e) => updateField(e, element.id)}
>
ok
</button>
</div>
);
};
// if editTitle = false (default):
const ifEditTitleIsFalse = (element, index) => {
return (
<h3
id={index}
className="task-title"
onDoubleClick={() => setEditTitle(true)}
>
{element.Title}
</h3>
);
};
// edit mode for inCharge field
const ifEditInChargeIsTrue = (element, index) => {
return (
<div>
{
<GetCollaboratorsForEditMode
catchValueInCharge={catchValueInCharge}
/>
}
<button className="exit__editmode-btn btn" onClick={exitEditMode}>
X
</button>
<button
className="update__edit-btn btn"
id="updateInCharge"
onClick={(e) => updateField(e, element.id)}
>
ok
</button>
</div>
);
};
{tasks[0] &&
tasks.map((element, index) => (
<div id={element.id} className="task" key={element.id}>
{editTitle
? ifEditTitleIsTrue(element, index)
: ifEditTitleIsFalse(element, index)}
You need to keep track of what element is in edit mode. You can do it by storing the element id in your editTitle state, instead of just a boolean
const ifEditTitleIsFalse = (element, index) => {
...
onDoubleClick={() => setEditTitle(element.id)}
...
};
The condition to render an element in edit mode or view mode would change to:
{editTitle === element.id
? ifEditTitleIsTrue(element, index)
: ifEditTitleIsFalse(element, index)}
I've solved it!!!
insted of EditTitle being a boolean, it's just an empty string.
then the condition is editTitle === index ? some function : some othe function;
and the doubleclick is (()=> setEditTitle(index)).

How to stop the toggle/show item in react-bootstrap Dropdown?

code:-
<td style={{ color: "white", width: "250px" }}>
<DropdownButton id="SelectFocus" title={playdata.ChannelName} tabIndex={-1}
onToggle={ontoggle}
menuVariant='dark'
>
{Channelname.map((val, id) => {
{
return (
<Fragment key={id}>
{removeRedundant([...val, playdata.ChannelName]).map((val1) => {
return (
<Dropdown.Item
onClick={(e) => { setPlayer(val1, playdata.idx, StoreIdx) }}
key={val1}>{val1}</Dropdown.Item>
)
})}
</Fragment>
)
}
})
}
</DropdownButton>
</td>
I am using react bootstrap for dropdown when I press down key button its automatically open the items/options how can I stop that?
i don't want item appear when I press the down key so how can I stop that in react js?
Please help...
When you use DropdownButton component, there is an additional div around the button. Use more low level Dropdown components to set tabIndex on button directly:
<Dropdown>
<Dropdown.Toggle tabIndex={-1}>Test</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item>Test 1</Dropdown.Item>
<Dropdown.Item>Test 2</Dropdown.Item>
<Dropdown.Item>Test 3</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
But keep in mind once you have focused the dropdown with click, you will get the same behaviour when pressing arrow down.
To prevent dropdown from toggling I believe you can use the onKeyDown event, but I wasn't able to get it work so far:
const handleKeyDown = (event) => {
if (event.key === "ArrowDown") {
event.preventDefault();
}
};

How to stop propagation outside item rows

Research on propagation has not given me answers. I have a React list using divs. Each item has a clickable row that takes the user to another page. But within the row, there are dropdown elements for updating the item. stopPropagation has been added to the dropdowns so that the user does not go to another page.
However, if you open the dropdown and then click off to the side because you changed your mind, the row action gets triggered anyway and takes the user to another page. How do I stop that from happening? I cannot target only the row container for redirecting to another page because I can't seem to add a reference to that element that I can match on in event.target. Is there another way to achieve this?
DROPDOWN
const select = () => (
<Select value={value} onChange={(e) => onChange(e)}>
{items.map((option) => (
<MenuItem className={menuItemClass} key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
)
ONCHANGE
const onChange = async (event) => {
event.stopPropagation();
updateItem();
...
ROW
<TableRowBody onClick={(event) => onClickRow(rowItem, event)}>
...
<select />
...
</TableRowBody>
ONCLICKROW
const onClickRow = (value, event) => {
setValue(value);
};
If the clickable element can be a sibling of the select, you can do a css hack like this:
select:focus ~ .clickable-div{
pointer-events:none
}
.clickable-div{
position: absolute;
height: 100%;
width: 100%;
}
When the select is open (focus), it's sibling, .clickable-div will not respond to clicks.
<TableRowBody >
....
<div onClick={(event) => onClickRow(rowItem, event)} className="clickable-div"></div>
<select />
...
</TableRowBody>

React Controlled Component Doesn't Rerender/Update Like I Expect

So, I was refactoring a React component after reading more about React principles of lifting state up and in particular after reading this about derived state.
I had a component like so (before the refactor) that was working but now I realize was duplicating state among multiple sources of truth (in the parent and the child):
const ChildComponent = ({
prevStep,
nextStep,
parentStateData,
updateParentStateData,
}) => {
const [childState, updateChildState] = useState(
parentStateData["thisChildData"]
);
const updateParentStateDataFromChild = () => {
return updateParentStateData(
Object.assign(parentStateData, { thisChildData: childState })
);
};
useEffect(() => updateParentStateDataFromChild(), [childState]);
return (
<div className="text-center">
<h1 className="step-title">Choose Child State</h1>
<div class="state-size-buttons">
<Button
onClick={() => updateChildState(1)}
active={childState === 1}
className="mt-3"
variant="light"
size="lg"
>
1
</Button>
<Button
onClick={() => updateChildState(2)}
active={childState === 2}
className="mt-3"
variant="light"
size="lg"
>
2
</Button>
<Button
onClick={() => updateChildState(3)}
active={childState === 3}
className="mt-3"
variant="light"
size="lg"
>
3
</Button>
</div>
<Button onClick={() => prevStep()} className="mr-2" variant="light">
Go Back
</Button>
<Button disabled={!childState} onClick={() => nextStep()} variant="light">
Next
</Button>
</div>
);
};
Again, this worked but it's not good (I think) because it duplicates state. The ChildComponent receives the parent state (parentStateData) and an update function for that state (updateParentStateData) but duplicates that state in its own childState and updateChildState - which in turn updates the parent using a useEffect() hook -> when child state updates, update the parent state.
So, I started my refactor, like this:
const ChildComponent = ({
prevStep,
nextStep,
parentStateData,
updateParentStateData,
}) => {
const updateState = (data) => {
return updateParentStateData(
Object.assign(parentStateData, { thisChildData: data })
);
};
const nextDisabled = !parentStateData["thisChildData"];
return (
<div className="text-center">
<h1 className="step-title">Choose A State</h1>
<div class="state-size-buttons">
<Button
onClick={() => updateState(1)}
active={parentStateData["thisChildData"] === 1}
className="mt-3"
variant="light"
size="lg"
>
1
</Button>
<Button
onClick={() => updateState(2)}
active={parentStateData["thisChildData"] === 2}
className="mt-3"
variant="light"
size="lg"
>
2
</Button>
<Button
onClick={() => updateState(3)}
active={parentStateData["thisChildData"] === 3}
className="mt-3"
variant="light"
size="lg"
>
3
</Button>
</div>
<Button onClick={() => prevStep()} className="mr-2" variant="light">
Go Back
</Button>
<Button
disabled={nextDisabled}
onClick={() => nextStep()}
variant="light"
>
Next
</Button>
</div>
);
};
This time as you can see, there is no internal state and the parent state object simply gets updated at the relevant key using Object.assign().
But my problem is these state changes never get reflected in my view. If I do a React component inspector I see the state is changing (although it seems to lag) but the view doesn't change. Most critically, the nextDisabled constant and variants of that (I tried putting that logic directly into the Next button <Button disabled={nextDisabled} onClick={()=>nextStep()} variant="light">Next</Button>) don't ever change. So the parent state gets updated, but !parentStateData['thisChildData'] never changes value or never runs again... so what gets rerendered when the props (via the change in parentStateData) change? Why doesn't the simple validation on the next button work as I expect when I update the parent state? That is, once I select a number using the buttons (no longer 0 from the default) I would expect it to not be disabled (example: !1 is false - so not disabled as its for the disabled prop).
But it never changes!
It stays evaluated to true despite the state updating in the parent and therefore the props changing in the child. So somewhere I have a conceptual error. Any help here would be very much appreciated.
React, and most of the tools working with it, are based on the fact that states are immutable. It means that if you want to update a state, you must not change values in the same object, but you need to provide a new object.
So, the following is an error, because it updates the value of the state:
const updateState = (data) => {
return updateParentStateData(
Object.assign(parentStateData, { thisChildData: data })
);
};
Instead, you should create a new object, for example:
const updateState = (data) => {
return updateParentStateData(
Object.assign({}, parentStateData, { thisChildData: data })
);
};
I think that your first example is working only because it really updates the child property.

Resources