useContext with no context - reactjs

I have defined a context using createContext that wraps some of my components like
import MyContext from './mycontext.js'
import A from 'a.js';
import B from b.js';
<MyContext>
<A/>
<B/>
</MyContext>
where A is defined something like
import C from './c.js'
const A = () => {
return (<C/>);
}
And C is define something like
import MyContext from 'mycontext.js';
const C = () => {
const { value, setValue } = useContext(MyContext);
return (<div>`This is the value - ${!!value ? value : 'UNK'}`</div>)
}
Finally the context is created like
const MyContext = createContext({value: '', setValue: () => {}});
The problem is that I get a runtime error
TypeError: Object is not iterable (cannot read property Symbol(Symbol.iterator))
From the component C.
I want to make provision for calling C outside of the provider. Not as a wrapped child of the provider. Where does this error come from and how do I work around it?

From only what I can tell from the code snippets it appears you're not rendering any context provider and not providing a context value.
Instead of trying to use the MyContext context as a React component, render the Provider component.
<MyContext.Provider value={{ value: 42, setValue: console.log }}>
<A />
<B />
</MyContext.Provider>

Related

Cannot destructure ref property as it is undefined trying to use a multiple ref context

I want to make a ref context, using that context's refs in my sections and then consuming the context in my navbar to use intersectionObserver.
Trying to achieve this at first i created a context with every ref and then using the context in my about component.
The context looks like this:
import React, { createContext, useRef } from "react";
export const RefsContext = createContext();
export const RefsProvider = (props) => {
const aboutRef = useRef();
const shopRef = useRef();
const customersRef = useRef();
return (
<RefsContext.Provider
value={{
aboutRef,
shopRef,
customersRef,
}}
>
{props.children}
</RefsContext.Provider>
);
};
export default RefsProvider;
And this is how i consume that context in my about.js:
First i import the context: import { RefsContext } from "../../../context/refsContext"; then i use it: const { aboutRef } = useContext(RefsContext); and here i use that ref to referenciate a section: <section className="about" id="about" ref={aboutRef}>
I get this error message Uncaught TypeError: Cannot destructure property 'aboutRef' of '(0 , react__WEBPACK_IMPORTED_MODULE_0__.useContext)(...)' as it is undefined. in about.js:10, this line: const { aboutRef } = useContext(RefsContext);
Looks like you forgot to wrap whatever component is using this in RefsProvider. Does that fix this problem?
EDIT: To provide further context, if you use useContext inside a component that is not wrapped in the Provider for the context you are accessing, the value will be undefined. I suspect that is what is happening here.
In order to fix this, you need to wrap the component in RefsProvider
For example:
<RefsProvider>
<YourComponentThatUsesRefsContext />
</RefsProvider>

Using object in ReactJS Context - method in object causes Property 'myMethod' does not exist on type 'unknown'

I am trying to use a context for the first time where I have a Reactjs library which creates the context, and then in the main app updates the context and passes as a context provider within app.js.
In my library I have the following:
import * as React from "react";
export const MyContext = React.createContext(null);
export const MyContextProvider = MyContext.Provider;
export const MyContextConsumer = MyContext.Consumer;
export class MyClass
{
constructor()
{
....
}
...
myMethod(param1, param2 = null)
{
...
}
Then in my main project inside app.tsx I have the following:
const my_class = new MyClass();
my_class.initialise("param1", "param2", "param3");
<MyContextProvider value={my_class}>
<ErrorBoundary>
<BrowserRouter>
<Routes>
<Route index element={<Home />} />
<Route path='/blog' element={<BlogHome />} />
<Route path='/blog/:slug' element={<BlogPost />} />
<Route path='*' element={<NotFound />} />
</Routes>
</BrowserRouter>
</ErrorBoundary>
</MyContextProvider>
Then in my child component in this case the NotFound component I then do the following:
const my_class = useContext(MyContext);
console.log("My class context", my_class);
my_class.myMethod(err, "Low");
This fails to compile as method myMethod throws an error Property 'myMethod' does not exist on type 'unknown'
When I remove the call to myMethod and just print my_class the object has everything that's been passed into the constructor, and I can see the method in there although it's within a [[Prototype]] object but not sure if that's expected or whether I should reference that in some way to access the method call.
Hard to say without an mcve, but my guess is that you're not using your MyContext properly. That said, typically, context will be an object with properties and methods:
value: {
firstName: "Bob", // value from state
lastName: "Smith", // value from state
changeFirstName: () => {}, // some sort of state updater
changeLastName: () => {}, // some sort of state updater
otherCallback: () => {}, // some sort of callback that affects the UI
...etc
}
The above adheres to a more standard approach as values are coming from state (either from a shared state object or separated useState values) and some methods containing state setters to update these values accordingly. Since you're using the useContext hook, a functional programming (FP) approach would make more sense; however, it appears that you're trying to mix OOP (a plain class) with FP. Unless there's an absolute requirement to use a class, I'd recommend the flat object approach above.
That said, here's how you can use a plain class within context...
Working Demo:
Ideally, you'll want to initialize the context with class fields from where you're creating context:
import { createContext, useContext } from "react";
import User, { IUser } from "./User";
const user = new User("Bob", "Smith");
export const UserContext = createContext<IUser>(user);
export const useUserContext = () => useContext(UserContext);
Then, import UserContext context and the useUserContext hook and use them at the top level of your app:
import { UserContext, useUserContext } from "./UserContext";
const App = (): ReactElement => (
<UserContext.Provider value={useUserContext()}>
{...}
</UserContext.Provider>
);
export default App;
In a child component, you can import the useUserContext hook and use its return value's fields/properties accordingly:
import { useUserContext } from "./UserContext";
import { ReactElement } from "react";
const Main = (): ReactElement => {
const user = useUserContext();
return (
<main className="main">
<h1>User Context</h1>
<h2 className="user">
<strong>First Name:</strong> {user.firstName}
</h2>
<h2 className="user">
<strong>Last Name:</strong> {user.lastName}
</h2>
<h2 className="user">
<strong>Combined Name:</strong> {user.combinedName()}
</h2>
</main>
);
};
export default Main;
BUT THERE'S A PROBLEM WITH THIS APPROACH!
The problem you're going to run into is if you plan on manipulating/updating the fields within the class... React won't handle mutable field changes. So if you change a class field, that change won't re-render the app (as shown in the demo above).
Instead, you'll need to utilize state (either from a React Component class or a hook) and some sort of callback that manipulates the state (a callback that invokes this.setState({...}) or a callback that invokes a useState state setter). Therefore, using a plain class within context really doesn't make much sense because it can only be statically stored/utilized.
Here's an example of how you can utilize state within context:
I figured the issue was caused by a typescript issue within my main project.
Where I had const my_class = useContext(MyContext) I had to set the type of my_class by doing const my_class : MyContext = useContext(MyContext) resolves the issue

Unable to pass react props.children

I am using react.
When I pass props.children to the Header component, I get the following error.
I think the type is correct because I am passing React.
I don't know why the error occurs.
error
react-dom.development.js?61bb:13231 Uncaught Error: Objects are not valid as a React child (found: object with keys {children}). If you meant to render a collection of children, use an array instead.
import React from 'react';
import styled from 'styled-components';
export const Header: React.FC = (children) => {
return <Head>{children}</Head>;
};
const Head = styled.div`
background: #8b8b8b;
`;
const Home = () => {
return (
<div>
<Header>
<span>home</span>
</Header>
<div>
);
};
export default Home;
You just named your props as children, try:
export const Header: React.FC = ({children}) => {
return <Head>{children}</Head>;
};
Let's just take a step back from destructuring for a second because it confuses a lot of JS beginners.
By default, your component receives an argument which is a bunch of properties, so we usually call it props. We can get particular properties from this props object like so:
export const Header = (props) => {
console.log(props)
return <Head>{props.children}</Head>;
};
Then once you get your head around destructuring, you'll notice we can do this in a faster, cleaner manner to get properties from our props object.
export const Header = ({ children }) => {
console.log(children)
return <Head>{children}</Head>;
};
The trade-off is we can't do props.something now, we'll need to add something to the restructure... but our code looks cleaner.

Change function components method using forwardRef

I am trying to do:
Change child state as in this tutorial: Change child state from parent
Tutorial uses class component, I've tried to use function components.It gives an error like "Function components don't have instances so they can't have refs"
So I used forwardRef (for the first time) according to a stackoverflow answer here
Code:
import React,{useRef,forwardRef, useEffect} from "react";
const Component = (props) => {
function sayHello(){
console.log("hello")
}
return <h1>{props.children}</h1>
}
const ForwardComponent = forwardRef((props,ref)=> (<Component ref={ref}> {props.children} </Component>))
export default function App() {
const hello = useRef()
useEffect(()=>{
hello.current.sayHello()
})
return (
<div>
<ForwardComponent ref={hello}>Hello</ForwardComponent>
</div>
);
}
note: I could use context api or redux here, but I believe it should be simpler in my use case. Also I get to learn something new

Testing react components that have JSS injected styles with enzyme

I'm having a react component. Let's say Todo
import React, { Component } from 'react';
import injectSheet from 'react-jss';
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export default injectSheet(Todo);
I want to test it with enzyme. And there are two problems with it.
1. Access to the state
(and other component specific features)
When I shallow or mount that composer in the suite I can't get access to its state of course because it's not my component anymore but something new around it.
E.g. this code will give me an error:
it('should have state updated on handleAddTodo', () => {
const todo = shallow(<Todo />);
const length = todo.state('todos').length;
});
It says of course TypeError: Cannot read property 'length' of undefined because the state is not what I expect but this: { theme: {}, dynamicSheet: undefined }
This won't also give me access to props, refs etc.
2. Problems with theme provider
To provide some default colouring to the project like this:
import React, { Component } from 'react';
import Colors from './whatever/Colors';
class App extends Component {
render() {
return (
<ThemeProvider theme={Colors}>
<WhateverInside />
</ThemeProvider>
);
}
}
And of course when running tests it gives me an error [undefined] Please use ThemeProvider to be able to use WithTheme.
So my question is the following. Is there a way to solve this problem in “one single place”. How can I make enzyme agnostic of what is my component wrapped with?
If not, then how do I solve the problem if passing the ThemeProvider features down to the component that I'm testing?
And how can I access the state, ref, props and other things of the wrapped component?
Thank you!
here's what I'd do to test the component,
import React, { Component } from 'react';
import injectSheet from 'react-jss';
const styles = {};
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export { styles, Todo as TodoCmp }
export default injectSheet(styles)(Todo);
and in the test file, I'd add the following:
import { theme } from 'your-theme-source';
const mockReducer = (prev, curr) => Object.assign({}, prev, { [curr]: curr });
const coerceStyles = styles =>
typeof styles === 'function' ? styles(theme) : styles;
const mockClasses = styles =>
Object.keys(coerceStyles(styles)).reduce(mockReducer, {});
import {TodoCmp, styles} from 'your-js-file';
// then test as you'd normally.
it('should blah blah', () => {
const classes = mockClasses(styles);
const todo = shallow(<Todo classes={classes} />);
const length = todo.state('todos').length;
})
Please read more about it in the nordnet-ui-kit library specifically in the test directory. Here's a quick example
It is not related to JSS specifically. Any HOC wraps your component. Ideally you don't test any internals of a component directly.
Components public api is props, use them to render your component with a specific state and verify the rendered output with shallow renderer.
For some edge cases if first and preferred way is impossible, you can access the inner component directly and access whatever you need directly. You will have to mock the props the HOC would pass otherwise for you.
const StyledComponent = injectSheet(styles)(InnerComponent)
console.log(StyledComponent.InnerComponent)
If your component relies on theming, you have to provide a theme provider, always.

Resources