Consumer not reading provider value - reactjs

This is my Provider component
import React, {useState} from 'react';
import MyComponent from './MyComponent/MyComponent';
export default function App(props) {
const [header, setHeader] = useState(null);
function setHeaderText(header) {
setHeader(header);
}
const contextItems = {
setHeaderText: setHeaderText,
what: 'what'
};
return <React.Fragment>
<AppContext.Provider value={contextItems}>
<MyComponent request={props.request}/>
</AppContext.Provider>
</React.Fragment>
}
export const AppContext = React.createContext({
setHeaderText: () => {},
what: ''
});
I am using the consumer to try and pass the context value to MyComponent which is a functional component.
function MyComponent() {...}
export default (props) => {
return <AppContext.Consumer>
{ value => <MyComponent context={value} {...props} />}
</AppContext.Consumer>
}
My issue is that it only passes the default value of AppContext not the value that i am passing in.
Can anyone see anything wrong with my code at all?
I am using the latest version of react 16.8.2.
Thanks for any help.
Side note: I know i could just use composition in this case but the header value will be used throughout the app.

Related

React, render dinamic child passing prop

I'm slowly getting introduced into advanced React and best practices for production apps. I want to know what is considered the "best practice" way of rendering a dinamic child passing props regarding maintainability, readability etc:
Here is the codeSandBox for more details
First method, render children as function passing the prop:
import { useState } from "react"
import "./Mouses.css"
export default function MouseChildren ({children}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
/* this is bad because if more than one child is passed it will break */
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{children(mousePos)}
</div>
)
}
and then you call it this way:
<MouseChildren>
{position => <RandomDiv position={position} method="children as function method" />}
</MouseChildren>
Second method, render function as prop:
import { useState } from "react"
import "./Mouses.css"
export default function MouseRenderMethod ({render}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{render(mousePos)}
</div>
)
}
and you call it like this:
<MouseRenderMethod render={position => <RandomDiv position={position} method="render as prop method:" />} />
and the third method is using React.Children:
import React, { useState } from "react"
import "./Mouses.css"
export default function MouseCreateComponent({children}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{React.Children.map(React.Children.toArray(children), child => {
if (React.isValidElement(child)) return React.cloneElement(child, {position: mousePos}, null)
})}
</div>
)
}
and you call it like this:
<MouseCreateComponent>
<RandomDiv method="React.CloneElement method" />
</MouseCreateComponent>
I'm not sure which way is considered to be best over the others. If you from your experience can explain a bit
If you want to pass anything in your code without retyping it, you need to use useContext and the structure is:
One example with full-functionality which it may help you is:
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [userData, setUserData]=useState('nothing here')
// any code you want to pass in the code
//e.g. a function
const randomFunction = ()=>{
//do something here
}
return (
<AppContext.Provider
value={{
userData,
setUserData,
randomFunction
}}
>
{children}
</AppContext.Provider>
);
};
export const useGlobalContext = () => {
return useContext(AppContext);
};
export { AppContext, AppProvider };
then all you have to do is to wrap all the components (children) you want, e.g. wrap <App /> so, more or less everything:
<AppProvider>
<App />
</AppProvider>
So now in this case you can use everything from your AppContext in all your code, you can pass more variables and functions if you want, and you import that by using:
import { useGlobalContext } from '/pathYouHaveIt';
function App() {
const {
userData,
setUserData,
randomFunction,
} = useGlobalContext();
// now you can use those like you have them set-up in the App()

Even if I specify the type when passing the setState function as props, a type error seems to occur

https://codesandbox.io/s/beautiful-ishizaka-140bq4?file=/src/App.tsx
An error occurs when sending props to the TagInput component.
SetStateAction<Props[]> can't assign string[]
In TagInput.tsx nothing error. Please Check it..
You are over complicating it. You only need to pass the type of your props to your useState generic.
Instead of :
import { Dispatch, SetStateAction, useState } from "react";
import TagInput from "./TagInput";
type Props = {
tags: string[];
setTags: Dispatch<SetStateAction<string[]>>;
};
const App = () => {
const [tags, setTags] = useState<SetStateAction<Props[]>>([]);
return (
<>
<TagInput tags={tags} setTags={setTags} />
</>
);
};
export default App;
do this:
import { Dispatch, SetStateAction, useState } from "react";
import TagInput from "./TagInput";
type Props = {
tags: string[];
setTags: Dispatch<SetStateAction<string[]>>;
};
const App = () => {
const [tags, setTags] = useState<string[]>([]);
return (
<>
<TagInput tags={tags} setTags={setTags} />
</>
);
};
export default App;
The Props type is useless.
Sandbox

React Context update value from provider wrapping child

This may be a simple problem with React Context, but I cant find a way to do what I need.
I have a component, which consumes some context:
export const App = () => {
const value = useContext(MyContext);
return <ComponentA>{value}</ComponentA>;
};
The context is in a different module:
import { createContext } from 'react';
export const MyContext = createContext("Default value");
Now, ComponentA has a child component, which wraps the children from ComponentA:
const ComponentB = ({ children }) => {
return <div>{children}</div>;
};
export const ComponentA: React.FC = ({ children }) => {
return (
<MyContext.Provider value='Modified value'>
<ComponentB>{children}</ComponentB>
</MyContext.Provider>
);
};
I expect the text to be updated, and App to render "Updated value"; but instead it renders "Default value".
Does anyone know why this happen? Here are two sandboxes, the first with the example above—https://codesandbox.io/s/dnd-kit-sortable-forked-cm2vnv— and the second trying to update the context with useState: https://codesandbox.io/s/dnd-kit-sortable-forked-e6vrhp
Move the consumption of you context below the context provider.
In your example it should be in ComponentB
export const ComponentB = ({ children }) => {
const value = useContext(MyContext);
return (
<>
{value} — (should render «Updated value»)
{children}
</>
);
};

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.

GatsbyJs update provider value from onRouteUpdate

I'm using a provider for holding global location using gatsby's browser API for accessing location by consumer in other components. The problem is i can't change the global location from onRouteUpdate, Here is my code:
gatsby-browser.js:
import React, {useContext} from 'react';
import Provider,{ appContext } from './provider';
export const onRouteUpdate = ({ location, prevLocation }) => {
console.log('new pathname', location.pathname)
console.log('old pathname', prevLocation ? prevLocation.pathname : null)
// wanna set the new location for provider to use in other pages
// this code does not work
return(
<appContext.Consumer>
{context => {
context.changeLocation(location.pathname)
}})
</appContext.Consumer>
)
}
Provider.js:
import React, { useState } from 'react';
import { globalHistory as history } from '#reach/router'
export const appContext = React.createContext();
const Provider = props => {
const [location, setLocation] = useState(history.location);
return (
<appContext.Provider value={{
location,
changeLocation: (newLocation)=> {setLocation({location:newLocation}); console.log('changing')}
}}>
{props.children}
</appContext.Provider>
)
};
export default Provider;
Thanks.
onRouteUpdate isn’t expected to return React nodes, and so the React element you’re returning isn't going to be evaluated like you’d expect.
Since you’re only looking to store the current page, you don’t actually need to do anything manually onRouteUpdate because this functionality is available out-of-the-box with Gatsby.
// gatsby-browser.js
import React from "react"
import { appContext } from "src/provider"
export const wrapPageElement = ({ element, props }) => (
<AppContext.Provider value={{ location: props.location }}>
{React.createElement(element, props)}
</AppContext.Provider>
)

Resources