useContext doesn't work when in same file - reactjs

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";

Related

Why Context is not sharing the data in React?

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;
}

React.useContext appearing as undefined

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/

React native typescript: usecontext functions not firing from inside child component

I have an issue when i try to use functions from a context inside a child component in a React native android app.
Below is my code for the context, and the form component im using it in (stripped down for brevity).
The "isFormOpen" object can be read no problem from inside any children that is wrapped in the provider, but when i try to call the "toggleForm" function from the same child component, it does nothing, no console errors either.
I have another context which is identical in structure and syntax except for vairable and function names etc, and that works perfectly, so im a bit confused as to why this does not work. I removed the other context, thinking there might be some type of conflict, but didnt solve it.
AccountContext.tsx
import React, { FC, createContext, useContext, useState } from 'react';
interface AccountContextType {
isFormOpen: boolean,
toggleForm: (toggle: boolean) => void
};
export const AccountContext = createContext<AccountContextType>({
isFormOpen: false,
toggleForm: () => null
});
export const AccountContextProvider: FC = props => {
const [formOpen, setFormOpen] = useState<boolean>(false);
const toggleForm = (toggle: boolean) => {
setFormOpen(toggle);
}
const value: AccountContextType = {
isFormOpen: formOpen,
toggleForm
}
return (
<AccountContext.Provider value={value}>
{props.children}
</AccountContext.Provider>
)
}
export const useAccountContext = () => useContext(AccountContext);
TrackUploadForm.js
import React from 'react';
import { SafeAreaView } from 'react-native';
import { Button } from 'react-native-paper';
import { useAccountContext } from '../contexts/AccountContext';
import { AccountContextProvider } from '../contexts/AccountContext';
const TrackUploadForm = () => {
const accountContext = useAccountContext();
return (
<AccountContextProvider>
<SafeAreaView>
<Button onPress={() => accountContext.toggleForm(false)} mode='outlined'>Cancel</Button>
</SafeAreaView>
</AccountContextProvider>
)
};
export default TrackUploadForm;
useAccountContext is called outside the provider
export default function App() {
return (
<AccountContextProvider>
<Content />
</AccountContextProvider>
);
}
const Content = () => {
const accountContext = useAccountContext();
return (
<div className="App">
<h1>{accountContext.isFormOpen ? "true" : "false"}</h1>
<Button onPress={() => accountContext.toggleForm(false)} mode='outlined'>Cancel</Button>
</div>
);
};
accountContext.toggleForm(false) <-- always false, change it to accountContext.toggleForm(!accountContext.isFormOpen)
Together we have
https://codesandbox.io/s/cranky-panini-yo129

How to test code that uses a custom hook based on useContext with react-testing-library and jest

I've created a custom context hook - and I'm struggling to figure out how to pass values to its provider during testing.
My hook:
import React, { createContext, useContext, useState } from 'react';
const Context = createContext({});
export const ConfigurationProvider = ({ children }) => {
// Use State to keep the values
const [configuration, setConfiguration] = useState({});
// pass the value in provider and return
return (
<Context.Provider
value={{
configuration,
setConfiguration,
}}
>
{children}
</Context.Provider>
);
};
export const useConfigurationContext = () => useContext(Context);
export const { Consumer: ConfigurationConsumer } = Context;
This is how it's used in the application:
function App() {
return (
<ConfigurationProvider>
<div className="app">
<ComponentA />
</div>
</ConfigurationProvider>
);
}
And in ComponentA:
const ComponentA = () => {
// Get configuration
const configuration = useConfigurationContext();
return (
<div>{JSON.stringify(configuration)}</div>
)
}
This all works fine - considered that I'm calling setConfiguration from another component and set an object. Now for the testing part:
import React, { Component, createContext } from 'react';
import { render, waitFor } from '#testing-library/react';
import ComponentA from 'componentA';
const config = {
propertyA: 'hello',
};
test('renders the config', async () => {
const ConfigurationContext = createContext();
const { queryByText } = render(
<ConfigurationContext.Provider value={config}>
<ComponentA />
</ConfigurationContext.Provider>
);
expect(queryByText('hello')).toBeInTheDocument();
});
This doesn't work - I'm expecting the value that I'm sending in would be rendered in the div, but the context is an empty object. What am I doing wrong?
Thanks to Carle B. Navy I got the reason why it doesn't work. For other people two wonder what the solution is I fixed it by doing the following:
In my context hook, I changed the last line to export the provider as well:
export const { Consumer: ConfigConsumer, Provider: ConfigProvider } = Context;
Then in my test case, instead of creating a new context, I import the ConfigProvider at the top, and then:
const { queryByText } = render(
<ConfigProvider value={config}>
<ComponentA />
</ConfigProvider>
);
Thanks for helping me solve this and hope this helps someone else.

TypeError: Object(…) is not a function when trying to implement a HOC

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));

Resources