I'm having serious issues with the "new" React Context ( https://reactjs.org/docs/context.html ) to work like I want/expect from the documentation. I'm using React v.16.8.6 (upgrading will probably take ages, it's a big app). I know there is a bit of a mix between old and new stuff but plz don't get stuck on that..
I did it like this to be as flexible as possible but it doesn't work.
The issue is, when it comes to contextAddToCart(..) it only executes the empty function instead of the one I defined in state as the documentation this.addToCart. I have consumers in other places as well. It seems like perhaps it's executing this in the wrong order. Or every time a Compontent imports MinicartContext it's reset to empty fn.. I don't know how to get around this..
I'll just post the relevant code I think will explain it best:
webpack.config.js:
const APP_DIR = path.resolve(__dirname, 'src/');
module.exports = function config(env, argv = {}) {
return {
resolve: {
extensions: ['.js', '.jsx'],
modules: [
path.resolve(__dirname, 'src/'),
'node_modules',
],
alias: {
contexts: path.resolve(__dirname, './src/contexts.js'),
},
contexts.js
import React from 'react';
export const MinicartContext = React.createContext({
addToCart: () => {},
getState: () => {},
});
MinicartContainer.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
MinicartContext,
} from 'contexts';
export default class MinicartContainer extends Component {
constructor(props) {
super(props);
this.addToCart = (product, qty) => {
const { prices } = product;
const { grandTotal, qtyTotal } = this.state;
this.setState({
grandTotal: grandTotal + prices.price,
qtyTotal: qtyTotal + qty,
});
};
this.state = {
grandTotal: -1,
qtyTotal: -1,
currencyCode: '',
addToCart: this.addToCart,
};
}
render() {
const { children } = this.props;
return (
<MinicartContext.Provider value={this.state}>
{children}
</MinicartContext.Provider>
);
}
Header.jsx:
import React, { Component } from 'react';
import {
MinicartContext,
} from 'contexts';
class Header extends Component {
render() {
return (
<div>
<MinicartContainer MinicartContext={MinicartContext}>
<Minicart MinicartContext={MinicartContext} />
</MinicartContainer MinicartContext={MinicartContext}>
{/* stuff */}
<MinicartContainer MinicartContext={MinicartContext}>
<Minicart MinicartContext={MinicartContext} />
</MinicartContainer MinicartContext={MinicartContext}>
</div>
)
}
}
export default Header;
AddToCartButton.jsx
import {
MinicartContext,
} from 'contexts';
export default class AddToCartButton extends Component {
addToCart(e, contextAddToCart) {
e.preventDefault();
const QTY = 1;
const { product, active } = this.props;
// doing stuff ...
contextAddToCart(product, QTY);
}
render() {
return (
<React.Fragment>
<MinicartContext.Consumer>
{({context, addToCart}) => (
<div
onClick={(e) => { this.addToCart(e, addToCart); }}
Seems to me that you don't have fully understand how the context API words.
Here's my HOC implementation of contexts, maybe it can help you to understand better how things work.
export const MinicartContext = React.createContext({}) // Export the Context so we can use the Consumer in class and functional components (above). Don't use the Provider from here.
// Wrap the provider to add some custom values.
export const MinicartProvider = props => {
const addToCart = () => {
//Add a default version here
};
const getState = () => {
//Add a default version here
};
// Get the custom values and override with instance ones.
const value = {addToCart, getState, ...props.value}
return <MinicartContext.Provider value={value}>
{props.children}
</MinicartContext.Provider>
}
Then when using the provider:
const SomeComponent = props => {
const addToCart = () => {
//A custom version used only in this component, that need to override the default one
};
//Use the Wrapper, forget the MinicartContext.Provider
return <MinicartProvider value={{addToCart}}>
/* Stuff */
</MinicartProvider>
}
And when using the consumer you have three options:
Class Components with single context
export default class AddToCartButton extends Component {
static contextType = MinicartContext;
render (){
const {addToCart, getState} = this.context;
return (/*Something*/)
}
}
Class Components with multiple contexts
export default class AddToCartButton extends Component {
render (){
return (
<MinicartContext.Consumer>{value => {
const {addToCart, getState} = value
return (/*Something*/)
}}</MinicartContext.Consumer>
)
}
}
Functional Components
const AddToCartButton = props => {
const {addToCart, getState} = useContext(MinicartContext);
}
You can create the Wrapper Provider as a class component too, and pass the full state as value, but it's unnecessary complexity.
I Recommend you take a look at this guide about contexts, and also, avoid using the same name on the same scope... Your AddToCartButton.jsx file was reeeeally confusing :P
The issue I had was that I was using <MinicartContainer> in multiple places but all should act as one and the same. Changing it so it wrapped all elements made other elements reset their state when the context updated.
So the only solution I found was to make everything static (including state) inside MinicartContainer, and keep track of all the instances and then use forceUpdate() on all (needed) instances. (Since I am never doing this.setState nothing ever updates otherwise)
I though the new React context would be a clean replacement for things like Redux but as it stands today it's more a really vague specification which can replace Redux in a (sometimes) non standard way.
If you can just wrap all child Consumers with a single Provider component without any side-effects then you can make it a more clean implementation. That said I don't think what I have done is bad in any way but not what people expect a clean implementation should look like. Also this approach isn't mentioned in the docs at all either.
In addition to Toug's answer, I would memoize the exposed value prop of the provider. Otherwise it will re-render it's subscribers every time even if the state doesn't change.
export const MinicartContext = React.createContext({}) // Export the Context so we can use the Consumer in class and functional components (above). Don't use the Provider from here.
// Wrap the provider to add some custom values.
export const MinicartProvider = props => {
const addToCart = () => {
//Add a default version here
};
const getState = () => {
//Add a default version here
};
// Get the custom values and override with instance ones.
const value = useMemo(
() => ({addToCart, getState, ...props.value}),
[addToCart, getState, props.value]
);
return <MinicartContext.Provider value={value}>
{props.children}
</MinicartContext.Provider>
}
Related
I'm trying to access functions inside a class based component which can be used throughout the project. The reason I'm thinking class based is because these request/ functions require an init() method to be called before accessing such data every time. For example:
SharedSDKFile .js
import Facebook from 'facebook-sdk';
class SharedSDKFile extends Component {
constructor() {
Facebook.init({// init some stuff})
}
async user() {
return Facebook.getUser()
}
render() {
return(// ????????????????)
}
}
// *****************************************************
Dashboard.js
// *****************************************************
import Facebook from '../{path}/SharedSDKFile'
const dashboard = () => {
let [person,setPerson] = useState()
// cool function to get and set Users
let user = getUser();
setPerson(user)
// End of cool function
}
I even tried structuring it with a different approach just exporting functions
SharedSDKFile.js
async init() {
// init stuff
}
export const getUser = async(data) => {
init()
// get user
}
// *****************************************************
Dashboard.js
// *****************************************************
import {getUser}from '../{path}/SharedSDKFile'
const dashboard = () => {
let [person,setPerson] = useState()
// cool function to get and set Users
let user = getUser();
setPerson(user)
// End of cool function
}
While a file that exports your function works, the state disappears on reload/ refresh.
Perhaps there is a better solution to this and I'm overthinking it. I have considered redux or localstate, but I will have several functions inside the sharedSDKFile.js which will require several action and reducers...
I am trying to prevent invoking multiple init() and redundancy if I am to import, for example, the FacebookSDK in every file that needs it.
I like using a context singleton approach to isolate external services that can be used app-wide, which may or may not suit what you want to do. It uses context providers/consumers rather than things like Redux.
This isn't the complete picture but hopefully it provides some idea of how the approach might work for you:
FacebookContext.js (or AWSContext.js, or... any specific service)
import React, { Component, createContext } from "react";
import Facebook from "facebook-sdk";
export const FacebookContext = createContext({});
class FacebookProvider extends Component {
state = {
user: null,
// whatever else you need to expose to calling components, like
// lastLoggedIn: null,
// verified: false,
// ...
};
componentDidMount() {
Facebook.init({
// init some stuff
})
}
setUser = user => {
this.setState({
user
});
}
// whatever other methods/data you want this class to expose
render() {
return (
<FacebookContext.Provider
value={{
user: this.state.user // available with FacebookContext.user
// other state values
//
setUser: this.setUser // available with FacebookContext.setUser
// other class methods
}}
>
{this.props.children}
</FacebookContext.Provider>
);
}
}
export default FacebookProvider;
Add the Provider to your top-line app file, something like this in e.g. App.js:
import FacebookProvider from "/path/to/FacebookContext";
// ...
class App extends React.Component {
render() {
return (
<FacebookProvider>
{yourAppRenderStuff}
</FacebookProvider>
);
}
}
Add the Consumer to any calling components:
import { FacebookContext } from "/path/to/FacebookContext";
class SomethingComponent extends Component {
componentDidMount() {
const { facebookContext } = this.props;
facebookContext.setUser(`some user`);// available in other components
console.log(facebookContext.user);// broadcasted to other components
}
render() {
return (
<></>
);
}
}
const Something = () => (
<FacebookContext.Consumer>
{facebookContext => (
<SomethingComponent facebookContext={facebookContext} />
)}
</FacebookContext.Consumer>
);
export default Something;
So I have this navigator component where depending on a value coming from another component, I need to show a different bottom navigation.
For now I am getting an error on the context consumer, here:
import { ThemeProvider, ThemeConsumer } from '../context/some';
const SelectedRoute = () => (
<ThemeConsumer>
{context => (context ? MainTabNavigator : PickupNavigator)}
</ThemeConsumer>
);
export default createAppContainer(
createSwitchNavigator(
{
App: SelectedRoute,
},
),
);
This is the only thing I have to create context:
const ThemeContext = React.createContext(0);
export const ThemeProvider = ThemeContext.Provider;
export const ThemeConsumer = ThemeContext.Consumer;
I am getting this warning:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
What can I do to render what I need correctly?
You want to return JSX from the function given as child to ThemeConsumer, not just return a component.
const SelectedRoute = () => (
<ThemeConsumer>
{context => (context ? <MainTabNavigator /> : <PickupNavigator />)}
</ThemeConsumer>
);
I have not run the example, but just suggesting from the docs. I thought the explanation was pretty clear but I could be wrong.
Just define a context variable in a separate file, in your case like this:
export const IndexContext = React.createContext({
indexValue: value,
toggleNavigator: () => {},
});
In your component(which receives indexValue), you can use the context value and toggle accordingly:
<ThemeContext.Consumer>
{({indexValue, toggleNavigator}) => (
// your component which uses the theme
)}
</ThemeContext.Consumer>
Since your component A is a stateful component, you can handle changes and update the context value there.
class App extends React.Component {
constructor(props) {
super(props);
this.toggleIndex = () => {
this.setState({ index });
this.handleStateIndexChange();
MY_CONTEXT = index;
};
// State also contains the updater function so it will
// be passed down into the context provider
this.state = {
index: index,
toggleIndex: this.toggleIndex,
};
}
render() {
// The entire state is passed to the provider
return (
<IndexContext.Provider value={this.state}>
<Content />
</IndexContext.Provider>
);
}
}
I hope this helps.
I have a stateless functional component which has no props and populates content from React context. For reference, my app uses NextJS and is an Isomorphic App. I'm trying to use React.memo() for the first time on this component but it keeps re-rendering on client side page change, despite the props and context not changing. I know this due to my placement of a console log.
A brief example of my component is:
const Footer = React.memo(() => {
const globalSettings = useContext(GlobalSettingsContext);
console.log('Should only see this once');
return (
<div>
{globalSettings.footerTitle}
</div>
);
});
I've even tried passing the second parameter with no luck:
const Footer = React.memo(() => {
...
}, () => true);
Any ideas what's going wrong here?
EDIT:
Usage of the context provider in _app.js looks like this:
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
...
return { globalSettings };
}
render() {
return (
<Container>
<GlobalSettingsProvider settings={this.props.globalSettings}>
...
</GlobalSettingsProvider>
</Container>
);
}
}
The actual GlobalSettingsContext file looks like this:
class GlobalSettingsProvider extends Component {
constructor(props) {
super(props);
const { settings } = this.props;
this.state = { value: settings };
}
render() {
return (
<Provider value={this.state.value}>
{this.props.children}
</Provider>
);
}
}
export default GlobalSettingsContext;
export { GlobalSettingsConsumer, GlobalSettingsProvider };
The problem is coming from useContext. Whenever any value changes in your context, the component will re-render regardless of whether the value you're using has changed.
The solution is to create a HOC (i.e. withMyContext()) like so;
// MyContext.jsx
// exported for when you really want to use useContext();
export const MyContext = React.createContext();
// Provides values to the consumer
export function MyContextProvider(props){
const [state, setState] = React.useState();
const [otherValue, setOtherValue] = React.useState();
return <MyContext.Provider value={{state, setState, otherValue, setOtherValue}} {...props} />
}
// HOC that provides the value to the component passed.
export function withMyContext(Component){
<MyContext.Consumer>{(value) => <Component {...value} />}</MyContext.Consumer>
}
// MyComponent.jsx
const MyComponent = ({state}) => {
// do something with state
}
// compares stringified state to determine whether to render or not. This is
// specific to this component because we only care about when state changes,
// not otherValue
const areEqual = ({state:prev}, {state:next}) =>
JSON.stringify(prev) !== JSON.stringify(next)
// wraps the context and memo and will prevent unnecessary
// re-renders when otherValue changes in MyContext.
export default React.memo(withMyContext(MyComponent), areEqual)
Passing context as props instead of using it within render allows us to isolate the changing values we actually care about using areEqual. There's no way to make this comparison during render within useContext.
I would be a huge advocate for having a selector as a second argument similar to react-redux's new hooks useSelector. This would allow us to do something like
const state = useContext(MyContext, ({state}) => state);
Who's return value would only change when state changes, not the entire context.
But I'm just a dreamer.
This is probably the biggest argument I have right now for using react-redux over hooks for simple apps.
Consider this react Component code syntax which include prop-type:
import PropTypes from 'prop-types';
class MyComponent extends React.Component {
render() {
const children = this.props.children;
return (
<div>
{children}
</div>
);
}
}
MyComponent.propTypes = {
children: PropTypes.element.isRequired
};
I want to apply above syntax to refactor this of this code. But I have problem in line resetHighlights: () => void.
It would be nice if someone refactor whole this blow code:
type Props = {
highlights: Array<T_ManuscriptHighlight>,
resetHighlights: () => void
};
const updateHash = highlight => {
location.hash = `highlight-${highlight.id}`;
};
function Sidebar({ highlights, resetHighlights }: Props) {
return (
<div>
...
</div>
);
}
export default Sidebar;
also I want to add function like below inside state-full class but I got error:
togglePersonsHandler = () => {
const show = this.state.showPersons;
this.setState({showPersons: !show});
};
It looks like that file is using either flow or typescript to provide static typing, rather than prop-types. If you wanted to remove the typescript/flow usage, the best equivalent you could do with prop-types would be
Sidebar.propTypes = {
highlights: PropTypes.arrayOf(PropTypes.shape({...})),
resetHighlights: PropTypes.func
}
May want to stick with Typescript/flow if the codebase is using it a lot though; type safety can be nice.
I need to wrap functionality in a, lets say button. However when I call the HOC in the render method of another component I get nothing.
I have this HOC
import React,{Component,PropTypes} from 'react';
export let AddComment = (ComposedComponent) => class AC extends React.Component {
render() {
return (
<div class="something">
Something...
<ComposedComponent {...this.props}/>
</div>
);
}
}
and trying to do this
import {AddComment} from '../comments/add.jsx';
var Review = React.createClass({
render: function(){
return (
<div className="container">
{AddComment(<button>Add Comment</button>,this.props)}
</div>
});
module.exports = Review;
I want AddComment to open a Dialog and submit a comments form when I click the button. I need AddComment to be available other components throughtout the app.
Is the HOC pattern correct? How can I easily accomplish this?
Thanks
To summarize really quick: What are higher-order components?
Just a fancy name for a simple concept: Simply put: A component that takes in a component and returns you back a more enhanced version of
the component.
We are essentially enhancing a component.
Accepts a function that maps owner props to a new collection of props
that are passed to the base component.
We are basically passing the props down from that BaseComponent down
to the Wrapped Component so that we can have them available in that
child component below:
Use to compose multiple higher-order components into a single
higher-order component.
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { AddComment } from '../comments/add.jsx';
const mapProps = propFunction => Component => (props) => {
return React.createFactory(Component)(propFunction(props));
};
const compose = (propFunction, ComponentContainer) => (BaseComponent) => {
return propFunction(ComponentContainer(BaseComponent));
};
const Review = AddComment(({ handleReviewToggle }) => (
<div className="container">
<ReviewButton
primaryText="Add Comment"
_onClick={handleReviewToggle}
/>
</div>
));
export default Review;
// ================================================================== //
const EnhanceReview = compose(withProps, AddComment)(Review);
const withProps = mapProps(({ ...props }) => ({ ...props }));
The AddComment Container that will have the button and the dialog itself.
export function AddComment(ComposedComponent) {
class AC extends React.Component {
constructor() {
super();
this.state = {open: false};
}
handleReviewToggle = () => {
this.setState({ open: !this.state.open })
}
render() {
return (
<ComposedComponent
{...this.props}
{...this.state}
{...{
handleReviewToggle: this.handleReviewToggle,
}}
/>
);
}
}
export default AddComment;
// ==================================================================
The ReviewButton Button that will fire an event to change state true or false.
const ReviewButton = ({ _onClick, primaryText }) => {
return (
<Button
onClick={_onClick}
>
{primaryText || 'Default Text'}
</Button>
);
};
export default ReviewButton;
// ================================================================== //
However this was all done without using a library. There's one out called recompose here: https://github.com/acdlite/recompose. I highly suggest that you try it out without a library to get a good understanding of Higher Order Components.
You should be able to answer these questions below after playing with Higher Order components:
What is a Higher Order Component?
What are the disadvantages of using HOC? What are some use cases?
How will this improve performance? And how can I use this to optimize for performance?
When is the right time to use a HOC?