React TreeSelect inject custom filtering logic - javascript

I have been trying to alter logic in TreeSelect React component.
So, what I am trying to do is to search by value, but with trimmed spaces, for example if we have entries:
dog
cat
fish
when entering dog it should return me dog entry, simply - ignore spaces at the beginning, while still keeping them in UI.
I tried
const [filterValue, setFilterValue] = useState('')
...
<TreeSelect
...otherProps,
filter
filterValue={filterValue}
onFilterValueChange={(e) => setFilterValue(e?.value?.trimStart())}
/>
but unfortunately this affects how component work and while typing spaces at the beginning, it also trims it in displayed value, giving wrong user experience.
QUESTION
Is there simple way I can somehow override this filtering?
Maybe somehow by changing equality comparison?

Yes, you can change the equality comparison logic by passing a custom function to the filterOption.
so first create the compare function and pass it in filterOption.
if that not work for you try filterTreeNode={trimStartAndCompare}
const trimStartAndCompare = (searchValue, optionValue) => {
return optionValue.trimStart() === searchValue.trimStart();
};
const [filterValue, setFilterValue] = useState('');
...
<TreeSelect
...otherProps,
filter
filterValue={filterValue}
filterOption={trimStartAndCompare} // add this part
onFilterValueChange={(e) => setFilterValue(e.value)}
/>

I finally found a way to work around it. Simply make the TreeSelect controlled component and supply your own input in panelHeaderTemplate property of TreeSelect.
This way you can inject bit of extra logic in filtering:
import { useCallback, useState, ChangeEvent } from 'react'
...
const [filterValueProxyState, setFilterValueProxyState] = useState('')
const handleFilterValueChange = useCallback(
(event: ChangeEvent<HTMLTextAreaElement>) => {
// here we trim filter proxy value, to filter by trimmed string in TreeSelect list
setFilterValueProxyState(event.target.value.trimStart())
},
[props.onProxyFilterValueChange]
)
<TreeSelect
panelHeaderTemplate={<textarea value={filterValue} onChange={handleFilterValueChange}/>}
filterValue={filterValueProxyState}
{...restProps}/>
This is merely pseudo code, as most probably textarea is not best choice here, but I just wanted to describe how it could be achieved.

Related

Difference in react between rendering component and calling a function which returns JSX

Can somebody help me figure out what is going on here and why? I've tried googling, but people focus on one way being faster vs other, one being more correct, etc... I didn't find resource which explains what is going on under the hood, what is the actual implementation difference and why do they behave differently.
I am interested in why it behaves like it does and what triggers that behavior.
Let's look at this code
const SomeComponent = (arg) => {
const [typedAnswer, setTypedAnswer] = useState();
const shouldAskForUserInput= arg === 1; // assume true
const handleInputChange = (e) => setTypedAnswer(e.target.value);
const ShortAnswer = () => (
<Input type="text"
name="answer"
value={typedAnswer}
autofocus
handleInputChange={handleInputChange} />
);
return (
<div>
<p>whatever</p>
<p>something else</p>
{shouldAskForUserInput && <ShortAnswer />}
</div>
)
In this example, it will render a div with 2 p and an input field. This input field, when typed into behaves like any normal controlled component, except it doesn't.
If you type test into this input field and then move your cursor in the middle and try typing numbers 12 you will end up with string te1st2. This is because after each keypress (onChange) it will render again the input field. If I remove autofocus from input, you can type only one character at the time.
If I change following so that it is function call instead
return (
<div>
<p>whatever</p>
<p>something else</p>
{shouldAskForUserInput && ShortAnswer()}
</div>
)
It will start to behave as expected, I can type any number of characters inside the input, it doesn't rerender on change, etc. Same result if I remove the function all together and put the input code directly instead of function call.
Now, what is actually happening there and why?
Here you go! The article doesn't go into React.createComponent() as much but I think it's a good jumping-off point.
https://dev.to/igor_bykov/react-calling-functional-components-as-functions-1d3l
https://reactjs.org/docs/react-without-jsx.html

how to select element with getByTestId that also has specific value with react-testing-library

In react, I am mapping out some elements from an array. For example
{options.map((option)=>{
return <div data-testid="option">{option}</div>
})
I have multiple tests where I want to select an option (without knowing what the textContent of the option is) so I used data-testid="option" and select with screenGetAllByTestId('option')[0] to select the first option...
However, there are some times that I know what particular option I want, and I want to getByTestId but because they all share the same data-testid its a bit harder.
What I'm trying to do, is something similar to this pseudo code:
screen.getAllByTestId('option').getByText("Apples")
which will get all the options, but then get the specific one that has Apples as text
You can write custom matcher functions too, like:
screen.getByText((content, element) => {
return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello')
})
From the docs
I think in your case, that translates to:
screen.getByText((content, element) => {
return element.getAttribute('data-testid').toLowerCase() === 'option' && content === 'Apples'
})
An even better solution might be to have legible text labels attached to each group of options, so your tests could say:
// Find your menu / list header
const fruitsListHeader = screen.getByText('Fruits list');
// Find the menu or whatever it's in
// You can use plain old query selectors
const fruitsList = fruitsListHeader.closest('[role="menu"]')
// Look for your option in the right place, instead of
// praying there are no duplicated options on the page.
const appleOption = within(fruitsList.closest('[role="menu"))
.getByText('Apples');
within docs

How to make an association map containing white spaces

I am building an association map that matched a company with its loogo.
The purpose would be to show this logo on google-map.
I am completing the association map company <--> logo but I realized that the name of some vessels have a white space as shown below and I don't know how to correct that problem:
the problem I have is how do I make an association map with a white space? In this way I get a compilation error and don't know how to solve the problem:
Below a snippet of code:
import React from 'react';
import { Table } from 'reactstrap';
const shipCompanyMap = {
VesselA: 'COMPANY-A', //<-- No white space in
Vessel B: 'COMPANY-B', // <-- white space
Vessel C: 'COMPANY-C', // <-- white space
// Other companies...
};
const companyImageMap = {
COMPANY-A: '../src/logos/company_A.jpg',
COMPANY-B: '../src/logos/company_B.png',
COMPANY-C: '../src/logos/company_C.png',
// Other logos...
};
const associationMap = Object.values(shipCompanyMap).reduce(
(acc, curr) => ({
...acc,
[curr]: companyImageMap[curr]
}),
{}
);
const Ship = ({ ship }) => {
const shipName = ship.NAME;
const company = shipCompanyMap[shipName];
const shipImage = companyImageMap[company] || defaultImg;
return (
<div>
{/* Render shipImage image */}
<img src={shipImage} alt="Logo" />
</div>
);
};
export { Ship };
What I have done so far:
I suspected that I had to treat it as an array and started working in that direction. But since I am not passing a "real" object but am trying to make an association map, working to pass the object was not a good way and I have some issues understanding how to treat white spaces.
I am not sure I have to replace the white space. But I don't think so as the name of some vessels have a white space as shown above.
I came across the trimming function that seems to be doing something close, but I am not sure how to use it.
I have the impression that the white space should be sort of neglected (or trimmed) but I am not sure if this is the right approach.
You may quote your keys:
const shipCompanyMap = {
"VesselA": 'COMPANY-A', //<-- No white space in
"Vessel B": 'COMPANY-B', // <-- white space
"Vessel C": 'COMPANY-C', // <-- white space
// Other companies...
};
However in a real world scenario this may not be relevant any longer as you will probably load the data from a service via JSON anyhow.
I like to use sanitised/normalised keys, so i will replace any [^a-zA-Z0-9] characters with a "_", and i may be stripping out whitespaces altogether and also lowercasing all the characters. In most situations you can care less about the actual value for the purpose of a quick lookup. You may even be just using a hash of such a normalised value as key.

CSS transition lost when array changes

I have a simple react app that shifts and unshifts an array of letters. The transition animation occurs when a user hits next and back button. Functionally, the array is properly changed, but the transition only works for next. I have a hunch that this may be an issue more basic than React, but I did make sure that the key is unique to prevent redraw.
// this is the issue.
clickLeftRightHandler = () => {
const { list } = this.state;
// Does using slice or shifting the array cause a new redraw? Is it
the CSS?
const newList = [list[list.length-1], ...list.slice(0, -1)];
this.setState({list : newList});
}
Code Link:
https://stackblitz.com/edit/react-7nsrjg
Any help is appreciated!
Just use the unshift method:
clickLeftRightHandler = () => {
const { list } = this.state;
const newList = list.slice(0, -1);
newList.unshift(list[list.length-1]); // <-- right here
this.setState({list : newList});
}
working example
edit
I honsetly don't know why it works, but it seems like if the string is a bit longer, the animation works as you want it to, so this works:
newList.unshift(list[list.length-1]+' ');
example. I don't know why's that happening, truly.
It turns out that the reason the animation wasn't working properly was due to the key being supplied to the Alphabet Component. The solution adds an index state that makes sure the shifted key receives a new key that's different from the cycled key.
It should also be noted that, #yuvi's updated example also implicitly fixes the issue with a new string that'll be passed to the key causing it to be uniquely set.
See updated example

Too many calls to R.ap?

I have a list, which contains items such as this:
[{name:...}, {name:...}], ...
I would like to extract only those elements, where name matches to any in a set of regular expressions.
I was able to do it like so:
const cards = yield ... //Network request to get my list of items
const matchers = [/^Remaining Space:/, /^Remaining Weight:/, /^Gross:/];
const propTester = (prop, pred) => R.pipe(R.prop(prop), R.test(pred));
const extractors = R.ap([propTester('name')], matchers);
const [ spaceCard, weightCard, grossCard ] =
R.ap(R.ap([R.find], extractors), [cards]);
Is there any way to simplify that?
Here's one possibility:
const matchers = [/^Remaining Space:/, /^Remaining Weight:/, /^Gross:/];
const testers = R.map(pred => R.pipe(R.prop('name'), R.test(pred)), matchers);
const extractors = R.map(R.find, testers)
const [ spaceCard, weightCard, grossCard ] = R.juxt(extractors)(cards);
It assumes that 'name' is fixed and you don't need to be able to change it dynamically. If you do, it would be slightly more work. The main point is the use of R.juxt, which "applies a list of functions to a list of values."
If we tried really hard, we could probably make testers points-free as well, but threading all that through the whole thing to make a single function from matchers and cards to your results, while possible, would likely read to less readable code.
You can see this in action on the Ramda REPL.

Categories