I am using useRef hook in my context. I am logging that out in useEffect hook inside context. I passing that useRef variable so that it can be accessed my components. Here is the context.
import { createContext, useContext, useRef, useEffect } from "react";
export const SearchContext = createContext({});
export const useSearch = () => useContext(SearchContext);
const SearchProvider = ({ children }) => {
const loader = useRef(null);
useEffect(() => {
console.log(loader); // returns {current: null}
}, []);
return (
<SearchContext.Provider
value={{
loader
}}
>
{children}
</SearchContext.Provider>
);
};
export default SearchProvider;
Next in my component I am adding ref property to div element with value loader which is coming from context. However, when I run this code I see {current: null} for loader. How can I use useRef in context to have access to DOM elements in component to make this work?
Here is my component
import { useSearch } from "./context";
const Component = () => {
const { loader } = useSearch();
return (
<div>
<div ref={loader}>Hello</div>
</div>
);
};
export default Component;
Here is the sandbox link.
https://codesandbox.io/s/nervous-jepsen-l83mf?file=/src/component.js:0-203
Related
Considering the following project setup on a react-redux application that uses context API to avoid prop drilling. The example given is simplified.
Project Setup
React project uses React Redux
Uses context API to avoid prop drilling in certain cases.
Redux store has a prop posts which contains list of posts
An action creator deletePost(), which deletes a certain post by post id.
To avoid prop drilling, both posts and deletePosts() is added to a context AppContext and returned by a hook funciton useApp().
posts array is passed via contexts so it is not used by connect() function. Important
Problem:
When action is dispatched store is updated however Component is not re-rendered (because the prop is not connected?). Of course, if I pass the prop with connect function and drill it down to child rendering works fine.
What is the solution?
Example Project
The example project can be found in codesandbox. Open up the console and try to click the delete button. You will see no change in the UI while you can see the state is updated in the console.
Codes
App.js
import Home from "./routes/Home";
import "./styles.css";
import { AppProvider } from "./context";
export default function App() {
return (
<AppProvider>
<div className="App">
<Home />
</div>
</AppProvider>
);
}
context.js
import { useDispatch, useStore } from "react-redux";
import { useContext, createContext } from "react";
import { deletePost } from "./redux/actions/posts";
export const AppContext = createContext();
export const useApp = () => {
return useContext(AppContext);
};
export const AppProvider = ({ children }) => {
const dispatch = useDispatch();
const {
posts: { items: posts }
} = useStore().getState();
const value = {
// props
posts,
// actions
deletePost,
dispatch
};
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};
Home.js
import { connect } from "react-redux";
import Post from "../components/Post";
import { useApp } from "../context";
const Home = () => {
const { posts } = useApp();
return (
<section>
{posts.map((p) => (
<Post key={p.id} {...p} />
))}
</section>
);
};
/*
const mapProps = ({ posts: { items: posts } }) => {
return {
posts
};
};
*/
export default connect()(Home);
Post.js
import { useApp } from "../context";
const Post = ({ title, content, id }) => {
const { deletePost, dispatch } = useApp();
const onDeleteClick = () => {
console.log("delete it", id);
dispatch(deletePost(id));
};
return (
<article>
<h1>{title}</h1>
<p>{content}</p>
<div className="toolbar">
<button onClick={onDeleteClick}>Delete</button>
</div>
</article>
);
};
export default Post;
You're not using the connect higher order component method properly . Try using it like this so your component will get the states and the function of your redux store :
import React from 'react';
import { connect } from 'react-redux';
import { callAction } from '../redux/actions.js';
const Home = (props) => {
return (
<div> {JSON.stringify(props)} </div>
)
}
const mapState = (state) => {
name : state.name // name is in intialState
}
const mapDispatch = (dispatch) => {
callAction : () => dispatch(callAction()) // callAction is a redux action
//and should be imported in the component also
}
export default connect(mapState , mapDispatch)(Home);
You can access the states and the actions from your redux store via component props.
Use useSelector() instead of useState(). Example codepen is fixed.
Change from:
const { posts: { items: posts } } = useStore().getState();
Change to:
const posts = useSelector(state => state.posts.items);
useStore() value is only received when component is first mounted. While useSlector() will get value when value is changed.
I just started playing with context today and this is my usercontext
import { createContext, useEffect, useState } from "react";
import axios from "axios";
export const userContext = createContext({});
const UserContext = ({ children }) => {
const [user, setUser] = useState({});
useEffect(() => {
axios.get("/api/auth/user", { withCredentials: true }).then((res) => {
console.log(res);
setUser(res.data.user);
});
}, []);
return <userContext.Provider value={user}>{children}</userContext.Provider>;
};
export default UserContext;
this is how im using it in any component that needs the currently logged in user
const user = useContext(userContext)
my question is whenever the user logs in or logs out I have to refresh the page in order to see the change in the browser. is there any way that I can do this where there does not need to be a reload. also any general tips on react context are appreciated
(EDIT)
this is how Im using the UserContext if it helps at all
const App = () => {
return (
<BrowserRouter>
<UserContext>
<Switch>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
component={route.component}
/>
))}
</Switch>
</UserContext>
</BrowserRouter>
);
};
Where is your context consumer?
The way it is set up, any userContext.Consumer which has a UserContext as its ancestor will re render when the associated user is loaded, without the page needing to be reloaded.
To make it clearer you should rename your UserContext component to UserProvider and create a corresponding UserConsumer component:
import { createContext, useEffect, useState } from "react";
import axios from "axios";
export const userContext = createContext({});
const UserProvider = ({ children }) => {
const [user, setUser] = useState({});
useEffect(() => {
axios.get("/api/auth/user", { withCredentials: true }).then((res) => {
console.log(res);
// setting the state here will trigger a re render of this component
setUser(res.data.user);
});
}, []);
return <userContext.Provider value={user}>{children}</userContext.Provider>;
};
const UserConsumer = ({ children }) => {
return (
<userContext.Consumer>
{context => {
if (context === undefined) {
throw new Error('UserConsumer must be used within a UserProvider ')
}
// children is assumed to be a function, it must be used
// this way: context => render something with context (user)
return children(context)
}}
</userContext.Consumer>
);
};
export { UserProvider, UserConsumer };
Usage example:
import { UserConsumer } from 'the-file-containing-the-code-above';
export const SomeUiNeedingUserInfo = props => (
<UserConsumer>
{user => (
<ul>
<li>{user.firstName}</>
<li>{user.lastName}</>
</ul>
)}
</UserConsumer>
)
To be fair, you could also register to the context yourself, this way for a functional component:
const AnotherConsumer = props => {
const user = useContext(userContext);
return (....);
}
And this way for a class component:
class AnotherConsumer extends React.Component {
static contextType = userContext;
render() {
const user = this.context;
return (.....);
}
}
The benefit of the UserConsumer is reuasability without having to worry if you're in a functional or class component: it will used the same way.
Either way you have to "tell" react which component registers (should listen to) the userContext to have it refreshed on context change.
That's the whole point of context: allow for a small portion of the render tree to be affected and avoid prop drilling.
I have navigation constant which is an array of objects(web-store mega-nav). I need to use context provider and when I'm trying to use my context it's telling me NavContext' is not defined no-undef.
NavContext.js
import { createContext } from 'react'
const navigation = [...] // array of objects
const NavContext = createContext(navigation)
export default NavContext
Nav.js
import {createContext} from 'react'
import NavContext from './context/NavContext' //added
function Nav() {
return (
<NavContext.Provider> //deleted value
// childrens
</NavContext.Provider>
)
}
Sidebar.js
//then in one of the child I'm trying to call it:
import { useContext } from 'react'
import NavContext from '../context/NavContext' //added
function Sidebar(){
const nav = useContext(NavContext)
return (
{nav.map(...)} // nav is undefined
)
}
Now nav constant is undefined when I'm using useContext
You need to export the create context like this
export const NavContext = createContext(navigation)
Then import it into your child component like this
import { NavContext} from "../Nav";
//Create a new NavContext.js File.
import React, { createContext, useReducer } from "react";
export const NavContext = createContext();
const initialState = {
}
function reducer(state, action) {
return { ...state, ...action };
}
export const NavProvider = (props) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<NavContext.Provider value={{ state, dispatch }}>
{props.children}
</NavContext.Provider>
);
};
Then in your index.js file.
import { NavProvider} from "./NavContext";
ReactDOM.render(
<NavProvider>
<App />
</NavProvider>,
document.getElementById("root")
);
If that doesnt work idk what will.
Try not to pass any arguments into createContext, then pass navigation into Context provider as prop
<Context.Provider value={navigation} />
And then get the value using useContext Hook in your consumer component
For my knowledge, you have to import useContext like this.
import React, { useContext } from 'react'
In my react application I am trying to use context api. In my component I am importing the context but it is giving error that object can not destructure the property. I am trying to implement cart functionality in my app. I am using hooks.
ImgContext.js
import React, { createContext, useState } from 'react';
const ImgContext = createContext();
const ImgConProvider = ({children}) => {
const [myCart, setMyCart] = useState([]);
return(
<ImgContext.Provider value={{myCart, setMyCart}}>
{children}
</ImgContext.Provider>
)
}
export {ImgContext, ImgConProvider}
ImageGrid.js
import React, { useContext, useState } from 'react';
import ImageGrid from './ImageGrid';
import { ImgContext } from './Context/ImageContext';
const Home = () => {
const { myCart } = useContext(ImgContext);
return (
<div className="App">
{myCart}
</div>
)
}
export default Home;
You are not providing a a default value when creating the context. If there is a scenario where the component doenst have access to a provider the value from context would be undefined which maybe causing the issue.
Better provide a default value.
const ImgContext = createContext({});
I need to use dispatch Context API methods in _app.js.
The main limitation is that I use React hooks along with Context API, since _app.js is a Class, I can't use hooks within it.
My code:
// store.js
import React, { createContext, useContext, useReducer } from "react";
import mainReducer from "../store/reducers";
const AppStateContext = createContext();
const AppDispatchContext = createContext();
const initialState = {
filters: {
diet: {
selected: []
}
}
};
const useAppState = () => useContext(AppStateContext);
const useAppDispatch = () => useContext(AppDispatchContext);
const useApp = () => [useAppState(), useAppDispatch()];
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(mainReducer, initialState);
return (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
);
};
export { AppProvider, useAppState, useAppDispatch, useApp };
// _app.js
import App from "next/app";
import React from "react";
import { AppProvider } from "../store";
class MyApp extends App {
componentDidMount() {
/***********************************/
// HERE I WOULD LIKE TO USE DISPATCH
/***********************************/
}
render() {
const { Component, router, pageProps } = this.props;
return (
<AppProvider>
<Component {...pageProps} />
</AppProvider>
);
}
}
export default MyApp;
If you really want to use hooks, then just put a wrapper around _app.js like this:
import React from 'react'
import App from 'next/app'
function MyComponent({ children }) {
// You can use hooks here
return <>{children}</>
}
class MyApp extends App {
render() {
const { Component, pageProps } = this.props
return (
<MyComponent>
<Component {...pageProps} />
</MyComponent>
)
}
}
export default MyApp