簡體   English   中英

對象的初始數據在使用 useState 掛鈎更新 state 時被重置

[英]Object's initial data getting reset on updating state using useState hook

我正在嘗試實現 react-beautiful-dnd 提供的拖放組件,用於將課程從數據庫上可用的一組給定課程映射到程序。 DND(Drag&Drop) 組件有 2 列 - Mapped Courses & Available Courses,其數據取自 API。 為了實現這一點,我制作了一個 initialData object,用從 DB 獲取的映射課程和可用課程填充它,然后最終將此 initialData 復制到 DND 組件的列中。 問題 - 使用“映射課程”更新 initialData object 時,“可用課程”正在重置,而在更新“可用課程”時,“映射課程”正在重置。

代碼 -

const MainComponent=()=>{

 //initialData containing courses fetched from DB
 const [initialData, setInitialData] = useState({
    ['Mapped Courses']: {
        name: 'Mapped Courses',
        items: []
    },
    ['Available Courses']: {
        name: 'Available Courses',
        items: []
    }
 });

 //columns of react DnD containing courses after they've been fetched
 const [columns, setColumns] = useState({
     ['Mapped Courses']: {
         name: 'Mapped Courses',
         items: []
     },
     ['Available Courses']: {
         name: 'Available Courses',
         items: []
     }
 });

//react DnD fn for making changes after drag
const onDragEnd = (result, columns, setColumns) => {
    if (!result.destination) return;
    const { source, destination } = result;

    if (source.droppableId !== destination.droppableId) {
        const sourceColumn = columns[source.droppableId];
        const destColumn = columns[destination.droppableId];
        const sourceItems = [...sourceColumn.items];
        const destItems = [...destColumn.items];
        const [removed] = sourceItems.splice(source.index, 1);
        destItems.splice(destination.index, 0, removed);
        setColumns({
            ...columns,
            [source.droppableId]: {
                ...sourceColumn,
                items: sourceItems
            },
            [destination.droppableId]: {
                ...destColumn,
                items: destItems
            }
        });
        console.log(columns);
    } else {
        const column = columns[source.droppableId];
        const copiedItems = [...column.items];
        const [removed] = copiedItems.splice(source.index, 1);
        copiedItems.splice(destination.index, 0, removed);
        setColumns({
            ...columns,
            [source.droppableId]: {
                ...column,
                items: copiedItems
            }
        });
        console.log(columns);
    }
};

useEffect(async()=>{
axios
    .get(`http://programsAPI/program`)
    .then((response) =>
        setInitialData({
            ...initialData,
            ['Mapped Courses']: {
                ...initialData['Mapped Courses'],
                items: response.data.courses.map((course) => ({ id: course.id, content: course.slug }))
            }
        })
    )
    .then(console.log(`Initial Data after Mapping Courses - `), console.log(initialData))
    .then(
        axios
            .get('http://coursesAPI/courses')
            .then((response) =>
                setInitialData({
                    ...initialData,
                    ['Available Courses']: {
                        ...initialData['Available Courses'],
                        items: response.data.map((course) => ({ id: course._id, content: course.info.slug }))
                    }
                })
            )
            .then(console.log('Initial Data after Getting Courses - '), console.log(initialData))
            .catch((err) => console.log(err))
    )
    .catch((err) => console.log(err))
)
}, [programLoads])

useEffect(() => {
    setColumns({
        ...columns,
        ['Mapped Courses']: {
            ...columns['Mapped Courses'],
            items: initialData['Mapped Courses'].items
        },
        ['Available Courses']: {
            ...columns['Available Courses'],
            items: initialData['Available Courses'].items
        }
    });
    console.log('Initial Data after setting columns/update');
    console.log(initialData);
}, [initialData]);

return ( 
    //HTML STARTS HERE
)
}

似乎這個問題更多地與在渲染周期的 scope 中對同一 state 的一次更新進行排隊有關。 換句話說,在useEffect回調中,您將兩個 state 更新排入隊列,這兩個更新都是從 state 的陳舊外殼更新的。

You can remedy this by using a functional state update so each individual update is applied against the previous state and not the state value closed over in scope.

您還將useEffect回調聲明為async ,這不是鈎子的正確使用,因為回調隱式返回 Promise,React 將其解釋為返回的效果清理 function。 聲明 function async然后使用 Promise 鏈而不是等待承諾解決也是反模式。

Promise chains were also designed/meant to solve a problem of callback nesting, you can return the second axios Promise and flatten the Promise chain and use a single catch .

useEffect(() => {
  const courseMapFn = (course) => ({
    id: course._id,
    content: course.info.slug,
  });

  axios
    .get(`http://programsAPI/program`)
    .then((response) =>
      setInitialData(initialData => ({ // <-- previous state
        ...initialData,
        ['Mapped Courses']: {
          ...initialData['Mapped Courses'],
          items: response.data.courses.map(courseMapFn),
        },
      }))
    )
    .then(() => axios.get('http://coursesAPI/courses'))
    .then((response) =>
      setInitialData(initialData => ({ // <-- previous state
        ...initialData,
        ['Available Courses']: {
          ...initialData['Available Courses'],
          items: response.data.map(courseMapFn),
        },
      }))
    )
    .catch((err) => console.log(err))
  )
}, [programLoads]);

This will enqueue two state updates though, an improvement may be to use an async function and await both GET requests to resolve and map and enqueue a single state update.

useEffect(() => {
  const courseMapFn = (course) => ({
    id: course._id,
    content: course.info.slug,
  });

  const fetchInitialData = async () => {
    try {
      const mappedCourseData = await axios.get(`http://programsAPI/program`);
      const availableCourseData = await axios.get('http://coursesAPI/courses');

      setInitialData(initialData => ({ // <-- previous state
        ...initialData,
        ['Mapped Courses']: {
          ...initialData['Mapped Courses'],
          items: mappedCourseData.data.courses.map(courseMapFn),
        },
       ['Available Courses']: {
          ...initialData['Available Courses'],
          items: availableCourseData.data.map(courseMapFn),
        },,
      }));
    } catch(error) {
      console.error(error);
    }

    fetchInitialData(); // <-- invoke async funciton
}, [programLoads]);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM