来自redux的值在组件体中与返回函数中不同

1hdlvixo  于 12个月前  发布在  其他
关注(0)|答案(1)|浏览(114)

我试图从redux store中获取最新的历史值,以传递有效负载。问题是,每当我提交一个查询时,历史记录都会被更新,并在return()中使用map()循环,它显示的是最新的值,但在submitQuery()中的// CONSOLE HERE,它一直显示旧的值。

export default function Chat() {
  const dispatch = useDispatch();
  const { customInstructionId, chatId } = useParams()
  const { history } = useSelector((state) => state.customData)
  const [query, setQuery] = useState('');
  const [loading, setLoading] = useState(false)

  const handleSubmitQuery = async () => {
    if(!query || query === "" || query === null) {
      return notification("Query can't be empty.",  "error")
    }
    setLoading(true)
    const data = { id: Math.random(), role: 'user', content: query }
    setQuery("")
    dispatch(addData(data))
    const config = { query: [...history, data], key: customInstructionId }
    try {
      const response = await fetch("http://localhost:8000/api/query/chat", {
        method: "post",
        headers: {
          Accept: "application/json, text/plain, */*",
          "Content-Type": "application/json",
          Authorization: `Bearer ${token}`
        },
        body: JSON.stringify(config),
      });
      if (!response.ok || !response.body) {
        throw response.statusText;
      }
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      const loopRunner = true;
      let str = ""
      let count = 0
      const resId = Math.random()
      setLoading(false);
      /* eslint-disable no-await-in-loop */
      while (loopRunner) {
        const { value, done } = await reader.read();
        if (done) {
            // CONSOLE HERE
          handleSaveHistory()
          break;
        }
        const decodedChunk = decoder.decode(value, { stream: true });
        if(value[0] === 10){
          str += "\n"
        } else {
          str += decodedChunk
        }
        if(count === 0){
          dispatch(addData({ id: resId, role: 'assistant', content: str }))
        } else {
          dispatch(updateData({ id: resId, content: str }))
        }
        count += 1
      }
      // CONSOLE HERE
    } catch (err) {
      console.error(err, "err");
    } finally {
      setLoading(false);
    }
  }

  const handleSaveHistory = async () => {
    // CONSOLE HERE
    const config = saveHistory({ history, customInstructionId, chatId }, token)
    axios(config)
    .then((response) => {
      console.log(response.data)
    })
    .catch((error) => {
      console.log(error)
      notification(`Unable to save the history.`, "error")
    })
  }

  const handleKeyDown = (event) => {
    if (event.key === 'Enter') {
        handleSubmitQuery();
    }
  };

  return (
    <Page title="Query" sx={{ height: '100%' }}>
      <Container sx={{ height: '100%' }}>
        <Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-between', height: '100%' }}>
          <Box sx={{ overflowY: 'scroll', height: '70vh'}}>
            {(history && history.length > 0) ? history.map((item) =>
              <Card key={item.id} sx={{ marginBottom: '1rem' }}>
                <Typography sx={{ padding: '1rem', backgroundColor: item.role === 'user' ? '#f8eff0' : '#edeff1' }}>
                  <div dangerouslySetInnerHTML={{ __html: item.content.replace(/\n/g, '<br />') }} />
                </Typography>
              </Card>
            ) : 
            <Card sx={{ marginBottom: '1rem' }}>
              <Typography sx={{ padding: '1rem', backgroundColor: '#edeff1' }}>Hi! How can I assist you today?</Typography>
            </Card>
            }
            {loading && loading === true &&
            <Card sx={{ marginBottom: '1rem' }}>
              <Typography sx={{ padding: '1rem', backgroundColor: '#edeff1' }}>
                <Skeleton variant="text" sx={{ fontSize: '1rem' }} />
              </Typography>
            </Card>
            }
          </Box>
        </Box>
      </Container>
    </Page>
  );
}

Redux商店:

export const counterSlice = createSlice({
  name: "customData",
  initialState: {
    history: [],
    boolean: true,
    string: ""
  },
  reducers: {
    addData: (state, action) => {
      state.history.push(action.payload)
    },
    updateData: (state, action) => {
      state.history = state.history.map(data => {
        if (data.id === action.payload.id) {
          return {
            ...data,
            content: action.payload.content
          };
        }
        return data;
      });
    },
    removeData: (state, action) => {
      state.history = state.history.filter(data => data.id !== action.payload)
    },
    recentData: (state, action) => {
      state.history = action.payload
    },
    clearData: (state) => {
      state.history = []
    }
  }
});

export const { addData, updateData, removeData, clearData, recentData } = counterSlice.actions;

export default counterSlice.reducer;

注意:这不是完整的代码,只是显示了问题的上下文。如果有人知道为什么会发生这种情况。
在下面的图片中,历史记录确实是用dispatch()更新的,我可以在UI中看到它(就像在chatgpt中一样,它是逐字逐句地更新的)。当整个响应完成后,我将执行saveHistory函数,但在该函数中,它不接受最近的问题/答案。

历史(UI中):

  1. ABC
  2. def
  3. ghi
  4. jkl
    历史记录(在saveHistory函数中):
  5. ABC
  6. def

**UPDATE:**问题是在提交函数中调用保存函数。下面解决了这个问题,但我知道这不是一个好的做法。

useEffect(() => {
    if(ready === true) {
      handleSaveHistory()
      setReady(false)
    }
  }, [ready]);
sf6xfgos

sf6xfgos1#

异步工作需要在组件外部使用createAsyncThunk完成。以下内容应该放在你的Redux商店中:

const handleSaveHistory = createAsyncThunk(
  'customData/handleSaveHistory',
  async (customInstructionId, chatId, thunkAPI) => {
    // CONSOLE HERE
    const config = thunkAPI.dispatch(saveHistory({
      history: thunkAPI.getState().history,
      customInstructionId,
      chatId,
    }, token))
    axios(config)
      .then((response) => {
        console.log(response.data)
      })
      .catch((error) => {
        console.log(error)
        notification(`Unable to save the history.`, "error")
      })
  } 
);

const fetchUserById = createAsyncThunk(
  'customData/handleSubmitQuery',
  async (query, customInstructionId, chatId, thunkAPI) => {
    if(!query || query === "" || query === null) {
      return notification("Query can't be empty.",  "error")
    }
    const data = { id: Math.random(), role: 'user', content: query }
    thunkAPI.dispatch(addData(data))
    const config = { query: [...history, data], key: customInstructionId }
    try {
      const response = await fetch("http://localhost:8000/api/query/chat", {
        method: "post",
        headers: {
          Accept: "application/json, text/plain, */*",
          "Content-Type": "application/json",
          Authorization: `Bearer ${token}`
        },
        body: JSON.stringify(config),
      });
      if (!response.ok || !response.body) {
        throw response.statusText;
      }
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      const loopRunner = true;
      let str = ""
      let count = 0
      const resId = Math.random()
      /* eslint-disable no-await-in-loop */
      while (loopRunner) {
        const { value, done } = await reader.read();
        if (done) {
            // CONSOLE HERE
          thunkAPI.dispatch(handleSaveHistory(customInstructionId, chatId))
          break;
        }
        const decodedChunk = decoder.decode(value, { stream: true });
        if(value[0] === 10){
          str += "\n"
        } else {
          str += decodedChunk
        }
        if(count === 0){
          thunkAPI.dispatch(addData({ id: resId, role: 'assistant', content: str }))
        } else {
          thunkAPI.dispatch(updateData({ id: resId, content: str }))
        }
        count += 1
      }
      // CONSOLE HERE
    } catch (err) {
      console.error(err, "err");
    }
  }
)

问题是你的钩子handleSaveHistory函数关闭了旧版本的数据。您有async函数,这些函数在先前呈现的组件版本中执行。
初始状态:

Chat1 (visible to user)
 - history1
 - handleSubmitQuery1
 - handleSaveHistory1

下一个状态:

(re-rendered after executing `await fetch`)

Chat1 (DEAD)                      Chat2 (visible to user)
 - history1                        - history2
 - handleSubmitQuery1 (awaiting)   - handleSubmitQuery2
 - handleSaveHistory1              - handleSaveHistory2

现在fetch返回到 * 旧的、死的Chat1组件 * 中
下一个状态:

Chat1 (DEAD)                      Chat2 (visible to user)
 - history1                        - history2
 - handleSubmitQuery1              - handleSubmitQuery2
 - handleSaveHistory1 (console!)   - handleSaveHistory2

相关问题