I'm playing with React Context API. I created a simple component:
import React, { createContext, useContext } from 'react';
const MyContext = createContext(1);
const MyComponent = () => (
<>
<p>{useContext(MyContext)}</p>
<MyContext.Provider value={2}>
<p>{useContext(MyContext)}</p>
</MyContext.Provider>
</>
);
export default MyComponent;
I'm getting two <p>1</p>. Why isn't the second context updated with 2? Am I using useContext() incorrectly?
You must use a separate Component to get Context to work.
I've filed a bug about this; see https://github.com/facebook/react/issues/18629
Simply split the code using the Context into a different Component.
const Inner = () => (
<p>{useContext(MyContext)}</p>
);
const MyComponent = () => (
<>
<p>{useContext(MyContext)}</p>
<MyContext.Provider value={2}>
<Inner />
</MyContext.Provider>
</>
);
That should fix it.
You'll need to render another component inside the context provider to get the value of 2. As useContext's documentation states:
Accepts a context object (the value returned from React.createContext) and returns the current context value for that context. The current context value is determined by the value prop of the nearest <MyContext.Provider> above the calling component in the tree.
Emphasis added. The important point is that it doesn't matter where you call useContext inside the component - what matters is where that component where it's called in is located in the tree.
import React, { createContext, useContext } from "react";
const MyContext = createContext(1);
const ChildComponent = () => (
<p>{useContext(MyContext)}</p>
)
const MyComponent = () => (
<>
<p>{useContext(MyContext)}</p>
<MyContext.Provider value={2}>
<ChildComponent/>
</MyContext.Provider>
</>
);
export default MyComponent;
Related
props are passing fine when we are passing them as a whole array of objects but it is not working when I am passing the props by traversing through the array using map function.
import { React, useEffect, useState } from "react";
import axios from "axios";
import "./Home.css";
import Cardimg from "./Cardimg";
const Home = props => {
return (
<>
<div className="header">PHOTO GALLERY</div>
<div className="photos">
{props.data?.map(e => {
<Cardimg data={e.ImgUrl}></Cardimg>;
})}
</div>
</>
);
};
export default Home;
in the above code props are passing when I am passing manually in Cardimg component...but as soon as I start using map then it doesn't work...like the props are not reaching the component.
below is my Cardimg component
import React from 'react'
const Cardimg = (props) => {
console.log(props.data);
return (
<div>{props.data}</div>
)
}
export default Cardimg
You need to return the Cardimg component inside map callback function.
Either like this
{
props.data?.map(e => {
return <Cardimg data={e.ImgUrl}></Cardimg>;
});
}
Or like this
{
props.data?.map(e => <Cardimg data={e.ImgUrl}></Cardimg>)
}
I am trying to pass a function on the sectionOne, then call sectionOne from another component but I am getting an error.
Error: Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
import React from 'react';
import {Home} from './home';
export const appTabBar = {
sectionOne: function sectionOne() {
return (
<>
<Home />
</>
);
},
};
import React from 'react';
import PropTypes from 'prop-types';
export const DrawerSection = props => {
const {sectionOne} = props;
return (
<>
<div>{sectionOne}</div>
</>
);
};
DrawerSection.propTypes = {
sectionOne: PropTypes.any,
};
You want to render sectionOne as a React component, and user-defined components must be capitalized. You can either rename sectionOne in the file where it is declared or assign it to a variable in the file where it is used:
export const DrawerSection = props => {
const {sectionOne: SectionOne} = props;
return (
<>
<div><SectionOne /></div>
</>
);
};
I am currently reworking my DataProvider, updating it from a class component to a functional component with React Hooks.
I believe my issue is in the way I am setting up my context consumer but I haven't found a good way to test this.
DataProvider.js
import React, { createContext } from "react";
const DataContext = createContext();
export const DataProvider = (props) => {
const [test, setTest] = React.useState("Hello");
return (
<DataContext.Provider value={test}>{props.children}</DataContext.Provider>
);
};
export const withContext = (Component) => {
return function DataContextComponent(props) {
return (
<DataContext.Consumer>
{(globalState) => <Component {...globalState} {...props} />}
</DataContext.Consumer>
);
};
};
So my withContext function should receive a component and pass it the props of the Context Provider.
I try to pull in my test state into a component.
import React from "react";
import style from "./DesktopAboutUs.module.css";
import { withContext } from "../../DataProvider";
const DesktopAboutUs = ({ test }) => {
return (
<div className={style.app}>
<div>{test}</div>
</div>
);
};
export default withContext(DesktopAboutUs);
No data is showing up for test. To me this indicates that my withContext function is not properly receiving props from the Provider.
Because you passed value={test}, globalState is a string, not an object with a test property.
Either of these solutions will result in what you expected:
Pass an object to the value prop of DataContext.Provider using value={{ test }} instead of value={test} if you intend globalState to contain multiple props.
Pass globalState to the test prop of Component using test={globalState} instead of {...globalState} if you do not intend globalState to contain multiple props.
const DataContext = React.createContext();
const DataProvider = (props) => {
const [test, setTest] = React.useState("Hello");
return (
<DataContext.Provider value={{ test }}>
{props.children}
</DataContext.Provider>
);
};
const withContext = (Component) => (props) => (
<DataContext.Consumer>
{(globalState) => <Component {...globalState} {...props} />}
</DataContext.Consumer>
);
const DesktopAboutUs = ({ test }) => (
<div>{test}</div>
);
const DesktopAboutUsWithContext = withContext(DesktopAboutUs);
ReactDOM.render(
<DataProvider>
<DesktopAboutUsWithContext />
</DataProvider>,
document.querySelector('main')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<main></main>
component which I'm exporting and want to test:
export default connectToStore(DefaultComponent);
connectToStore wrapper around component:
import React from 'react';
import AppContext from '../components/context/AppContext';
const connectToStore = Component => props => (
<AppContext.Consumer>
{({ state }) => (
<Component {...props} state={state} />
)}
</AppContext.Consumer>
);
export default connectToStore;
unit test calling component
it('should render view', () => {
const wrapper = render(<DefaultComponent />);
expect(wrapper.html()).toBeTruthy();
});
Error which I get:
Cannot destructure property state of 'undefined' or 'null'.
How do you test a component in general if it has a wrapper around it when being exported? How can I have the state injected hence being present in the wrapper.
Just to offer an alternative that's less complicated from a test perspective, you could have simply included a named export for the component itself as a test harness
export { DefaultComponent }
export default connectToStore(DefaultComponent)
That way your original test would still stand, you would just need to import as
import { DefaultComponent } from './defaultComponent'
And the of course when mounting mock the state prop provided by your context
const wrapper = render(<DefaultComponent state={{ ... }} />);
AppContext value property needs to be mocked:
Solution:
const wrapper = mount(
<AppContext.Provider
value={{
data,
callbackList: {}
}}
>
<DefaultComponent />
</AppContext.Provider>
);
I am trying to practice using hooks and i am not able to wrap my head around it.
I have a single component MessageBoard component that reads the data from state which just displays a simple list of messages.
I am passing down the dispatch and state via createContext so that the child components can consume it, which in-turn uses useContext in the child components to read the value.
When the page is refreshed, I expect to see the initial UI but it fails to render that the value in the context is undefined. I have already provided the initial state to the reducer when initializing it.
App.js
import React from "react";
import MessageBoard from "./MessageBoard";
import MessagesContext from "../context/MessagesContext";
function App() {
return (
<div>
<MessagesContext>
<h2>Reaction</h2>
<hr />
<MessageBoard />
</MessagesContext>
</div>
);
}
export default App;
MessageBoard.js
import React, { useContext } from "react";
import MessagesContext from "../context/MessagesContext";
function MessageBoard(props) {
const { state } = useContext(MessagesContext);
return (
<div>
{state.messages.map(message => {
return (
<div key={message.id}>
<h4>{new Date(message.timestamp).toLocaleDateString()}</h4>
<p>{message.text}</p>
<hr />
</div>
);
})}
</div>
);
}
export default MessageBoard;
MessagesContext.js
import React, { createContext, useReducer } from "react";
import reducer, { initialState } from "../state/reducer";
export default function MessagesContext(props) {
const Context = createContext(null);
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Context.Provider
value={{
dispatch,
state
}}
>
{props.children}
</Context.Provider>
);
}
Broken Example - https://codesandbox.io/s/black-dust-13kj2
Instead if I change the MessagesContext file a bit and instead the Provider is directly injected into the App, it works as expected. Wondering what I have misunderstood here and what might be going on ?
MessagesContext.js
import { createContext } from "react";
export default createContext(null);
App.js
import React, { useReducer } from "react";
import reducer, { initialState } from "../state/reducer";
import PublishMessage from "./PublishMessage";
import MessageBoard from "./MessageBoard";
import MessagesContext from "../context/MessagesContext";
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<MessagesContext.Provider
value={{
dispatch,
state
}}
>
<h2>Reaction</h2>
<hr />
<PublishMessage />
<hr />
<MessageBoard />
</MessagesContext.Provider>
</div>
);
}
export default App;
Working Example - https://codesandbox.io/s/mystifying-meitner-vzhok
useContext accepts a context object (the value returned from React.createContext) and returns the current context value for that context.
const MyContext = createContext(null);
const value = useContext(MyContext);
// MessagesContext Not a contex object.
const { state } = useContext(MessagesContext);
In the first example:
// export the context.
export const Context = createContext(null);
export default function MessagesContext(props) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Context.Provider
value={{
dispatch,
state
}}
>
{props.children}
</Context.Provider>
);
}
and then use it:
import { Context } from '../context/MessagesContext';
function MessageBoard() {
const { state } = useContext(Context);
...
}
Working broken example: