Redux using functional component without React - reactjs

I have a functional componet without React but that uses Redux like following:
export const isAuthenticated = () => ({user}) => {
console.log("user : ", user);
return true;
};
const mapStateToProps = (state) => {
return {
user: state.auth.userInfo
}
}
export default connect(mapStateToProps)(isAuthenticated as any)
And to use above function, I uses like:
{isAuthenticated() && (
<li className="nav-item">
<NavLink
className="nav-link"
activeStyle={{
color: "#1ebba3"
}}
to="/dashboard"
onClick={(e) => { if (this.menu.classList.contains("show")) { this.inputElement.click() } }}
>
Dashboard
</NavLink>
</li>
)}
It doesn't work. It just doesn't even get into that isAuthenticated function since I don't see any output for console.log("user : ", user);. It should output something like user: undefined, but it doesn't even output that.
If I change
export const isAuthenticated = () => ({user}) => {
to
export const isAuthenticated = ({user}) => {
then the problem is that I can't call it with isAuthenticated() and might be duplication between passed param from function call and retrived state from Redux.
How can I fix it if I want to keep using "isAuthenticated()" for calling that method, without passing any param, but let Redux pass user state to that function?

This can be solved with React's Hooks API. What you are aiming for is a custom hook that will internally use useSelector from react-redux. If you don't want to use functional components, you can always opt for Higher-Order Components (HOCs)
Code Samples
Custom Hook
import { useSelector } from 'react-redux';
export function useIsAuthenticated() {
return useSelector(state => !!state.auth.userInfo);
}
export function YourComponent(props) {
const isAuthenticated = useIsAuthenticate();
// Return your react sub-tree here based on `isAuthenticated`
// instead of `isAuthenticated()` like before.
}
Higher-Order Components
import { connect } from 'react-redux';
export function withIsAuthenticated(Component) {
function mapStateToProps(state) {
return {
isAuthenticated: !!state.auth.userInfo
};
}
return connect(mapStateToProps)(function({ isAuthenticated, ...props }) {
return <Component isAuthenticated={isAuthenticated} {...props}/>;
});
}
export function YourComponent({ isAuthenticated, ...props }) {
// Return your react sub-tree here based on `isAuthenticated`
// instead of `isAuthenticated()` like before.
}
Opinion
Personally, I feel that HOCs introduce a lot more complexity than necessary, and if I'm not mistaken, this was one of the primary drivers behind the creation of the Hooks API.

Related

NEXTJS ReactJS passing props from page to all its sub-components

It's possible to pass all the props of the page to all its children components implicitly (without passing as parameter)?
I need something like this:
export default function Home({ user }) {
if (!user) {
return (
<>
<component1/>
<component2/>
<component3/>
</>
);
} else {
return (
<></>
);
}
}
export async function getServerSideProps(context) {
const { Auth } = withSSRContext(context);
try {
const user = await Auth.currentAuthenticatedUser();
return {
props: {
user: JSON.parse(JSON.stringify(user)),
}
}
}
I need that user is available to component1, component2, component3 and all its sub-components without passing explicitly.
I've read that you can use context for this. But I'm using amplify aws and I don't know if it's possible ...
First your components should be capitalize.
You can use context to pass the values without the use of the props.
For that you need to setup context using createContext
import { createContext } from "react";
const UserContext = createContext()
export default function Home({ user }) {
if (user) {
return (
<UserContext.Provider value={user}>
<Component1/>
<Component2/>
<Component3/>
</UserContext.Provider>
);
} else {
return (
<></>
);
}}
And then inside the Component1 or 2 or 3 ... You can use useContext to get the value.
import { useContext } from "react"
function Component1() {
const user = useContext(UserContext);
return (
<>
<h1>Component 1</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}
You can use react context, redux, recoil. Recoil is easy to use.
Context provides a way to pass data through the component tree without having to pass props down manually at every level, It's react concept, AWS Amplify will not restrict from using this. Feel free to use it here.

React Native - reduce render times to optimize performance while using React hooks

Background
After releasing React v16.8, now we have hooks to use in React Native.
I am doing some simple tests to see the render times and the performance between
Hooked functional components and class components. Here is my sample:
#Components/Button.js
import React, { memo } from 'react';
import { TouchableOpacity, Text } from 'react-native';
const Button = memo(({ title, onPress }) => {
console.log("Button render"); // check render times
return (
<TouchableOpacity onPress={onPress} disabled={disabled}>
<Text>{title}</Text>
</TouchableOpacity>
);
});
export default Button;
#Contexts/User.js
import React, { createContext, useState } from 'react';
import User from '#Models/User';
export const UserContext = createContext({});
export const UserContextProvider = ({ children }) => {
let [ user, setUser ] = useState(null);
const login = (loginUser) => {
if (loginUser instanceof User) { setUser(loginUser); }
};
const logout = () => {
setUser(null);
};
return (
<UserContext.Provider value={{value: user, login: login, logout: logout}}>
{children}
</UserContext.Provider>
);
};
export function withUserContext(Component) {
return function UserContextComponent(props) {
return (
<UserContext.Consumer>
{(contexts) => <Component {...props} {...contexts} />}
</UserContext.Consumer>
);
}
}
Cases
We have two cases below for constructing screen components:
#Screens/Login.js
Case 1: Functional Component with Hooks
import React, { memo, useContext, useState } from 'react';
import { View, Text } from 'react-native';
import Button from '#Components/Button';
import { UserContext } from '#Contexts/User';
const LoginScreen = memo(({ navigation }) => {
const appUser = useContext(UserContext);
const [foo, setFoo] = useState(false);
const userLogin = async () => {
let response = await fetch('blahblahblah');
if (response.is_success) {
appUser.login(user);
} else {
// fail on login, error handling
}
};
const toggleFoo = () => {
setFoo(!foo);
console.log("current foo", foo);
};
console.log("render Login Screen"); // check render times
return (
<View>
<Text>Login Screen</Text>
<Button onPress={userLogin} title="Login" />
<Button onPress={toggleFoo} title="Toggle Foo" />
</View>
);
});
export default LoginScreen;
Case 2: Component wrapped with HOC
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import Button from '#Components/Button';
import { withUserContext } from '#Contexts/User';
import UserService from '#Services/User';
class LoginScreen extends Component {
state = { foo: false };
userLogin = async () => {
let response = await UserService.login();
if (response.is_success) {
login(user); // function from UserContext
} else {
// fail on login, error handling
}
};
toggleFoo = () => {
const { foo } = this.state;
this.setState({ foo: !foo });
console.log("current foo", foo);
};
render() {
console.log("render Login Screen"); // check render times
return (
<View>
<Text>Login Screen</Text>
<Button onPress={userLogin} title="Login" />
<Button onPress={toggleDisable} title="Toggle" />
</View>
);
}
}
Results
Both cases have identical render times at the beginning:
render Login Screen
Button render
Button render
But while I press the "Toggle" button, the state changed and here is the result:
Case 1: Functional Component with Hooks
render Login Screen
Button render
Button render
Case 2: Component wrapped with HOC
render Login Screen
Questions
Although the Button Component isn't a large bunch of codes, considering the re-render times between two cases, Case 2 should have a better performance than Case 1.
However, considering the code readability, I definitely love using hooks more than using HOC. (Especially the function: appUser.login() and login())
So here's the question. Is there any solution that I can keep the benefits of both size, decreasing the re-render times while using the hooks? Thank you.
The reason that both button re-render even though you use memo in case of a functional component is because the function references are changed on each re-render as they are defined within the functional component.
Similar case will happen if you use arrow functions in render for a class component
In case of a class the function references don't change with the way you define them as functions are defined outside of your render method
To optimize on rerenders, you should make use of useCallback hook to memoize your function references
const LoginScreen = memo(({ navigation }) => {
const appUser = useContext(UserContext);
const [foo, setFoo] = useState(false);
const userLogin = useCallback(async () => {
let response = await fetch('blahblahblah');
if (response.is_success) {
appUser.login(user);
} else {
// fail on login, error handling
}
}, []); // Add dependency if need i.e when using value from closure
const toggleFoo = useCallback(() => {
setFoo(prevFoo => !prevFoo); // use functional state here
}, []);
console.log("render Login Screen"); // check render times
return (
<View>
<Text>Login Screen</Text>
<Button onPress={userLogin} title="Login" />
<Button onPress={toggleFoo} title="Toggle Foo" />
</View>
);
});
export default LoginScreen;
Also note that React.memo cannot prevent re-renders due to context value changes. Also note that while passing value to context provider you should make use of useMemo too
export const UserContextProvider = ({ children }) => {
let [ user, setUser ] = useState(null);
const login = useCallback((loginUser) => {
if (loginUser instanceof User) { setUser(loginUser); }
}, []);
const logout = useCallback(() => {
setUser(null);
}, []);
const value = useMemo(() => ({
value: user,
login: login,
logout: logout,
}), [user, login, logout]);
/*
Note that login and logout functions are implemented using `useCallback` and
are created on initial render only and hence adding them as dependency here
doesn't make a difference and will definitely not lead to new referecne for
value. Only `user` value change will create a new object reference
*/
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
};
The reason is in functional component whenever the component re-render, new userLogin created => Button component is re-render.
const userLogin = async () => {
const response = await fetch("blahblahblah")
if (response.is_success) {
appUser.login(user)
} else {
// fail on login, error handling
}
}
You can use useCallback to memoize userLogin function + wrap Button component with React.memo (as what you did) prevent unwanted re-render:
const userLogin = useCallback(async () => {
const response = await fetch("blahblahblah")
if (response.is_success) {
appUser.login(user)
} else {
// fail on login, error handling
}
}, [])
The reason why it not happen in class component is when class component is re-rendered only render function is trigger (of course some other lifecycle function such as shoudlComponentUpdate, componentDidUpdate trigger too). ==> userLogin not change ==> Button component is not re-render.
This is great article to have a look about useCallback + memo
Notice: When you use Context, memo can not prevent the component, which is a Consumer, re-render if values of Context Provider changed.
For example:
If you call setUser in UserContext => UserContext re-render => value={{value: user, login: login, logout: logout}} change => LoginScreen re-render. You cannot use shouldComponentUpdate (class compoenent) or memo (functional component) to prevent re-render, because it's not update via props, it's updated via value of Context Provide

How would I use React Hooks to replace my withAuth() HOC?

I've been spending a bunch of time reading up on React Hooks, and while the functionality seems more intuitive, readable, and concise than using classes with local state and lifecycle methods, I keep reading references to Hooks being a replacement for HOCs.
The primary HOC I have used in React apps is withAuth -- basically a function that checks to see if the currentUser (stored in Redux state) is authenticated, and if so, to render the wrapped component.
Here is an implementation of this:
import React, { Component } from "react";
import { connect } from "react-redux";
export default function withAuth(ComponentToBeRendered) {
class Authenticate extends Component {
componentWillMount() {
if (this.props.isAuthenticated === false) {
this.props.history.push("/signin");
}
}
componentWillUpdate(nextProps) {
if (nextProps.isAuthenticated === false) {
this.props.history.push("/signin");
}
}
render() {
return <ComponentToBeRendered {...this.props} />;
}
}
function mapStateToProps(state) {
return { isAuthenticated: state.currentUser.isAuthenticated };
}
return connect(mapStateToProps)(Authenticate);
}
What I can't see is how I can replace this HOC with hooks, especially since hooks don't run until after the render method is called. That means I would not be able to use a hook on what would have formerly been ProtectedComponent (wrapped with withAuth) to determine whether to render it or not since it would already be rendered.
What is the new fancy hook way to handle this type of scenario?
render()
We can reframe the question of 'to render or not to render' a tiny bit. The render method will always be called before either hook-based callbacks or lifecycle methods. This holds except for some soon-to-be deprecated lifecycle methods.
So instead, your render method (or functional component) has to handle all its possible states, including states that require nothing be rendered. Either that, or the job of rendering nothing can be lifted up to a parent component. It's the difference between:
const Child = (props) => props.yes && <div>Hi</div>;
// ...
<Parent>
<Child yes={props.childYes} />
</Parent>
and
const Child = (props) => <div>Hi</div>;
// ...
<Parent>
{props.childYes && <Child />}
</Parent>
Deciding which one of these to use is situational.
Hooks
There are ways of using hooks to solve the same problems the HOCs do. I'd start with what the HOC offers; a way of accessing user data on the application state, and redirecting to /signin when the data signifies an invalid session. We can provide both of those things with hooks.
import { useSelector } from "react-redux";
const mapState = state => ({
isAuthenticated: state.currentUser.isAuthenticated
});
const MySecurePage = props => {
const { isAuthenticated } = useSelector(mapState);
useEffect(
() => {
if (!isAuthenticated) {
history.push("/signin");
}
},
[isAuthenticated]
);
return isAuthenticated && <MyPage {...props} />;
};
A couple of things happening in the example above. We're using the useSelector hook from react-redux to access the the state just as we were previously doing using connect, only with much less code.
We're also using the value we get from useSelector to conditionally fire a side effect with the useEffect hook. By default the callback we pass to useEffect is called after each render. But here we also pass an array of the dependencies, which tells React we only want the effect to fire when a dependency changes (in addition to the first render, which always fires the effect). Thus we will be redirected when isAuthenticated starts out false, or becomes false.
While this example used a component definition, this works as a custom hook as well:
const mapState = state => ({
isAuthenticated: state.currentUser.isAuthenticated
});
const useAuth = () => {
const { isAuthenticated } = useSelector(mapState);
useEffect(
() => {
if (!isAuthenticated) {
history.push("/signin");
}
},
[isAuthenticated]
);
return isAuthenticated;
};
const MySecurePage = (props) => {
return useAuth() && <MyPage {...props} />;
};
One last thing - you might wonder about doing something like this:
const AuthWrapper = (props) => useAuth() && props.children;
in order to be able to do things like this:
<AuthWrapper>
<Sensitive />
<View />
<Elements />
</AuthWrapper>
You may well decide this last example is the approach for you, but I would read this before deciding.
Building on the answer provided by backtick, this chunk of code should do what you're looking for:
import React, { useEffect } from "react";
import { useSelector } from "react-redux";
const withAuth = (ComponentToBeRendered) => {
const mapState = (state) => ({
isAuthenticated: state.currentUser.isAuthenticated,
});
const Authenticate = (props) => {
const { isAuthenticated } = useSelector(mapState);
useEffect(() => {
if (!isAuthenticated) {
props.history.push("/signin");
}
}, [isAuthenticated]);
return isAuthenticated && <ComponentToBeRendered {...props} />;
};
return Authenticate;
};
export default withAuth;
You could render this in a container using React-Router-DOM as such:
import withAuth from "../hocs/withAuth"
import Component from "../components/Component"
// ...
<Route path='...' component={withAuth(Component)} />

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