Trying to use a function that is in my context provider file - reactjs

I am using Context-Api and am trying to use a function provided from my file in a lifecycle method. the function isnt wrapped in a consumer of course so i looked at the documentation and set value to context. this still isnt working.Everyting is working in my return of my class component but component did mount does not work.
import { ProductConsumer } from '../context';
export default class Details1 extends Component
componentDidMount() {
let value = this.context;
let id = this.props.match.params.id;
value.handleDetail(id);
}
render() {
{value => {
const {
id,...} = value.detailProduct;
return (
<ProductConsumer>
{value => {
My Component
</ProductConsumer>
export const Details = () => (
<Product.Consumer>
{context =>
<Details1 context={context}/>
}
</Product.Consumer>
)

You can either wrap the component with the consumer, passing it the function as a prop, or (better - ) convert your component to a functional component, using the useContext hook to get the values from your context.
import React, { useContext } from "react";
import someContext from "./context-path";
const MyComponent = () => {
const { myFunction } = useContext(someContext);
...
};

Related

How to set `contextType` on stateless component

I am using the React context new API as below code. The last line will register the context into my component. I wonder how I can use the contextType for stateless component?
class MyClass extends React.Component {
render() {
let value = this.context;
/* render something based on the value of MyContext */
}
}
MyClass.contextType = MyContext;
I tried below code but it doesn't seem to work. The context in the component is empty object.
const MyClass = (props, context) => {
...
}
MyClass.contextType = MyContext;
There is no way of doing it with contextType.
You should use contextConsumer with renderProps pattern or React's useContext hook (introduced in React 16.8)
The first one will look like this:
const MyClass = (props) => {
return (
<myContext.Consumer>
{({name}) => <View>{name}</View>}
</myContext.Consumer>
)
}
And the second (preferred) way of doing it looks like this:
import React, {useContext} from 'react';
const MyClass = (props) => {
const {name} = useContext(myContext)
return <View>{name}</View>
}
You must import useContext, then define a variable which fills output of useContext with the argument of your defined context.
import React, {useContext} from 'react';
const MyClass = (props) => {
const context = useContext(yourContext)
return <p>{context.name}</p>
}

Ho to simulate 'this.props' inside React Component when using Enzyme for tests?

So, my goal is to reach the func cashedImageCheker which must return the string , based on recived from this.props -> imageFetchedURL value. And my problem is that I cannot understand how to put this value into the corresponding func cashedImageCheker of my component Banners.
I tried to use Enzyme .setProps({}) method to transfer mock props inside my testing Component, but for now I just gets undefined. Here is my test and component codes below.
Thank you for any help...
TEST FILE:
import React from 'react'
import { mount, shallow } from 'enzyme'
import Banners from '../'
describe('<Banners />', () => {
it('imageFetchedURL must return true', () => {
const Component = shallow(<Banners />)
Component.setProps({
imageFetchedURL: 'https://google.com/la-la-la/1.jpg'
})
const { imageFetchedURL } = Component.instance().cashedImageCheker()
expect(imageFetchedURL.length).toBe(33)
})
})
COMPONENT FILE:
import PropTypes from 'prop-types'
import React, { Component } from 'react'
class Banners extends Component {
cashedImageCheker = () => {
const { imageFetchedURL } = this.props
const imageFetchCached = imageFetchedURL && imageFetchedURL.length > 1
return imageFetchCached
}
render() {
const isLocalImgCached = this.cashedImageCheker()
return (
<div className='bannerWrap'>
{isLocalImgCached}
</div>
)
}
}
export default Banners
When testing your component, you can pass the props required by it while shallow mounting the component like
describe('<Banners />', () => {
it('imageFetchedURL must return true', () => {
const Component = shallow(<Banners imageFetchedURL={'https://google.com/la-la-la/1.jpg'}/>)
const imageFetchedURL = Component.instance().cashedImageCheker()
expect(imageFetchedURL.length).toBe(33)
})
})
Also the major problem in code test case is that cashedImageChecker doesn't return you an object but a value and hence you need to write
const imageFetchedURL = Component.instance().cashedImageCheker()
instead of
const { imageFetchedURL } = Component.instance().cashedImageCheker()
Doing the above change will allow you to setProps and test the component method too.
describe('<Banners />', () => {
it('imageFetchedURL must return true', () => {
const Component = shallow(<Banners />)
Component.setProps({
imageFetchedURL: 'https://google.com/la-la-la/1.jpg'
})
const imageFetchedURL = Component.instance().cashedImageCheker()
expect(imageFetchedURL.length).toBe(33)
})
})
So, my goal is to reach the func cashedImageCheker which must return
the string , based on recived from this.props -> imageFetchedURL
value.
Based on this one way is this
import React from 'react';
import { shallow } from 'enzyme';
import Banners from '../'
describe('<Banners />', () => {
const url = 'https://google.com/la-la-la/1.jpg';
it('imageFetchedURL must return true', () => {
const Component = shallow(<Banners imageFetchedURL= url />)
const imageFetchedURL = Component.instance().cashedImageCheker();
expect(imageFetchedURL.length).toBe(33)
})
})
And my problem is that I cannot understand how to put this value into
the corresponding func cashedImageCheker of my component Banners.
For this query you must know a thing when something is connected to props then during testing you can pass it on as actual props as I have done it
<Banners imageFetchedURL= url />
I tried to use Enzyme .setProps({}) method to transfer mock props
inside my testing Component
This is absolutely correct way too. It is another way of doing so.
But you were getting undefined because you were destructuring the return value which had nothing to do in your case.

How to access the `context` for new react context API [duplicate]

I am developing a new app using the new React Context API instead of Redux, and before, with Redux, when I needed to get a list of users for example, I simply call in componentDidMount my action, but now with React Context, my actions live inside my Consumer which is inside my render function, which means that every time my render function is called, it will call my action to get my users list and that is not good because I will be doing a lot of unecessary requests.
So, how I can call only one time my action, like in componentDidMount instead of calling in render?
Just to exemplify, look at this code:
Let's suppose that I am wrapping all my Providers in one component, like this:
import React from 'react';
import UserProvider from './UserProvider';
import PostProvider from './PostProvider';
export default class Provider extends React.Component {
render(){
return(
<UserProvider>
<PostProvider>
{this.props.children}
</PostProvider>
</UserProvider>
)
}
}
Then I put this Provider component wrapping all my app, like this:
import React from 'react';
import Provider from './providers/Provider';
import { Router } from './Router';
export default class App extends React.Component {
render() {
const Component = Router();
return(
<Provider>
<Component />
</Provider>
)
}
}
Now, at my users view for example, it will be something like this:
import React from 'react';
import UserContext from '../contexts/UserContext';
export default class Users extends React.Component {
render(){
return(
<UserContext.Consumer>
{({getUsers, users}) => {
getUsers();
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}}
</UserContext.Consumer>
)
}
}
What I want is this:
import React from 'react';
import UserContext from '../contexts/UserContext';
export default class Users extends React.Component {
componentDidMount(){
this.props.getUsers();
}
render(){
return(
<UserContext.Consumer>
{({users}) => {
getUsers();
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}}
</UserContext.Consumer>
)
}
}
But ofcourse that the example above don't work because the getUsers don't live in my Users view props. What is the right way to do it if this is possible at all?
EDIT: With the introduction of react-hooks in v16.8.0, you can use context in functional components by making use of useContext hook
const Users = () => {
const contextValue = useContext(UserContext);
// rest logic here
}
EDIT: From version 16.6.0 onwards. You can make use of context in lifecycle method using this.context like
class Users extends React.Component {
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of UserContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of UserContext */
}
}
Users.contextType = UserContext; // This part is important to access context values
Prior to version 16.6.0, you could do it in the following manner
In order to use Context in your lifecyle method, you would write your component like
class Users extends React.Component {
componentDidMount(){
this.props.getUsers();
}
render(){
const { users } = this.props;
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}
}
export default props => ( <UserContext.Consumer>
{({users, getUsers}) => {
return <Users {...props} users={users} getUsers={getUsers} />
}}
</UserContext.Consumer>
)
Generally you would maintain one context in your App and it makes sense to package the above login in an HOC so as to reuse it. You can write it like
import UserContext from 'path/to/UserContext';
const withUserContext = Component => {
return props => {
return (
<UserContext.Consumer>
{({users, getUsers}) => {
return <Component {...props} users={users} getUsers={getUsers} />;
}}
</UserContext.Consumer>
);
};
};
and then you can use it like
export default withUserContext(User);
Ok, I found a way to do this with a limitation. With the with-context library I managed to insert all my consumer data into my component props.
But, to insert more than one consumer into the same component is complicated to do, you have to create mixed consumers with this library, which makes not elegant the code and non productive.
The link to this library: https://github.com/SunHuawei/with-context
EDIT: Actually you don't need to use the multi context api that with-context provide, in fact, you can use the simple api and make a decorator for each of your context and if you want to use more than one consumer in you component, just declare above your class as much decorators as you want!
For my part it was enough to add .bind(this) to the event. This is how my Component looks like.
// Stores File
class RootStore {
//...States, etc
}
const myRootContext = React.createContext(new RootStore())
export default myRootContext;
// In Component
class MyComp extends Component {
static contextType = myRootContext;
doSomething() {
console.log()
}
render() {
return <button onClick={this.doSomething.bind(this)}></button>
}
}
The following is working for me. This is a HOC that uses useContext and useReducer hooks. There's also a way to interact with sockets in this example.
I'm creating 2 contexts (one for dispatch and one for state). You would first need to wrap some outer component with the SampleProvider HOC. Then by using one or more of the utility functions, you can gain access to the state and/or the dispatch. The withSampleContext is nice because it passes both the dispatch and state. There are also other functions like useSampleState and useSampleDispatch that can be used within a functional component.
This approach allows you to code your React components as you always have without needing to inject any Context specific syntax.
import React, { useEffect, useReducer } from 'react';
import { Client } from '#stomp/stompjs';
import * as SockJS from 'sockjs-client';
const initialState = {
myList: [],
myObject: {}
};
export const SampleStateContext = React.createContext(initialState);
export const SampleDispatchContext = React.createContext(null);
const ACTION_TYPE = {
SET_MY_LIST: 'SET_MY_LIST',
SET_MY_OBJECT: 'SET_MY_OBJECT'
};
const sampleReducer = (state, action) => {
switch (action.type) {
case ACTION_TYPE.SET_MY_LIST:
return {
...state,
myList: action.myList
};
case ACTION_TYPE.SET_MY_OBJECT:
return {
...state,
myObject: action.myObject
};
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
};
/**
* Provider wrapper that also initializes reducer and socket communication
* #param children
* #constructor
*/
export const SampleProvider = ({ children }: any) => {
const [state, dispatch] = useReducer(sampleReducer, initialState);
useEffect(() => initializeSocket(dispatch), [initializeSocket]);
return (
<SampleStateContext.Provider value={state}>
<SampleDispatchContext.Provider value={dispatch}>{children}</SampleDispatchContext.Provider>
</SampleStateContext.Provider>
);
};
/**
* HOC function used to wrap component with both state and dispatch contexts
* #param Component
*/
export const withSampleContext = Component => {
return props => {
return (
<SampleDispatchContext.Consumer>
{dispatch => (
<SampleStateContext.Consumer>
{contexts => <Component {...props} {...contexts} dispatch={dispatch} />}
</SampleStateContext.Consumer>
)}
</SampleDispatchContext.Consumer>
);
};
};
/**
* Use this within a react functional component if you want state
*/
export const useSampleState = () => {
const context = React.useContext(SampleStateContext);
if (context === undefined) {
throw new Error('useSampleState must be used within a SampleProvider');
}
return context;
};
/**
* Use this within a react functional component if you want the dispatch
*/
export const useSampleDispatch = () => {
const context = React.useContext(SampleDispatchContext);
if (context === undefined) {
throw new Error('useSampleDispatch must be used within a SampleProvider');
}
return context;
};
/**
* Sample function that can be imported to set state via dispatch
* #param dispatch
* #param obj
*/
export const setMyObject = async (dispatch, obj) => {
dispatch({ type: ACTION_TYPE.SET_MY_OBJECT, myObject: obj });
};
/**
* Initialize socket and any subscribers
* #param dispatch
*/
const initializeSocket = dispatch => {
const client = new Client({
brokerURL: 'ws://path-to-socket:port',
debug: function (str) {
console.log(str);
},
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000
});
// Fallback code for http(s)
if (typeof WebSocket !== 'function') {
client.webSocketFactory = function () {
return new SockJS('https://path-to-socket:port');
};
}
const onMessage = msg => {
dispatch({ type: ACTION_TYPE.SET_MY_LIST, myList: JSON.parse(msg.body) });
};
client.onConnect = function (frame) {
client.subscribe('/topic/someTopic', onMessage);
};
client.onStompError = function (frame) {
console.log('Broker reported error: ' + frame.headers['message']);
console.log('Additional details: ' + frame.body);
};
client.activate();
};
You have to pass context in higher parent component to get access as a props in child.

Access React Context outside of render function

I am developing a new app using the new React Context API instead of Redux, and before, with Redux, when I needed to get a list of users for example, I simply call in componentDidMount my action, but now with React Context, my actions live inside my Consumer which is inside my render function, which means that every time my render function is called, it will call my action to get my users list and that is not good because I will be doing a lot of unecessary requests.
So, how I can call only one time my action, like in componentDidMount instead of calling in render?
Just to exemplify, look at this code:
Let's suppose that I am wrapping all my Providers in one component, like this:
import React from 'react';
import UserProvider from './UserProvider';
import PostProvider from './PostProvider';
export default class Provider extends React.Component {
render(){
return(
<UserProvider>
<PostProvider>
{this.props.children}
</PostProvider>
</UserProvider>
)
}
}
Then I put this Provider component wrapping all my app, like this:
import React from 'react';
import Provider from './providers/Provider';
import { Router } from './Router';
export default class App extends React.Component {
render() {
const Component = Router();
return(
<Provider>
<Component />
</Provider>
)
}
}
Now, at my users view for example, it will be something like this:
import React from 'react';
import UserContext from '../contexts/UserContext';
export default class Users extends React.Component {
render(){
return(
<UserContext.Consumer>
{({getUsers, users}) => {
getUsers();
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}}
</UserContext.Consumer>
)
}
}
What I want is this:
import React from 'react';
import UserContext from '../contexts/UserContext';
export default class Users extends React.Component {
componentDidMount(){
this.props.getUsers();
}
render(){
return(
<UserContext.Consumer>
{({users}) => {
getUsers();
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}}
</UserContext.Consumer>
)
}
}
But ofcourse that the example above don't work because the getUsers don't live in my Users view props. What is the right way to do it if this is possible at all?
EDIT: With the introduction of react-hooks in v16.8.0, you can use context in functional components by making use of useContext hook
const Users = () => {
const contextValue = useContext(UserContext);
// rest logic here
}
EDIT: From version 16.6.0 onwards. You can make use of context in lifecycle method using this.context like
class Users extends React.Component {
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of UserContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of UserContext */
}
}
Users.contextType = UserContext; // This part is important to access context values
Prior to version 16.6.0, you could do it in the following manner
In order to use Context in your lifecyle method, you would write your component like
class Users extends React.Component {
componentDidMount(){
this.props.getUsers();
}
render(){
const { users } = this.props;
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}
}
export default props => ( <UserContext.Consumer>
{({users, getUsers}) => {
return <Users {...props} users={users} getUsers={getUsers} />
}}
</UserContext.Consumer>
)
Generally you would maintain one context in your App and it makes sense to package the above login in an HOC so as to reuse it. You can write it like
import UserContext from 'path/to/UserContext';
const withUserContext = Component => {
return props => {
return (
<UserContext.Consumer>
{({users, getUsers}) => {
return <Component {...props} users={users} getUsers={getUsers} />;
}}
</UserContext.Consumer>
);
};
};
and then you can use it like
export default withUserContext(User);
Ok, I found a way to do this with a limitation. With the with-context library I managed to insert all my consumer data into my component props.
But, to insert more than one consumer into the same component is complicated to do, you have to create mixed consumers with this library, which makes not elegant the code and non productive.
The link to this library: https://github.com/SunHuawei/with-context
EDIT: Actually you don't need to use the multi context api that with-context provide, in fact, you can use the simple api and make a decorator for each of your context and if you want to use more than one consumer in you component, just declare above your class as much decorators as you want!
For my part it was enough to add .bind(this) to the event. This is how my Component looks like.
// Stores File
class RootStore {
//...States, etc
}
const myRootContext = React.createContext(new RootStore())
export default myRootContext;
// In Component
class MyComp extends Component {
static contextType = myRootContext;
doSomething() {
console.log()
}
render() {
return <button onClick={this.doSomething.bind(this)}></button>
}
}
The following is working for me. This is a HOC that uses useContext and useReducer hooks. There's also a way to interact with sockets in this example.
I'm creating 2 contexts (one for dispatch and one for state). You would first need to wrap some outer component with the SampleProvider HOC. Then by using one or more of the utility functions, you can gain access to the state and/or the dispatch. The withSampleContext is nice because it passes both the dispatch and state. There are also other functions like useSampleState and useSampleDispatch that can be used within a functional component.
This approach allows you to code your React components as you always have without needing to inject any Context specific syntax.
import React, { useEffect, useReducer } from 'react';
import { Client } from '#stomp/stompjs';
import * as SockJS from 'sockjs-client';
const initialState = {
myList: [],
myObject: {}
};
export const SampleStateContext = React.createContext(initialState);
export const SampleDispatchContext = React.createContext(null);
const ACTION_TYPE = {
SET_MY_LIST: 'SET_MY_LIST',
SET_MY_OBJECT: 'SET_MY_OBJECT'
};
const sampleReducer = (state, action) => {
switch (action.type) {
case ACTION_TYPE.SET_MY_LIST:
return {
...state,
myList: action.myList
};
case ACTION_TYPE.SET_MY_OBJECT:
return {
...state,
myObject: action.myObject
};
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
};
/**
* Provider wrapper that also initializes reducer and socket communication
* #param children
* #constructor
*/
export const SampleProvider = ({ children }: any) => {
const [state, dispatch] = useReducer(sampleReducer, initialState);
useEffect(() => initializeSocket(dispatch), [initializeSocket]);
return (
<SampleStateContext.Provider value={state}>
<SampleDispatchContext.Provider value={dispatch}>{children}</SampleDispatchContext.Provider>
</SampleStateContext.Provider>
);
};
/**
* HOC function used to wrap component with both state and dispatch contexts
* #param Component
*/
export const withSampleContext = Component => {
return props => {
return (
<SampleDispatchContext.Consumer>
{dispatch => (
<SampleStateContext.Consumer>
{contexts => <Component {...props} {...contexts} dispatch={dispatch} />}
</SampleStateContext.Consumer>
)}
</SampleDispatchContext.Consumer>
);
};
};
/**
* Use this within a react functional component if you want state
*/
export const useSampleState = () => {
const context = React.useContext(SampleStateContext);
if (context === undefined) {
throw new Error('useSampleState must be used within a SampleProvider');
}
return context;
};
/**
* Use this within a react functional component if you want the dispatch
*/
export const useSampleDispatch = () => {
const context = React.useContext(SampleDispatchContext);
if (context === undefined) {
throw new Error('useSampleDispatch must be used within a SampleProvider');
}
return context;
};
/**
* Sample function that can be imported to set state via dispatch
* #param dispatch
* #param obj
*/
export const setMyObject = async (dispatch, obj) => {
dispatch({ type: ACTION_TYPE.SET_MY_OBJECT, myObject: obj });
};
/**
* Initialize socket and any subscribers
* #param dispatch
*/
const initializeSocket = dispatch => {
const client = new Client({
brokerURL: 'ws://path-to-socket:port',
debug: function (str) {
console.log(str);
},
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000
});
// Fallback code for http(s)
if (typeof WebSocket !== 'function') {
client.webSocketFactory = function () {
return new SockJS('https://path-to-socket:port');
};
}
const onMessage = msg => {
dispatch({ type: ACTION_TYPE.SET_MY_LIST, myList: JSON.parse(msg.body) });
};
client.onConnect = function (frame) {
client.subscribe('/topic/someTopic', onMessage);
};
client.onStompError = function (frame) {
console.log('Broker reported error: ' + frame.headers['message']);
console.log('Additional details: ' + frame.body);
};
client.activate();
};
You have to pass context in higher parent component to get access as a props in child.

React Context API: Getting context outside of JSX (render) [duplicate]

I am developing a new app using the new React Context API instead of Redux, and before, with Redux, when I needed to get a list of users for example, I simply call in componentDidMount my action, but now with React Context, my actions live inside my Consumer which is inside my render function, which means that every time my render function is called, it will call my action to get my users list and that is not good because I will be doing a lot of unecessary requests.
So, how I can call only one time my action, like in componentDidMount instead of calling in render?
Just to exemplify, look at this code:
Let's suppose that I am wrapping all my Providers in one component, like this:
import React from 'react';
import UserProvider from './UserProvider';
import PostProvider from './PostProvider';
export default class Provider extends React.Component {
render(){
return(
<UserProvider>
<PostProvider>
{this.props.children}
</PostProvider>
</UserProvider>
)
}
}
Then I put this Provider component wrapping all my app, like this:
import React from 'react';
import Provider from './providers/Provider';
import { Router } from './Router';
export default class App extends React.Component {
render() {
const Component = Router();
return(
<Provider>
<Component />
</Provider>
)
}
}
Now, at my users view for example, it will be something like this:
import React from 'react';
import UserContext from '../contexts/UserContext';
export default class Users extends React.Component {
render(){
return(
<UserContext.Consumer>
{({getUsers, users}) => {
getUsers();
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}}
</UserContext.Consumer>
)
}
}
What I want is this:
import React from 'react';
import UserContext from '../contexts/UserContext';
export default class Users extends React.Component {
componentDidMount(){
this.props.getUsers();
}
render(){
return(
<UserContext.Consumer>
{({users}) => {
getUsers();
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}}
</UserContext.Consumer>
)
}
}
But ofcourse that the example above don't work because the getUsers don't live in my Users view props. What is the right way to do it if this is possible at all?
EDIT: With the introduction of react-hooks in v16.8.0, you can use context in functional components by making use of useContext hook
const Users = () => {
const contextValue = useContext(UserContext);
// rest logic here
}
EDIT: From version 16.6.0 onwards. You can make use of context in lifecycle method using this.context like
class Users extends React.Component {
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of UserContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of UserContext */
}
}
Users.contextType = UserContext; // This part is important to access context values
Prior to version 16.6.0, you could do it in the following manner
In order to use Context in your lifecyle method, you would write your component like
class Users extends React.Component {
componentDidMount(){
this.props.getUsers();
}
render(){
const { users } = this.props;
return(
<h1>Users</h1>
<ul>
{users.map(user) => (
<li>{user.name}</li>
)}
</ul>
)
}
}
export default props => ( <UserContext.Consumer>
{({users, getUsers}) => {
return <Users {...props} users={users} getUsers={getUsers} />
}}
</UserContext.Consumer>
)
Generally you would maintain one context in your App and it makes sense to package the above login in an HOC so as to reuse it. You can write it like
import UserContext from 'path/to/UserContext';
const withUserContext = Component => {
return props => {
return (
<UserContext.Consumer>
{({users, getUsers}) => {
return <Component {...props} users={users} getUsers={getUsers} />;
}}
</UserContext.Consumer>
);
};
};
and then you can use it like
export default withUserContext(User);
Ok, I found a way to do this with a limitation. With the with-context library I managed to insert all my consumer data into my component props.
But, to insert more than one consumer into the same component is complicated to do, you have to create mixed consumers with this library, which makes not elegant the code and non productive.
The link to this library: https://github.com/SunHuawei/with-context
EDIT: Actually you don't need to use the multi context api that with-context provide, in fact, you can use the simple api and make a decorator for each of your context and if you want to use more than one consumer in you component, just declare above your class as much decorators as you want!
For my part it was enough to add .bind(this) to the event. This is how my Component looks like.
// Stores File
class RootStore {
//...States, etc
}
const myRootContext = React.createContext(new RootStore())
export default myRootContext;
// In Component
class MyComp extends Component {
static contextType = myRootContext;
doSomething() {
console.log()
}
render() {
return <button onClick={this.doSomething.bind(this)}></button>
}
}
The following is working for me. This is a HOC that uses useContext and useReducer hooks. There's also a way to interact with sockets in this example.
I'm creating 2 contexts (one for dispatch and one for state). You would first need to wrap some outer component with the SampleProvider HOC. Then by using one or more of the utility functions, you can gain access to the state and/or the dispatch. The withSampleContext is nice because it passes both the dispatch and state. There are also other functions like useSampleState and useSampleDispatch that can be used within a functional component.
This approach allows you to code your React components as you always have without needing to inject any Context specific syntax.
import React, { useEffect, useReducer } from 'react';
import { Client } from '#stomp/stompjs';
import * as SockJS from 'sockjs-client';
const initialState = {
myList: [],
myObject: {}
};
export const SampleStateContext = React.createContext(initialState);
export const SampleDispatchContext = React.createContext(null);
const ACTION_TYPE = {
SET_MY_LIST: 'SET_MY_LIST',
SET_MY_OBJECT: 'SET_MY_OBJECT'
};
const sampleReducer = (state, action) => {
switch (action.type) {
case ACTION_TYPE.SET_MY_LIST:
return {
...state,
myList: action.myList
};
case ACTION_TYPE.SET_MY_OBJECT:
return {
...state,
myObject: action.myObject
};
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
};
/**
* Provider wrapper that also initializes reducer and socket communication
* #param children
* #constructor
*/
export const SampleProvider = ({ children }: any) => {
const [state, dispatch] = useReducer(sampleReducer, initialState);
useEffect(() => initializeSocket(dispatch), [initializeSocket]);
return (
<SampleStateContext.Provider value={state}>
<SampleDispatchContext.Provider value={dispatch}>{children}</SampleDispatchContext.Provider>
</SampleStateContext.Provider>
);
};
/**
* HOC function used to wrap component with both state and dispatch contexts
* #param Component
*/
export const withSampleContext = Component => {
return props => {
return (
<SampleDispatchContext.Consumer>
{dispatch => (
<SampleStateContext.Consumer>
{contexts => <Component {...props} {...contexts} dispatch={dispatch} />}
</SampleStateContext.Consumer>
)}
</SampleDispatchContext.Consumer>
);
};
};
/**
* Use this within a react functional component if you want state
*/
export const useSampleState = () => {
const context = React.useContext(SampleStateContext);
if (context === undefined) {
throw new Error('useSampleState must be used within a SampleProvider');
}
return context;
};
/**
* Use this within a react functional component if you want the dispatch
*/
export const useSampleDispatch = () => {
const context = React.useContext(SampleDispatchContext);
if (context === undefined) {
throw new Error('useSampleDispatch must be used within a SampleProvider');
}
return context;
};
/**
* Sample function that can be imported to set state via dispatch
* #param dispatch
* #param obj
*/
export const setMyObject = async (dispatch, obj) => {
dispatch({ type: ACTION_TYPE.SET_MY_OBJECT, myObject: obj });
};
/**
* Initialize socket and any subscribers
* #param dispatch
*/
const initializeSocket = dispatch => {
const client = new Client({
brokerURL: 'ws://path-to-socket:port',
debug: function (str) {
console.log(str);
},
reconnectDelay: 5000,
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000
});
// Fallback code for http(s)
if (typeof WebSocket !== 'function') {
client.webSocketFactory = function () {
return new SockJS('https://path-to-socket:port');
};
}
const onMessage = msg => {
dispatch({ type: ACTION_TYPE.SET_MY_LIST, myList: JSON.parse(msg.body) });
};
client.onConnect = function (frame) {
client.subscribe('/topic/someTopic', onMessage);
};
client.onStompError = function (frame) {
console.log('Broker reported error: ' + frame.headers['message']);
console.log('Additional details: ' + frame.body);
};
client.activate();
};
You have to pass context in higher parent component to get access as a props in child.

Resources