Reputation: 493
I am learning React and building a project using context. Originally I build the context in the App.js component but I want to separate it into its own file to keep App.js cleaner. However, I am having trouble figuring out how this is done or if its even possible. I think the part I am stuck on is how to provide to the context. I pasted my new Context file below along with App.js with the original Context structure commented out. Does anyone know how I can accomplish this?
// TaskManipulatorContext
import React, { useReducer } from "react";
import AddTask from "../Components/dnd_components/AddTask";
export const TaskManipulatorContext = React.createContext();
const taskManipulatorInitialState = {
panelData: {
height: 50,
open: false,
title: "title",
content: "content",
},
newTask: false,
deleteTask: false,
taskContainer: null,
};
const taskManipulatorReducer = (state, action) => {
switch (action.type) {
case "addTask-botPanel":
// Brings up bottom panel to add new task
return {
...state,
panelData: {
...state.panelData,
height: 20,
open: true,
title: "Add Task",
content: <AddTask />,
},
taskContainer: {
...state.taskContainer,
...action.value,
},
};
case "addTask-submitTask":
// Submits task and adds it to list
return {
...state,
panelData: {
...state.panelData,
closing: true,
},
newTask: true,
taskContainer: {
...state.taskContainer,
newTask: action.value,
},
};
case "reset":
// Reset back to initial state
return taskManipulatorInitialState;
default:
return taskManipulatorInitialState;
}
};
const [state, dispatch] = useReducer(
taskManipulatorReducer,
taskManipulatorInitialState
);
// App.js
import React, { useContext } from "react";
import "./index.css";
import RenderXMilageBoxes from "./Components/RenderXMilageBoxes";
import BottomPanel from "./Components/BottomPanel";
import AddTask from "./Components/dnd_components/AddTask";
import { TaskManipulatorContext } from "./Contexts/TaskManipulatorContext";
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * CONTEXTS * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// export const TaskManipulatorContext = React.createContext();
// const taskManipulatorInitialState = {
// panelData: {
// height: 50,
// open: false,
// title: "title",
// content: "content",
// },
// newTask: false,
// deleteTask: false,
// taskContainer: null,
// };
// const taskManipulatorReducer = (state, action) => {
// switch (action.type) {
// case "addTask-botPanel":
// // Brings up bottom panel to add new task
// return {
// ...state,
// panelData: {
// ...state.panelData,
// height: 20,
// open: true,
// title: "Add Task",
// content: <AddTask />,
// },
// taskContainer: {
// ...state.taskContainer,
// ...action.value,
// },
// };
// case "addTask-submitTask":
// // Submits task and adds it to list
// return {
// ...state,
// panelData: {
// ...state.panelData,
// closing: true,
// },
// newTask: true,
// taskContainer: {
// ...state.taskContainer,
// newTask: action.value,
// },
// };
// case "reset":
// // Reset back to initial state
// return taskManipulatorInitialState;
// default:
// return taskManipulatorInitialState;
// }
// };
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * MAIN COMPONENT * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function App() {
// Contexts
// const [state, dispatch] = useReducer(
// taskManipulatorReducer,
// taskManipulatorInitialState
// );
const taskManipulatorContext = useContext(TaskManipulatorContext);
return (
<div>
<taskManipulatorContext.Provider
value={{
state,
dispatch,
}}
>
<RenderXMilageBoxes currentMiles={5001} numFutureServices={5} />
<BottomPanel panelData={state.panelData} />
</taskManipulatorContext.Provider>
</div>
);
}
export default App;
Upvotes: 2
Views: 3340
Reputation: 1506
My preferred way of doing this is to create a context, then provide the same context to both a Provider and a useContext hook to consume in components.
It would look something like this:
TaskManiupulatorContext.js
const TaskManipulatorContext = createContext({})
// dispatcher logic
export const TaskManipulatorContextProvider = () => {
const [state, dispatch] = useReducer(
taskManipulatorReducer,
taskManipulatorInitialState
);
// You can choose to wrap this in a useMemo if you want to be extra careful about potential rerenders
const taskManipulatorContextStore = {
state,
dispatch,
}
return <TaskManipulatorContext.Provider value={taskManipulatorContextStore}>{children}</TaskManipulatorContext.Provider>
}
export const useTaskManipulatorContext = () => useContext(TaskManipulatorContext)
Then make the context available at the app level by wrapping your app with the provider:
App.js
import { TaskManipulatorContextProvider } from './TaskManipulatorContext'
// app logic
const App = () => {
return (
<TaskManipulatorContextProvider>
<App />
</TaskManipulatorContextProvider>
)
}
Now, from any component inside the App, you can import your useTaskManipulatorContext
hook and consume it:
SomeComponent.js
import { useTaskManipulatorContext } from './TaskManipulatorContext'
export const SomeComponent = () => {
const taskManipulatorCtx = useTaskManipulatorContext()
const someFunctionWhichUpdatesContextState = (action) => {
taskManipulatorCtx.dispatch(action)
}
return (
<div>
<text>{'Here is some state from the context'}</text>
<text>{taskManipulatorCtx.state}</text>
</div>
)
}
Note that taking this approach gives you a lot of control over the access patterns inside of the Context. For example, if you don't want components to have direct access to the dispatch in order to change state, you can create a method inside your Provider which dispatches some action, and export it as a method which your functions can consume (ie. you don't rely on your components, or developers consuming your components, to know the exact behavour/implementation of how to update state).
const taskManipulatorContextStore = {
state,
addTask: (taskToAdd) = dispatch(actions.add, taskToAdd)
}
Upvotes: 8