简体   繁体   English

对象的初始数据在使用 useState 挂钩更新 state 时被重置

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

I'm trying to implement Drag and Drop component offered by react-beautiful-dnd, for mapping courses to a program from a given set of courses available on the database.我正在尝试实现 react-beautiful-dnd 提供的拖放组件,用于将课程从数据库上可用的一组给定课程映射到程序。 DND(Drag&Drop) component has 2 columns - Mapped Courses & Available Courses, the data of which is fetched from API. DND(Drag&Drop) 组件有 2 列 - Mapped Courses & Available Courses,其数据取自 API。 For accomplishing this, I'm make an initialData object, populating it with Mapped Courses & Available Courses fetched from DB, then finally copying this initialData to the columns of DND Component.为了实现这一点,我制作了一个 initialData object,用从 DB 获取的映射课程和可用课程填充它,然后最终将此 initialData 复制到 DND 组件的列中。 Problem - When updating initialData object with 'Mapped Courses', 'Available Courses' is getting reset, and while updating 'Available Courses', 'Mapped Courses' is getting reset.问题 - 使用“映射课程”更新 initialData object 时,“可用课程”正在重置,而在更新“可用课程”时,“映射课程”正在重置。

Code -代码 -

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
)
}

It seems the issue is more to do with enqueueing more than a single update to the same state within a render cycle's scope.似乎这个问题更多地与在渲染周期的 scope 中对同一 state 的一次更新进行排队有关。 In other words, in the useEffect callback you are enqueueing two state updates that are both updating from the stale enclosure of 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. 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.

You are also declaring the useEffect callback as async which isn't a correct use of the hook as the callback implicitly returns a Promise which React interprets as a returned effect cleanup function.您还将useEffect回调声明为async ,这不是钩子的正确使用,因为回调隐式返回 Promise,React 将其解释为返回的效果清理 function。 It's also anti-pattern to declare a function async and then use Promise chains instead of awaiting promises to resolve.声明 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 . 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. 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