0

What I am trying to do is focus on the previous TextInput when the next is deleted. At the moment I have it working for adding items in the list by setting the autofocus to true for just that item but it does not work for deleting. The setting of autofocus to true does seem to be working correctly but I think it is not re-rendering the same way as it did with adding?

I have a boolean calculated outside the list, which can be parsed into the list item component (named Task in my project) so theoretically I should be able to call .focus() from inside that component or the outer one but I can not figure out how.

I have tried using useRef but that did not seem to like the FlatList.

Here is my code:

App.js :

import { StyleSheet, View } from 'react-native';

//import Title from './components/title';
import TaskList from './components/list';

export default function App() {
  return (
    <View style={styles.screen}>
        {/*<View style={styles.titles}>
            <Title text={'Now'}></Title>
            <Title text={'Later'}></Title>
        </View>*/}
        <View style={styles.notes}>
            <TaskList list={'Now'}></TaskList>
            {/*<TaskList list={'Later'}></TaskList>*/}
        </View>
    </View>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
  },
  titles: {
    flexDirection: 'row',
  },
  notes: {
    flex: 1,
    flexDirection: 'row',
  },
});

components/list.js :

import { FlatList } from 'react-native';
import { useState, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';

import Task from './task';

await AsyncStorage.clear();

const printArray = (array) => {
    for (let i = 0; i < array.length; i++)
        console.log(list, array[i].created, array[i].done, array[i].text);
}

const getList = async (list) => {
    try {
        return JSON.parse(await AsyncStorage.getItem(list))
    } catch (error) {
        console.log(error);
    }
}

const saveList = async (list, array) => {
    try {
        await AsyncStorage.setItem(list, JSON.stringify(array));
    } catch (error) {
        console.log(error);
    }
}

const resetFocus = (array) => {
    let result = []
    for (let i = 0; i < array.length; i++) {
        if (array[i].focus) {
            result.push(JSON.parse(JSON.stringify(array[i])));
            result[i].focus = false;
        } else
            result.push(array[i]);
    }
    return result;
}

const addItem = async (list, createdFrom, setTasks) => {
    let task = {created:Date.now(), done:0, text:"", focus:true};
    let array = await getList(list);
    if (array == null)
        array = [task];
    else if (createdFrom == 0)
        array.push(task);
    else
        for (let i = 0; i < array.length; i++)
            if (array[i].created == createdFrom) {
                array.splice(i+1, 0, task);
                break;
            }
    setTasks(array);
    await saveList(list, resetFocus(array));
}

const updateItem = async (list, created, text, setTasks) => {
    let array = await getList(list);
    for (let i = 0; i < array.length; i++)
        if (array[i].created == created)
            array[i].text = text;
    setTasks(array);
    await saveList(list, array);
}

const deleteItem = async (list, created, setTasks) => {
    let array = await getList(list);
    if (array.length <= 1)
        return
    array = resetFocus(array);
    for (let i = 0; i < array.length; i++)
        if (array[i].created == created) {
            array.splice(i, 1);
            if (i > 0)
                array[i-1].focus = true;
            break;
        }
    setTasks(array);
    await saveList(list, array);
}

const getInitialList = async (list, setTasks) => {
    let result = await getList(list);
    if (result == null || result.length == 0) {
        await addItem(list, 0, setTasks);
        result = await getList(list);
    } else
        setTasks(result);
    return result;
}

const TaskList = (props) => {
    const [tasks, setTasks] = useState([]);
    useEffect(()=>{
        getInitialList(props.list, setTasks);
    }, []);
    return (
        <FlatList
            data={tasks}
            extraData={tasks}
            renderItem={({item}) => (
                <Task
                    data={item}
                    add={(createdFrom) => {
                        addItem(props.list, createdFrom, setTasks);
                    }}
                    update={(created, text) => {
                        updateItem(props.list, created, text, setTasks);
                    }}
                    delete={(created) => {
                        deleteItem(props.list, created, setTasks);
                    }}
                />
            )}
            keyExtractor={(item) => {
                return item.created
            }}
        />
    );
}

export default TaskList;

components/task.js :

import { StyleSheet, View, CheckBox, TextInput } from 'react-native';
import { useState } from 'react';

const Task = props => {
    let data = props.data;
    console.log(data.created, data.focus);
    const [valueDisplayed, onChangeText] = useState(data.text);
    return (
        <View style={styles.task}>
            <CheckBox/>
            <TextInput
                value={valueDisplayed}
                onChangeText={input => {
                    onChangeText(input);
                    props.update(data.created, input);
                }}
                style={styles.textBox}
                onKeyPress={keyPress => {
                    if (keyPress.nativeEvent.key == "Enter") {
                        props.add(data.created);
                    } else if (data.text.length == 0 && 
                        keyPress.nativeEvent.key == "Backspace") {
                        props.delete(data.created);
                    }
                }}
                autoFocus={data.focus}
            />
        </View>
    );
}

const styles = StyleSheet.create({
  task: {
    flexDirection: "row",
  },
  textBox: {
    flex: 1,
  }
});

export default Task;

Sorry about the amount of code, hopefully my function names are appropriate enough to speed up understanding.

Apologies if it is similar to this question: React-Native TextInput focus on a FlatList

I could not understand the use of 'this' in the answer and cannot yet post a comment

Thanks for your help :)

Update:

Some progress using the link sent by Abe,

TaskList now looks like this:

const TaskList = (props) => {
    const [tasks, setTasks] = useState([]);
    useEffect(()=>{
        getInitialList(props.list, setTasks);
    }, []);
    const refs = useRef(tasks.map(() => createRef()));
    let i = 0;
    return (
        <FlatList
            data={tasks}
            extraData={tasks}
            renderItem={({item}) => (
                <Task
                    data={item}
                    add={(createdFrom) => {
                        addItem(props.list, createdFrom, setTasks);
                    }}
                    update={(created, text) => {
                        updateItem(props.list, created, text, setTasks);
                    }}
                    delete={(created) => {
                        deleteItem(props.list, created, refs, setTasks);
                    }}
                    finish={(created) => {
                        finishItem(props.list, created, setTasks);
                    }}
                    ref={refs[i++]}
                />
            )}
            keyExtractor={(item) => {
                return item.created
            }}
            showsHorizontalScrollIndicator={false}
        />
    );
}

and I am trying to use the reference in this:

const deleteItem = async (list, created, refs, setTasks) => {
    let array = await loadList(list);
    if (array.length <= 1)
        return
    array = resetFocus(array);
    for (let i = 0; i < array.length; i++)
        if (array[i].created == created) {
            array.splice(i, 1);
            if (i > 0) i--;
            array[i].focus = true;
            refs[i].focus();
            break;
        }
    setTasks(array);
    await saveList(list, array);
}

But now get the error

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'focus')

I tried putting a current in between the ref assignment and/or the useage and it has not seemed to help. I think the issue is that this delete function is run before the next render? which would make the ref undefined?

I also thought the error could be that I am removing that list item by splicing it from the array but with refs[i-1].focus(); before the i-- still gives the same error.

Update 2:

I forgot to drill the ref down to the TextInput, adding this with slightly different implementation above it works sometimes: ref={props.inputRef}

I works only if I have rendered again after creating the last note

  • Update: have now tried using json as tasks in useState in the hopes that the whole list would update (I do not know if it tries to be clever with arrays) with extraData but no cigar – Multiple Sources Aug 01 '23 at 21:19
  • Does this answer your question? [How to get dynamic ref and focus in TextInput component on React Native](https://stackoverflow.com/questions/69469067/how-to-get-dynamic-ref-and-focus-in-textinput-component-on-react-native) – Abe Aug 02 '23 at 14:09
  • Perhaps, so far I have tried adding this into TaskList ```let refs = []; for (let i = 0; i < tasks.length; i++) { refs.push(useRef()) } let i = 0;``` and this into the task inside renderItem ```ref={refs.current[i++]}``` but received these: Uncaught Error: Rendered more hooks than during the previous render. Warning: React has detected a change in the order of Hooks called by TaskList. This will lead to bugs and errors if not fixed. – Multiple Sources Aug 02 '23 at 15:04
  • And also this with same error ```ref={refs.current[i++]}``` – Multiple Sources Aug 02 '23 at 15:08
  • Using this ```const refs = useRef(tasks.map(() => createRef()));``` gets me past compilation but fails at runtime, I will add an update to the above code – Multiple Sources Aug 02 '23 at 15:11
  • Israel's answer on the question you linked uses ```createRef()```, mine did not like the ``````, any idea why? – Multiple Sources Aug 02 '23 at 15:21
  • 1
    that's a Typescript annotation – Abe Aug 02 '23 at 17:53

0 Answers0