Updating a state array while avoid rerenders in React - reactjs

I'm building a project in react native. I have a dynamic list of players, that I want the user to be able to edit.
const [players, setPlayers] = useState(['']);
function addPlayer() {
setPlayers([...players, '']);
}
...
<List>
{ players.map(player => (
<MyInput ...>
)}
</List>
What I want to achieve is this:
User enters name
User presses enter
We call addPlayer which adds a player
New input appears and gets focused on (by other functions).
What I get is this:
User enters name
User presses enter
We call addPlayer which adds a player
The Keyboard starts closing for a split second and then opens back up when the new input appears.
I have set blurOnSubmit to false on my inputs. In my opinion what happens is that the list gets rerendered, causing the keyboard to dismiss itself. Then when the setPlayers finishes executing (it's async), the focus function gets called
useEffect(() => {
if (lastRef.current) {
lastRef.current.focus();
}
}, [players]);
What is the best way to stop the list from rerendering so that the keyboard can stay open the whole time ? Thanks !

In the end I cheated, I added an input that’s sitting right outside the list. I can control whether it’s shown or not, it’s content, as well as it’s color with other state variables.
That way when a player is added the list rerenders but doesn’t affect my input, and my keyboard doesn’t move.
From a users POV it looks exactly the same as it did before, as if he’s editing items directly in the list.

Related

React - detect if page routing was triggered by keyboard press

I am using React 17 and react-router-dom 5.2.
I have a left sidebar and a main content area.
Each page opened directly via URL, or via click on a link in the navbar it opens a page.
Each page has an entry route such as:
<Route path="/testpage" component={TestComponent} />
I need to detect if a person entered the page via keyboard or via some other way (mouse click, or direct url entered).
Each compnent has a header:
If a person enters via mouse click or entering the direct URL in the browser nothing changes.
If the person clicked the TAB key until the right link is focused in the navbar, and then the person presses ENTER in the keyboard, the page changes.
And in this case the tabindex should be 1 - <TestHeader title="" tabIndex={0} />
I am wondering how can I create a state in this component that holds the information if the page was loaded via keybaord or not...
Ideally I would have that line as the following:
<TestHeader title="" tabIndex={enteredByKeyboard ? 1 : 0} />
I hope somebody can help
To acknowledge if the user has pressed "Enter" key can be achieved via listening to keypress event. The addional logic can take place after the key press is captured.
// Execute a function when the user presses a key on the keyboard
input.addEventListener("keypress", function(event) {
// If the user presses the "Enter" key on the keyboard
if (event.key === "Enter") {
// Cancel the default action, if needed
event.preventDefault();
// Trigger the button element with a click
document.getElementById("myBtn").click();
// run the custom logic
// extract this line at the component level,
// (as the effects can only be utilised there
// const navigate = useNavigate();
const calculateRequiredRoute = event.target.value;
// modify the line above to achieve the desired name of that route
navigate(calculateRequiredRoute);
}
});
This way it would
Refer :
https://reactrouter.com/en/main/components/navigate
https://reactrouter.com/en/main/hooks/use-navigate
Edit:
keydown event can be used for better result, just when the key press was down, instead of on release of the pressed-key.

How to change the value of hidden text input box when select menu option is changed

I want to change the existing value in a hidden text input box (automatically when the box is closed) to an empty string ('') when the user chooses one of the drop down menu selections. The problem is that I can close the component with the right menu selection, but the value does not change to an empty string until I click the button a second time. Then the value becomes an empty string and I get the correct information.
I originally based the approach to that of form data being passed to/from the parent and that does not seem to work for this component. I tried using a setState() function, however, this either didn't take or I did not implement correctly. All state has been set and all other components move data around as they're supposed to.
This is the parent component that sends/receives the information from the . The "cost={dailyTransportationCost}" is supposed to send the new value to the child.
<DailyTransportationCost
cost={dailyTransportationCost}
handleTransportationCost={e => setDailyTransportationCost(e.target.value)}
/>
This is the component that needs to change to an empty string when it's closed based on the menu option (separate component)
const DailyTransportationCost = ({ cost, handleTransportationCost }) => {
return (
<div className={`${styles.containerTransportationCost}`}>
<label className={`${styles.containerLabel}`} htmlFor='dailyTransportationCost'>Daily Cost</label>
<input className={`${styles.containerInput}`}
placeholder='0.00'
id='dailyTransportationCost'
type='number'
value={cost.dailyTransportationCost}
onChange={handleTransportationCost}
/>
</div>
);
};
Thank you for your help. I've been banging around on this for a couple of days. Any suggestions will be appreciated.
me again...
I figured it out. It was a simple useEffect() and everything worked remarkably well. Just want to thank anyone who might have stopped by.
Cheers!

Why formik is losing focus immediatelly I hit the second field?

I have a form with formik that is losing focus at the second field.
It's seems to be a really basic stuff, but I can't find the problem.
Check this sandbox: https://codesandbox.io/s/create-react-app-forked-m145g
Click on the e-mail field, type anything (or nothing), hit tab to jump to the next field and watch the focus going away.
As you can see, the field is beeing validated, so, I don't know (and would find quite unlikely) if my custom handleBlur function had something to do with it:
const customHandleBlur = (e) => {
if (!values.recaptcha) this._reCaptchaRef.current.execute();
handleBlur(e);
};
This function is responsible to execute Google's recaptcha v3.
What am I doing wrong?
Try changing your customHandleBlur to only execute if you have a value for both email and description.
const customHandleBlur = (e) => {
if (!!values.email && !!values.description && !values.recaptcha) this._reCaptchaRef.current.execute();
handleBlur(e);
};
This will keep the description from losing focus when the this._reCaptchaRef.current.execute() function is called.
It looks like there are other issues to... but this will keep your description field from losing the focus, which is what your question was.

How do I use React refs in a map for functional components?

I have a map function which renders a list of divs and these divs are based on the components state, so it is dynamic. I need to put a ref on each of these items on the list so that the user can basically press up and press down and traverse the list starting from the top.
Something like this:
const func = () => {
const item = items.map((i, index) => {
return (
<div ref={index}>{i.name}</div>
)
});
}
This has been difficult since it seems like refs are not like state where they can change based on dynamic data. Its initialized once and set to a constant. But I don't know what to initialize it as until this list is rendered and I can't call the useRef hook in a function of course so what is the solution here?
EDIT:
Ok so, its seems there needs to be some more context around this question:
I have a dropdown which also allows the user to search to filter out the list of items. I want to make an enhancement to it so that when the user searches it focuses on the first element and allows the use of arrow keys to traverse the list of elements.
I got stuck when I tried to focus on the first element because the first element doesn't exist until the list is rendered. This list changes dynamically based on what the user types in. So how do you focus on the first element of the list if the list is always changing as the user types?
This can be done without refs, so it should probably be done without refs.
You can put the index of the currently focused element into state. Whenever the displayed data changes, reset the index to 0:
const [focusedElementIndex, setFocusedElementIndex] = useState(0);
useEffect(() => {
setFocusedElementIndex(0);
}, [items]);
// using this approach,
// make sure items only gets a new reference when the length changes
When rendering, if you need to know the focused index to change element style, for example, just compare the index against the state value:
<div class={index === focusedElementIndex ? 'focused' : ''}
And have a keydown listener that increments or decrements focusedElementIndex when the appropriate key is pressed.

useMemo on useState with Array and many Object

My apps run although its kind of slow since I loop to create button and loop again to change the color of the button
Its slow because every time I change a color in a button I have to loop in useState array of objects
My question is, how do I only render one object in an Array that uses useState ?
Or.. is there any faster way / better way than looping like this ?
I also try to target the checked status with booleanMonth[0].checked=true which change the status without looping but it doesn't change the color.
this is the array:
const [booleanMonth,setMonth]=useState([
{key:0,value:'January',title:'Jan',color:'black',checked:false},
{key:1,value:'February',title:'Feb',color:'black',checked:false},
{key:2,value:'March',title:'Mar',color:'black',checked:false},
{key:3,value:'April',title:'Apr',color:'black',checked:false},
{key:4,value:'May',title:'May',color:'black',checked:false},
{key:5,value:'June',title:'Jun',color:'black',checked:false},
{key:6,value:'July',title:'Jul',color:'black',checked:false},
{key:7,value:'August',title:'Aug',color:'black',checked:false},
{key:8,value:'September',title:'Sep',color:'black',checked:false},
{key:9,value:'October',title:'Oct',color:'black',checked:false},
{key:10,value:'November',title:'Nov',color:'black',checked:false},
{key:11,value:'December',title:'Dec',color:'black',checked:false}
])
this is where I create buttons which will loop if one of the object in array ABOVE changed because I pressed a button :
const createButtonMonth = useMemo(() =>{
console.log('createButtonMonth')
return (<View style={styles.containerForButtons2}>
{
booleanMonth.map((item,key) =>
<View key={item.key} style={styles.buttonFilter3}>
<Button style={styles.buttonFilter3}
title={item.title}
value={item.checked}
onCheckColor='red'
color={item.checked==true ? 'green':'black'}
onPress={()=>onPressMonthFilter(booleanMonth[item.key].key,booleanMonth[item.key].checked)}
/></View>)
}
</View>)
},[booleanMonth])
this is the press button function which also loop to determine which button to change its color because its checked status true / false (I think this slows me down also because of the loop)
const onPressMonthFilter = (keyMonth,statusMonth) =>{
let newArr = [...booleanMonth]
if(newArr[keyMonth].checked==false){
newArr[keyMonth].checked=true
}else{
newArr[keyMonth].checked=false
}
setMonth(newArr)
}
open for any suggestion :< help newbie here please
The only thing I can think of to further enhance this is to wrap onPressMonthFilter with useCallback
const onPressMonthFilter = useCallback((keyMonth,statusMonth) =>{
let newArr = [...booleanMonth]
if(newArr[keyMonth].checked==false){
newArr[keyMonth].checked=true
}else{
newArr[keyMonth].checked=false
}
setMonth(newArr)
}, [setMonth, booleanMonth])
So the function will return a memoized value. Other than that the code seems to be pretty straight forward, and you don't really have any choice but to loop them to assign the UI.
looping on the array that small shouldn't slow down the app tho, unless you have thousands of data on the array.
I suggest keeping the static data static and only the application state in the state. From a record perspective I know it makes sense to have all the attributes for an entity together but it but in this case I think you can just manage the state of the buttons and infer everything else. For instance, you can infer the color of the button if you know whether it is checked or not.
You can have a list of months as an static array to generate your UI. Then keep array state to keep track of buttons that are checked. If the value in the button is present in the state then button should be checked. If the button is checked then the color should be green. The action of the button will simply add its value to the state array if missing or remove it if exists.
Please review this example showcasing the suggestions
https://codesandbox.io/s/elated-bose-0vwtw

Resources