zack_falcon
zack_falcon

Reputation: 4376

React hook forms with nested fields array from an async source returns empty array

Following this example of nested arrays, I'm trying to display the following data:

 { array: [
    {
        { "last_received_date": "2020-08-03 11:04:18 UTC+8", "tasks": [
            { "id": "1", "freq": "d", "prog": 0, "max": 5, "reward": 1000 },
            { "id": "2", "freq": "d", "prog": 0, "max": 1, "reward": 1000 },
            { "id": "3", "freq": "d", "prog": 0, "max": 3, "reward": 1000 }]
        }
    }, 
    { everything like above but more }
]}

This is a long one, and I'm not well-versed in react hooks myself, so please bear with me. My problem is that the fields in my nested field returns an empty array, if probed or queried any further. Here's a link to a sandbox I made (as far as I could emulate it).

Here's the nested field:

export default function TaskFields({ nestIndex, control, register, errors }) {
    const { fields, remove } = useFieldArray({ control, 
        name: `array[${nestIndex}].challenges` 
    });
    const indexMap = ['Daily', 'Weekly'];
    return (
        <Box>
            {fields.map((task, j) => { // Should repeat thrice, but fields is apparently empty
                return (
                <Box key={j}>
                    <Button variant="danger" onClick={() => {remove(j);}} size="small" }>x</Button>
                    ID: <input name={indexMap[nestIndex] + `task[${j}].id`}  ref={register()} defaultValue={task.id} />                     
                    Freq: // Same as above
                    Prog: // Same as above
                    Max Prog: // Same as above
                    Reward: // Same as above
                </Box> );
            })}
        </Box> );
}

Notice the name: array[${nestIndex}].challenges part; if displayed via console.log, it returns an empty array. If I remove the [${nestIndex}].challenges from it, it returns the complete data set (the value of array). But that outer part of the data has already been dealt with, and displayed, here:

The primary field:

export default function TaskTypeCards({ control, register, errors }) {
const { fields } = useFieldArray({ control, name: "array" });   
const indexMap = ['Daily', 'Weekly'];
return (
    <Box>
        {fields.map((item, i) => { // Will repeat twice, since there are two objects in the array
            return (
                <Box key={i}>
                    Title: {indexMap[i]}
                    Last Received Date: // This will display the last received date
                    <TaskFields data={item} nestIndex={i} {...{ control, register, errors }}/> 
                    // This should display the array of objects in 'tasks'
                </Box>
            )
        })}
    </Box> );
}

The above works fine, and displays two boxes with the Last Received Date, meaning the fields there retained the data I needed from the form, below:

export default UserTaskForm (data) {
    const [challengeData, setChallengeData] = useState({});
    const { register, control, handleSubmit, setError, reset, errors } = useForm(challengeData);
    useEffect(() => {
        async function fetchData() {
            let result = await dbGetUserTasks(data.userId); // Gets results from API
            let challengeData = JSON.parse(result[0].data)); // Looks like the data above
            setChallengeData(challengeData);
            reset(challengeData);
        }
        fetchData();        
    }, [reset]);    
    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <Button type="submit" style={{ marginLeft: "auto" }}>Save Changes</Button>
            {challengeData ? <TaskTypeCards {...{ control, register, errors }} /> : null}
        </form> );
}

So what I'm left with is two boxes with the Last Received Data, but nothing of the tasks that are listed therein, which is far from the example I linked above.

I've tried replacing the array[${nestIndex}].challenges with just array in the name of TaskFields, and then replaced the fields.map to fields[nestIndex].challenges.map which does work in displaying the data, but the delete button does deletes the entire object under array, not an object under tasks, presumably because fields there is set to array.

Is there anything I missed?

Upvotes: 2

Views: 4765

Answers (1)

hgb123
hgb123

Reputation: 14891

I saw two things to notice here

First is that the nested is tasks not challenges

// UserTaskFields.js
// ...
const { fields, remove } = useFieldArray({
  control,
  name: `array[${nestIndex}].tasks`,
})
// ...

Second, you used challengeData with default state of empty object {} to conditionally render if the object is not empty. But you might not remember that Boolean({}) === true, so that you accidentally render from the beginning, even before fetching data. So a solution might be using Object.keys(challengeData).length > 0 instead, or just use a boolean value of fetched for simplicity

const x = {}

console.log(Boolean(x))

console.log(x ? 'foo' : 'bar')

// UserTaskForm.js
const [challengeData, setChallengeData] = useState({});
const [fetched, setFetched] = useState({});
// ...

// This will always render because Boolean({}) return true
{challengeData ? <TaskTypeCards {...{ control, register, errors }} /> : null}

// => should be
{Object.keys(challengeData).length > 0 ? <TaskTypeCards {...{ control, register, errors }} /> : null}

// => other way
{fetched? <TaskTypeCards {...{ control, register, errors }} /> : null}

Below is my forked codesandbox with the fix

Edit compassionate-galileo-nkz71

Upvotes: 3

Related Questions