无法从上下文运行该函数

dbU*_*r11 1 reactjs react-context

我有一个上下文,我将其导入到我的功能组件中:

import { TaskContexts } from "../../../contexts";
Run Code Online (Sandbox Code Playgroud)

上下文存储数据和函数。

数据来自上下文并显示在站点上。

const {
    editTodo,
    setEditID,
    toggleTodoCompletion,
    editID,
    editTodoHandler,
    removeTodo,
    state,
    text,
    isEditError,
 } = useContext(TaskContexts);
Run Code Online (Sandbox Code Playgroud)

但!

<button onClick={() => editTodo(todo.id)}>
   <img src={editIcon} alt="edit button"></img>
</button>
Run Code Online (Sandbox Code Playgroud)

当我尝试调用 editTodo 函数时,它失败并出现以下错误:

Uncaught TypeError: editTodo is not a function
Run Code Online (Sandbox Code Playgroud)

如何修复这个错误?

UPD。

完整组件代码

import React, { useState } from 'react';
import ACTION_TYPES from '../ToDo/reducer/actionTypes';
import RenderedTable from './RenderedTable';
import styles from './TaskList.module.scss';
import allIcon from '../../icons/all.svg';
import completedIcon from '../../icons/completed.svg';
import notCompletedIcon from '../../icons/notCompleted.svg';
import mona from '../../icons/mona.gif';
import { TODO_TASK_CHEMA } from '../../utils/validationSchemas';
import { TaskContexts } from '../../contexts';

const TaskList = props => {
  const {
    reducerData: [state, dispatch],
  } = props;

  const [editID, setEditID] = useState(null);
  const [editText, setEditText] = useState(null);
  const [isEditError, setIsEditError] = useState(false);
  const [mode, setMode] = useState('All');

  const removeTodo = id => {
    dispatch({ type: ACTION_TYPES.REMOVE, id });
  };

  const toggleTodoCompletion = id => {
    dispatch({ type: ACTION_TYPES.TOGGLE, id });
  };

  const editTodo = id => {
    const text = editText.trim();

    try {
      TODO_TASK_CHEMA.validateSync({ text });
    } catch (e) {
      setIsEditError(true);
      throw new Error(e);
    }

    setIsEditError(false);
    setEditID(null);
    dispatch({ type: ACTION_TYPES.EDIT, id, text });
    setEditText(null);
  };

  const editTodoHandler = ({ target: { value } }) => {
    setEditText(value);
  };

  const contextsValues = {
    editID,
    setEditID,
    editText,
    setEditText,
    isEditError,
    setIsEditError,
    mode,
    setMode,
    state
  };

  return (
    <TaskContexts.Provider value={contextsValues}>
      <div className={styles.container}>
        {state.todos.length === 0 ? (
          <div>
            <h2 className={styles.noTask}>No tasks =)</h2>
            <img src={mona} alt='mona gif' />
          </div>
        ) : (
          <>
            <button
              className={styles.section}
              onClick={() => {
                setMode('All');
              }}
            >
              <img src={allIcon} alt='all button' />- All
            </button>
            <button
              className={styles.section}
              onClick={() => {
                setMode('Completed');
              }}
            >
              <img src={completedIcon} alt='completed button' />- Completed
            </button>
            <button
              className={styles.section}
              onClick={() => {
                setMode('NotCompleted');
              }}
            >
              <img src={notCompletedIcon} alt='not completed button' />- Not
              completed
            </button>

            <RenderedTable
              editTodo={editTodo}
              setEditID={setEditID}
              toggleTodoCompletion={toggleTodoCompletion}
              editID={editID}
              editTodoHandler={editTodoHandler}
              removeTodo={removeTodo}
              state={state}
              mode={mode}
              isEditError={isEditError}
            />
          </>
        )}
      </div>
    </TaskContexts.Provider>
  );
};

export default TaskList;
Run Code Online (Sandbox Code Playgroud)

该组件上的所有功能都不起作用。但这些都是功能。我不明白为什么 React 不这么认为。

Chi*_*olo 7

您需要做 3 件事才能成功传递上下文值:

  • 将上下文提供者放置在消费组件之上至少一层。
  • 创建您的上下文,声明上下文中的所有变量和方法,并在传递valueProp 后导出上下文的提供程序。
  • useContext()通过导入钩子TaskList.jsx/TaskList.js并在 Provider 对象上调用它来使用上下文值。

将上下文提供者放置在消费组件之上至少一层

editTodoJavaScript 认为不是函数或未定义的原因是,<TaskList/>在它 ( <TaskList/>) 意识到上下文之前,您试图在组件内的 React 中使用它。当<TaskList/>React 渲染时,传递任何上下文值都为时已晚。因此,我们需要将上下文放置在组件树的较高位置,在树中渲染(并将上下文值传递给)子组件之前,React 将提前了解上下文及其值。

要解决此问题,请将上下文提供程序包装器放置在消耗上下文提供程序值的组件之上至少一层。如果多个组件需要来自提供者的值,则放置提供者包装器的最佳位置将是在您App.jsx/App.js或您的index.jsx/index.js文件中。

里面App.jsx/App.js

import { TaskProvider } from 'path/to/context';

function App() { 
  <TaskProvider>
    {/* All your code/rendered elements/rendered route elements go here */}
  </TaskProvider>
} 

export default App; 
Run Code Online (Sandbox Code Playgroud)

或内部index.jsx/index.js

import React from "react";
import ReactDOM from "react-dom";
import { ToastProvider } from "path/to/context";
import "./index.css";
import App from "./App";

ReactDOM.render(
  <React.StrictMode>
    <ToastProvider>
      <App />
    </ToastProvider>
  </React.StrictMode>,
  document.getElementById("root")
);
Run Code Online (Sandbox Code Playgroud)

我将向您展示一种更好的方法来传递这些上下文值。

创建您的上下文,声明上下文中的所有变量和方法,并在传递valueProp 后导出上下文的提供程序:

里面TaskContexts.jsx/TaskContexts.js

import {useContext, createContext } from "react"; 
// ...All your necessary imports

// Create context first
const TaskContexts = createContext();

export const TaskProvider = ({ children }) => { 

  const [editID, setEditID] = useState(null);
  const [editText, setEditText] = useState(null);
  const [isEditError, setIsEditError] = useState(false);
  const [mode, setMode] = useState('All');

  const removeTodo = id => {
    dispatch({ type: ACTION_TYPES.REMOVE, id });
  };

  const toggleTodoCompletion = id => {
    dispatch({ type: ACTION_TYPES.TOGGLE, id });
  };

  const editTodo = id => {
    const text = editText.trim();

    try {
      TODO_TASK_CHEMA.validateSync({ text });
    } catch (e) {
      setIsEditError(true);
      throw new Error(e);
    }

    setIsEditError(false);
    setEditID(null);
    dispatch({ type: ACTION_TYPES.EDIT, id, text });
    setEditText(null);
  };
  
  // ...and the rest of the methods

  // Prepare your contextValues object here
  const contextValues = {
    editID,
    setEditID,
    // ...and the rest
  };

  // Notice that we have called the provider here
  // so that we don't have to do it within the `App.jsx` or `index.jsx`. 
  // We have also passed the default values here so we can that 
  // we don't have to export them and pass them in `App.jsx`. 
  // We used component composition to create a `hole` where the rest of 
  // our app, i.e, `{children}` will go in and returned the 
  // composed component from here, i.e, `<TaskProvider/>`. 
  // This is so that all the preparation of the context Provider object 
  // gets done in one file.
  return (<TaskContexts.Provider value={contextValues}>
              {children}
          </TaskContexts.Provider>);
};

// Now, use the context, we will export it in a function called `useTask()` 
// so that we don't have to call `useContext(TaskContexts)` every time we need values from the context. 

// This function will call `useContext()` for us and return the values 
// in the provider available as long as we wrap our app components 
// with the provider (which we have already done).

export function useTask() {
  return useContext(TaskContexts);
}
Run Code Online (Sandbox Code Playgroud)

useContext()通过导入钩子TaskList.jsx/TaskList.js并在 Provider 对象上调用它来使用上下文值。

由于我们已经调用useContext了提供者对象,我们只需要从 useTask()前面导入TaskList.jsx,运行它,它将返回contextValues我们可以解构的对象。

import React, { useState } from 'react';
import ACTION_TYPES from '../ToDo/reducer/actionTypes';
import RenderedTable from './RenderedTable';
import styles from './TaskList.module.scss';
import allIcon from '../../icons/all.svg';
import completedIcon from '../../icons/completed.svg';
import notCompletedIcon from '../../icons/notCompleted.svg';
import mona from '../../icons/mona.gif';
import { TODO_TASK_CHEMA } from '../../utils/validationSchemas';

// Import `useTask` only.
import { useTask } from '../../contexts';

const TaskList = props => { 

  // Values from context 
  const {editID, setEditID,...} = useTask();

  const {
    reducerData: [state, dispatch],
  } = props;

  const [editID, setEditID] = useState(null);
  const [editText, setEditText] = useState(null);
  const [isEditError, setIsEditError] = useState(false);
  const [mode, setMode] = useState('All'); 

  const removeTodo = id => {
    dispatch({ type: ACTION_TYPES.REMOVE, id });
  };

  const toggleTodoCompletion = id => {
    dispatch({ type: ACTION_TYPES.TOGGLE, id });
  };

  const editTodo = id => {
    const text = editText.trim();

    try {
      TODO_TASK_CHEMA.validateSync({ text });
    } catch (e) {
      setIsEditError(true);
      throw new Error(e);
    }

    setIsEditError(false);
    setEditID(null);
    dispatch({ type: ACTION_TYPES.EDIT, id, text });
    setEditText(null);
  };

  const editTodoHandler = ({ target: { value } }) => {
    setEditText(value);
  };

  return (
      <div className={styles.container}>
          {/*...everything else */}

          <RenderedTable
              editTodo={editTodo}
              setEditID={setEditID}
              toggleTodoCompletion={toggleTodoCompletion}
              editID={editID}
              editTodoHandler={editTodoHandler}
              removeTodo={removeTodo}
              state={state}
              mode={mode}
              isEditError={isEditError}
            />
          </>
        )}
      </div>
  );
};

export default TaskList; 
Run Code Online (Sandbox Code Playgroud)

总之,将上下文对象的所有内容范围限制在它自己的组件中,在它自己的文件中,将其导出并将所有子组件包装在根组件中(或包装根组件本身),然后调用组件中的提供useContext()程序对象需要上下文值。