Somehow I would like to create a shared service, creating manually the provider and accessing the context works, but using serviceprovider in order to do the same and be more flexible when using multiple services doesn't seem to provide access to the inner context.
Here is my code:
app.js
import React from 'react';
import TodosContainer from './TodosContainer';
import { TodoService } from './services/TodoService';
import { createServiceContext } from './ServiceProvider';
function App() {
const { TodoServiceProvider } = createServiceContext('TodoService');
return (
<>
<TodoServiceProvider service={TodoService}>
<TodosContainer />
</TodoServiceProvider>
</>
);
}
export default App;`
here is the serviceprovider.js
import React from 'react';
export function createServiceContext(name) {
const ServiceContext = React.createContext();
function ServiceProvider({ service, children }) {
return (
<ServiceContext.Provider value={service}>
{children}
</ServiceContext.Provider>
);
}
return {
[`${name}Context`]: ServiceContext,
[`${name}Provider`]: ServiceProvider,
};
}
and here is the TodosContainer.js:
import { createServiceContext } from './ServiceProvider';
import { useContext } from 'react';
export default function TodosContainer() {
const { TodoServiceContext } = createServiceContext('TodoService');
const TodoSvc = useContext(TodoServiceContext);
result: TodoSvc is undefined ?
I've been debugging the problem since several hours, have no idea what might be wrong.
You are creating a new context every time instead of using the same one. You would have to cache them
import React from "react";
const cache = {};
export function createServiceContext(name) {
if (name in cache) {
return cache[name];
}
const ServiceContext = React.createContext();
function ServiceProvider({ service, children }) {
return (
<ServiceContext.Provider value={service}>
{children}
</ServiceContext.Provider>
);
}
const result = {
[`${name}Context`]: ServiceContext,
[`${name}Provider`]: ServiceProvider,
};
cache[name] = result;
return result;
}
Related
So not sure if this is exactly the problem, but wondering why this is happening when using contexts in the same file vs different files.
So here is the constant:
StartupContext.js
import React from "react";
export const StartupContext = React.createContext()
export const StartupProvider = ({ something, children }) => {
return (
<StartupContext.Provider value={{ something }}>
{children}
</StartupContext.Provider>
)
}
No problems there. But when I run this:
App.js
function Root() {
const { something } = useContext(StartupContext);
return (
<Text>Root {something}</Text>
)
}
export default function App() {
const [something, setSomething] = useState("ayyyy")
return (
<StartupProvider something={something}>
<Root />
</StartupProvider>
);
}
I'll get this error:
TypeError: undefined is not an object (evaluating 'Context._context')
BUT
If I split into two files
App.js
import { Root } from "./Root";
export default function App() {
const [something, setSomething] = useState("ayyyy")
return (
<StartupProvider something={something}>
<Root />
</StartupProvider>
);
}
Root.js
export default function Root() {
const { something } = useContext(StartupContext);
return (
<Text>Root {something}</Text>
)
}
It will work just fine. Why is this happening?
Oh man. It was something totally different.
I was importing on the single file:
import StartupContext from "./app/contexts/startupContext";
which was wrong, what I should have had was this:
import { StartupContext } from "./app/contexts/startupContext";
I want to reuse a context provider in different parts of my app using HOC ("higher order components"), but my state does not get updated.
This is the wrapper of the provider.
import React, { FC, useState } from "react";
import AdminContext from "./adminContext";
const AdminContextWrapper: FC = ({ children }) => {
const [authAdmin, setAuthAdmin] = useState<boolean | null>(null);
const value = { authAdmin, setAuthAdmin };
return (
<AdminContext.Provider value={value}>{children}</AdminContext.Provider>
);
};
export default AdminContextWrapper;
This is how I am implementing it :
import { useContext } from "react";
import AdminContext from "#comp/contexts/adminContext";
import AdminLogin from "#comp/admin/adminLogin";
import Limit from "#comp/admin/limits";
import AdminContextWrapper from "#comp/contexts/adminWrapper";
const Admin = () => {
const { authAdmin } = useContext(AdminContext);
const AdminPage = () => {
return (
<div>
<Limit />
</div>
);
};
return (
<AdminContextWrapper>
{authAdmin ? <AdminPage /> : <AdminLogin />}
</AdminContextWrapper>
);
Finally, this is my context:
import { createContext } from "react";
import { AdminContextType } from "#comp/utils/types";
const InitialUserContext: AdminContextType = {
authAdmin: false,
setAuthAdmin: (authAdmin: boolean | null) => {},
};
const AdminContext = createContext<AdminContextType>(InitialUserContext);
export default AdminContext;
I can see the state change in the login page but the admin page is not getting the update.
adminLogin.tsx
//...
const { setAuthAdmin, authAdmin } = useContext(AdminContext);
useEffect(() => {
console.log(authAdmin); // returns true after validating but the admin does not update.
}, [authAdmin]);
//...
I highly appreciate any help. Thank you.
Unless I'm misreading things, in
const Admin = () => {
const { authAdmin } = useContext(AdminContext);
// ...
return (
<AdminContextWrapper>
{authAdmin ? <AdminPage /> : <AdminLogin />}
</AdminContextWrapper>
);
}
you're trying to use the context outside its provider AdminContextWrapper - useContext would return undefined there unless you're already nested within another provider for the admin context, in which case the inner AdminContextWrapper there would give the inner components a different admin context.
You may want to make sure there's only ever exactly one admin context.
(As an aside, the // ... above used to be a nested component in your original code. Never do that – nested components' identity changes on each update, causing spurious re-renders.)
I'm trying to create a SessionProvider to avoid propdrilling and hold session globally for my components.
Im am not able to set state; menu.js L:11: setIsLoggedin is not a function
My custom hook (useSession.js):
import { useState } from 'react'
function useSession(isLoggedInState) {
const [isLoggedIn, setIsLoggedIn] = useState(isLoggedInState);
return { isLoggedIn, setIsLoggedIn, }
}
export default useSession;
My provider (SessionContext.js):
import React, { createContext, useState } from 'react'
import useSession from '../hooks/useSession'
const SessionContext = createContext();
function SessionProvider({ children, isLoggedin = false }) {
const { isLoggedIn, setIsLoggedIn } = useSession(isLoggedin);
return (
<SessionContext.Provider value={isLoggedIn, setIsLoggedIn}>{children}</SessionContext.Provider>
)
}
export { SessionProvider, SessionContext };
I've wrapped my application using in _app.js and then in Home (index.js) I try to use it there:
import { useContext } from "react";
import { SessionContext } from "../contexts/SessionContext";
export default function Menu(props) {
const { setIsLoggedIn } = useContext(SessionContext)
function onButtonClick(e) {
e.preventDefault();
setIsLoggedIn(true)
}
return (
<><span onClick={onButtonClick}>Login</span></>
)
}
Im able to read isLoggedIn, but not set it?
Your import into SessionContext should be
import useSession from '../hooks/useSession'
because you use
export default useSession;
More explaination here
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Statements/export
if you installed any linter into your ide, the errors should be underlined.
This is my first time using the React context hooks in an app and my context default value keeps appearing as "undefined."
Troubleshooting so far:
I've made sure React.createContext is in a separate file (context.js)
I've made sure the child component is wrapped within the Provider
I'm providing a default value to React.createContext()
All of my code can be found in this CodeSandbox link below:
https://codesandbox.io/s/react-context-troubleshooting-ojnb2?file=/src/child.js
In your App.js file, you're passing value a string:
import React, { useContext } from "react";
import { SelectedBackgroundContext } from "./context";
import Child from "./child";
function App() {
const { selectedBackground } = useContext(SelectedBackgroundContext);
// selectedBackground is just a string, so value = "https://...", which is invalid, the value, in your case, should be an object
return (
<SelectedBackgroundContext.Provider value={selectedBackground}>
<Child />
</SelectedBackgroundContext.Provider>
);
}
export default App;
Instead, value needs to be an object, with a selectedBackground property that contains the string:
import React, { useContext } from "react";
import { SelectedBackgroundContext } from "./context";
import Child from "./child";
function App() {
const { selectedBackground, selectBackground } = useContext(
SelectedBackgroundContext
);
// alternatively, just collect all context, without destructuring,
// and pass it to the "value" prop: value={context}
// const context = useContext(SelectedBackgroundContext);
// you're also missing the "selectBackground" function, which should be added to this "value" prop
return (
<SelectedBackgroundContext.Provider
value={{ selectedBackground, selectBackground }}
>
<Child />
</SelectedBackgroundContext.Provider>
);
}
export default App;
Since you've created context using an object:
{
selectedBackground:
"https://lp-cms-production.imgix.net/2019-06/81377873%20.jpg?fit=crop&q=40&sharp=10&vib=20&auto=format&ixlib=react-8.6.4",
selectBackground: () => {}
}
The value property of the provider should also be an object!
value={{ selectedBackground, selectBackground }}
Working demo:
I have changed your code to following and its working.
import React, { useContext } from "react";
import { SelectedBackgroundContext } from "./context";
export default function Child() {
const selectedBackground = useContext(SelectedBackgroundContext);
// you can comment out line5 above and uncomment line7 below to verify all other code works
//let selectedBackground = 'https://lp-cms-production.imgix.net/2019-06/81377873%20.jpg?fit=crop&q=40&sharp=10&vib=20&auto=format&ixlib=react-8.6.4'
const renderSelected = (context) => {
console.log("Background img src is: " + context); //appears as undefined
if (context) {
return (
<img
style={{ height: "200px" }}
src={context}
key={context + "Thumbnail"}
alt={"thumbnail of " + context}
/>
);
} else {
return <p>None</p>;
}
};
return (
<div>
<p>Background:}</p> {renderSelected(selectedBackground)}
</div>
);
}
because your are not passing object from context value that is why no need of
const {selectedBackground} = useContext(SelectedBackgroundContext);
more about deconstructing variable
https://www.javascripttutorial.net/es6/javascript-object-destructuring/
I've tried to read the accepted answer over here and in other posts, but can't quit figure out if this helps me or not. I feel like the case there is different.
I've looked at a few examples on how to use HOC and it seems just like I do. Is it because I am trying to use HOC to implement connect?
This is my HOC:
import React from "react";
import { connect } from "react-redux";
const withResults = WrappedComponent => {
return class extends React.Component {
render() {
return <WrappedComponent {...this.props} />;
}
}
};
const mapStateToProps = state => {
return {
results: state.results
};
};
export default connect(mapStateToProps)(withResults);
And this is my component I am trying to wrap with the HOC:
import React, { useContext } from "react";
import WithResults from "./withResults";
import SingleResult from "../singleResult/Primary";
import { PrimarySearchTermContext } from "../../providers/PrimarySearchTermProvider";
const PrimaryResults = props => {
const { currentSearchTerm } = useContext(PrimarySearchTermContext);
const compare = (a, b) => {
if (a.resultCount > b.resultCount) return 1;
if (b.resultCount > a.resultCount) return -1;
return 0;
};
const renderResults = () => {
//According to requirements, this search starts showing results after the third character
if (props.results[0] === undefined || currentSearchTerm.length <= 2)
return null;
const relevantResults = [];
props.results[0]
.sort(compare)
.reverse()
.map(result => {
if (result.term.toLowerCase().includes(currentSearchTerm.toLowerCase()))
relevantResults.push(result);
});
return relevantResults.slice(0, 4).map(result => {
if (currentSearchTerm === result.term) return null;
return (
<SingleResult
searchTerm={currentSearchTerm}
term={result.term}
resultCount={result.resultCount}
key={result.term}
/>
);
});
};
return <div className="results">{renderResults()}</div>;
};
export default WithResults(PrimaryResults);
The error I keep getting is for the last line of the export in the wrapped component.
try to export like this:
const withResultHoc = connect(mapStateToProps)(withResults);
export default withResultHoc;
Try this:
export default compose(withResult, connect(.....))(PrimaryResults)
So exporting like this ended up being the solution to me problem:
const composedWithResults = compose(connect(mapStateToProps, null), withResults);
export default composedWithResults;
As a beginner I'll be honest and say I don't quit know why, but it works. An explanation form someone who does would be nice.
Try this in your HOC:
export default (WrappedComponent) => connect(mapStateToProps)(withResults(WrappedComponent));