Is there any pitfall of using ref as conditional statement? - reactjs

Context: I am trying to scroll view to props.toBeExpandItem item which keeps changing depending on click event in parent component. Every time a user clicks on some button in parent component I want to show them this list and scroll in the clicked item to view port. Also I am trying to avoid adding ref to all the list items.
I am using react ref and want to add it conditionally only once in my component. My code goes as below. In all cases the option.id === props.toBeExpandItem would be truthy only once in loop at any given point of time. I want to understand will it add any overhead if I am adding ref=null for rest of the loop elements?
export const MyComponent = (
props,
) => {
const rootRef = useRef(null);
useEffect(() => {
if (props.toBeExpandItem && rootRef.current) {
setTimeout(() => {
rootRef.current?.scrollIntoView({ behavior: 'smooth' });
});
}
}, [props.toBeExpandItem]);
return (
<>
{props.values.map((option) => (
<div
key={option.id}
ref={option.id === props.toBeExpandItem ? rootRef : null}
>
{option.id}
</div>
))}
</>
);
};

Depending upon your recent comment, you can get the target from your click handler event. Will this work according to your ui?
const handleClick = (e) => {
e.target.scrollIntoView()
}
return (
<ul>
<li onClick={handleClick}>Milk</li>
<li onclick={handleClick}>Cheese </li>
</ul>
)

Related

scroll to div using useRef in map method

i am using useRef to scroll to specific div but it is not working in map method's case (probably because of id) , so can anyone tell me how to provide id. it is taking last element in map method right now.
this is element to which i want to scroll to.
{allMessages?.map((message) => (
<div
key={message.data.id}
ref={filterRef}>
<div>
<p>{message.data.text}</p>
</div>
</div>
))}
this is filtered data in which i am getting filtered messages and clicks on specific div.
{filteredMsg?.map((item) => (
<li
onClick={() => goToFilterData(item.data.id)}
key={item.data.id}
>
{item.data.text}
</li>
))}
this is what i have done with useRef yet -
const scrollToRef = (ref) => window.scrollTo(0, ref.current.offsetTop);
const goToFilterData = (id) => {
scrollToRef(filterRef);
};
Just pass an id to every element that maybe should scroll into the view.
{allMessages?.map((message) => (
// the element you want to scroll to
<div id={`message_${message.someUniqueIdentifier}`} />
))}
Pass the identifier to the scroll function.
{filteredMsg?.map((item) => (
<li onClick={() => goToFilterData(item.data.someUniqueIdentifier)}>
{item.data.text}
</li>
))}
Query the element and make it scroll into the view.
const goToFilterData = (uniqueIdentifier) => {
const element = document.getElementById('message_' + uniqueIdentifier);
element.scrollIntoView()
};
Note: ofc you could handle this with a lot of single refs and pass them, but this should just work fine.

How to catch custom bootstrap5 event in react

I'm having problem with catching custom bootstrap event in react.
component:
const Example = () => {
let ref: HTMLLIElement | null
useLayoutEffect(() => {
//ref is not null here
ref?.addEventListener('show.bs.collapse', () => {
console.log('event')
})
}, [])
return(
<React.Fragment>
<li
ref={elem => ref = elem}
className={'align-items-center rounded collapsed nav-link text-white'}
data-bs-toggle={'collapse'}
data-bs-target={'#ex-collapse'}
aria-expanded={'true'}>
Test
</li>
<div
className={'collapse'}
id={'ex-collapse'}>
Content
</div>
</React.Fragment>
)
}
export default Example
When I click li element div is properly shown and hidden but console.log is not fired.
If I change show.bs.collapse to click then console.log is fired on every click as expected.
Event list for bootstrap collapse: https://getbootstrap.com/docs/5.0/components/collapse/#events

how to test a component if the component returns empty array in the beginning with react-testing-library?

i am trying to learn react-testing-library and i got stuck at this point. I have one array that returns an empty array in the start but then i can add items inside the list. Before i add an item, component shows no data available after i add the items it shows some text and delete button next to each item. The test will be about clicking to delete button and opening a modal with this click. My test finds the delete button but it throws error saying "Expected: 1 Received: [ ]" because it always returns "no data available" in the beginning when the component renders. Clearly its because the array returns empty in the beginning but i don't get it. here i share my component.
<div>
{props.formData && props.formData.length !== 0 ? (
props.formData.map((widget, index) => (
<div
id="component-item"
className="widget-wrapper"
key={index}
>
<div className="name-language">
<p>Name: {widget.name}</p>
<p>Language: {widget.language}</p>
</div>
<button
onClick={() => toggleVisible(index)}
type="button"
className="delete-button"
data-testid="component-delete"
>
Delete
</button>
{selectedItem == index ? (
modalVisible == true ? (
<ModalForm
id="component-modal"
toggleVisible={toggleVisible}
onClick={() => handleDeleteWidget(index)}
/>
) : null
) : null}
</div>
))
) : (
<div>
<p className="no-data">
No data available
</p>
</div>
)}
</div>
in the below you can see the test i wrote for it
test("The deleting widget asks confirmation with a modal window.", () => {
render(<WidgetsList />);
const deleteWidget = screen.queryAllByText("Delete");
const modalComponent = document.querySelector("#component-modal");
expect(deleteWidget).toBe(1);
fireEvent.click(deleteWidget);
expect(modalComponent).toBe(1);
});
i would very much appreciate if you show what i'm missing.
Not sure on what modalVisible is. Is it a state or a prop? You will need to mock that is some way to make the modal visible. Anyway, I would create at least two tests, one with formData empty, and other with some data. You will need to pass some mocked data to the component props, something like:
describe('WidgetsList', () => {
test('no data available', () => {
render(<WidgetsList formData={[]} />);
expect(/No data available/).toBeInTheDocument();
});
test('data available', () => {
const mockedFormDataProp = [{ name: 'whatever', language: 'whatever' }];
render(<WidgetsList formData={mockedFormDataProp} />);
expect(screen.getByText(/No data available/)).not.toBeInTheDocument();
fireEvent.click(screen.getByText(/Delete/));
// attach a data-testid prop to your modal, in case there is some text
// add test it like screen.getByText(...)
expect(screen.getByTestId('the-modal')).toBeTruthy();
});
});

Jest: functional component array list not mapping after adding element

I am trying to list person by clicking onClick function to add empty object so that map can loop
over and show div. I see prop onclick function calling works but i map is not looping over it.
a little help would be appreciated.
// functional component
const listing = ({list=[]}) => (
<>
{
<div id='addPerson' onClick={() => list.push({})}>Add person</div>}
{list.map((element, index) => (
<div key={index} className='listItems'>
<p>list name</p>
</div>
))}
</>
);
export default listing;
// test case
it('renders list-items', () => {
const wrapper = shallow(<listing />);
wrapper.find('#addPerson').prop('onClick')();
console.log(wrapper.find('.listItems').length); // showing zero, expected 1
});
Updating List is not going to cause a re-render in your component. I am not sure where List is coming from, but I am assuming it is coming from a local state in the parent, or probably redux store. From inside your component, you will have to call a function in your parent to update the list array there.
Below is a version of your component, assuming the list in your local state. You will have to adjust the code to update the state in the parent or redux store, but the below should be enough to give you an idea of what to do. Also, don't push the element to array, as it mutates the object and will not contact re-render.
const Listing = ({list = [], addToList}) => {
return <>
{
<div id='addPerson' onClick={() => addToList({})}>Add person</div>}
{list.map((element, index) => (
<div key={index} className='listItems'>
<p>list name</p>
</div>
))}
</>
};

Rendering large amount of items prevents other functions to work before render completes

I am trying to find out how to prevent rendering a large amount of data freezing other functionalities in a React component.
In the code, I have a button called Render, which renders 30000 items and another button, Reset, that removes them.
Once I click Render, I am unable to click the other button, Reset because the component is busy rendering 30000 items for the time being.
I want to be able to click Reset button while the component is trying to render the items. Please help me with ways to resolve this issue.
const ItemsComponent = () => {
const [displayItems, setDisplayItems] = useState(false);
const items = Array.from(Array(30000)).map((item, index) => {
return (
<li key={index}>
Item {index + 1}
</li>
)
});
const renderItems = () => { setDisplayItems(true) }
const resetItems = () => { setDisplayItems(false) }
return (
<div>
<button onClick={renderItems}>Render</button>
<button onClick={resetItems}>Reset</button>
<ul>
{ displayItems ? items : null }
</ul>
</div>
);
}

Resources