React Context Decorator/Subscriber? - javascript

Edit: Perhaps this could be referenced as a context subscriber?
I'm not even sure if this is the right concept that I'm trying to achieve. I want to be able to create a component that does the dirty work and just attaches context to the component that can the be consumed..
I've tried to find anything similar with no luck, which, leads me to believe I am not thinking of the right literal context of what it is I'm doing...
I've tried something like this:
import React, { Component } from "react";
export const Context = React.createContext();
export class ContextProvider extends Component {
state = {
scanning: false
};
render() {
return (
<Context.Provider
value={{
state: this.state,
handleClick: () => this.setState({
scanning: !this.state.scanning
})
}}
>
{this.props.children}
</Context.Provider>
);
}
}
And I trying to make it work with this..
import React from "react";
import { Context } from "./Context";
const WithContext = (children) => (props) => {
return (
<Context.Consumer>
{ state => (<children {...props} context={state} />) }
</Context.Consumer>
)
};
and then consuming with...
...
<WithContext>
<MyComponent />
</WithContext>
...
But, it just seems to fail or states that I'm returning a function instead of a react component..

Your WithContext component will not work like that... It needs to be a function that has the same functionality as the render function. like so:
import React from "react";
import { Context } from "./Context";
const WithContext = ({ children, ...props }) => (
<Context.Consumer>{state => React.Children.map(children, (child) => (
React.cloneElement(child, { context: state })
))}</Context.Consumer>
);
note that we traverse every direct child of the withContext children using React.Children.map (docs) and add a context prop to them by making use of React.cloneElement (docs). This keeps the child component's original props and shallow merges them into the second parameter passed to the function.

There are a bunch of little errors in your code for using context... Here is a complete example...
Let's say we have a structure where we have App.js -> Parent.js -> Child.js components... Instead of passing the data via props from App to Parent to Child we want to make use of the context API and avoid the prop drilling and have the Child consume the data directly...
Here is what that will look like:
context.js:
import React from 'react';
const Context = React.createContext();
export class Provider extends React.Component {
state = { name: 'Bob', age: 20 };
handleGrowUp = () => {
this.setState({ age: this.state.age + 1 });
};
render() {
return (
<Context.Provider
value={{
state: {
...this.state,
},
actions: {
growUp: this.handleGrowUp,
},
}}
>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
App.js:
import React from 'react';
import Parent from './Parent';
import { Provider } from './context';
const App = () => (
<Provider>
<Parent />
</Provider>
);
export default App;
Parent.js:
import React from 'react';
import Child from './Child';
const Parent = () => (
<div>
<Child />
</div>
);
export default Parent;
Child.js:
import React from 'react';
import { Consumer } from './context';
const Child = () => (
<div>
<Consumer>
{value => (
<div>
<p>{value.state.name}</p>
<p>{value.state.age}</p>
<button onClick={value.actions.growUp}>Grow Up</button>
</div>
)}
</Consumer>
</div>
);
export default Child;
Here is a working demo: https://codesandbox.io/s/9z06xzlyly

Related

In a Functional Component, how do you access props from the redux store using react-redux connect?

In a functional react component like below, how do you access the props that are sent over from the redux store, similar to how on a class component's this.props in componentDidMount() are accessible, shown in the comment below?
import React from 'react';
import { connect } from "react-redux";
import * as actions from "../../actions";
const ComponentName = () => {
// componentDidMount() {
// this.props;
// }
return (
<div>
<div>???</div>
</div>
);
};
function mapStateToProps(state) {
return { state };
}
export default connect(mapStateToProps, actions)(ComponentName);
The props will be passed as the first argument of the functional component.
const ComponentName = (props) => {
return (
<div>
<div>{ props.something }</div>
</div>
);
};
You can find more details on the official document https://reactjs.org/docs/components-and-props.html

Next JS Layout component, pass props to children

I have found a code that solved my problem in Next JS re rendering when changing pages. But now i need to send props to the children component. I got no idea how i can make it works here, this is my layout.js code. As you can see i can send props to Header component but for children i dont know how, because it is a variable and not a component.
import Header from "../components/header";
import Footer from "../components/footer";
import { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{children}
<Footer />
</Fragment>
);
}
Thank you for the help
Have you considered using React's Context API? The idea is that when using the Context API your component's state get's lifted, to be managed at a global scale. If a component needs a prop, instead of passing props down manually (prop drilling) you can simply wrap you component in what's known as a context provider. This will allow that Component to access the global state of your application. This is good because, when your application gets bigger, you may need to pass props down through many components which can clutter and add unneeded confusion.
React provides some great documentation to set your React application up to use the Context API. Highly recommend checking it out!
https://reactjs.org/docs/context.html
Try this
import Header from "../components/header";
import Footer from "../components/footer";
import { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
function recursiveMap(children, fn) {
return React.Children.map(children, child => {
if (!React.isValidElement(child) || typeof child.type == 'string') {
return child;
}
if (child.props.children) {
child = React.cloneElement(child, {
children: recursiveMap(child.props.children, fn)
});
}
return fn(child);
});
}
// Add props to all child elements.
const childrenWithProps = recursiveMap(children, child => {
// Checking isValidElement is the safe way and avoids a TS error too.
if (isValidElement(child)) {
// Pass additional props here
return cloneElement(child, { currentUser: { ...user } })
}
return child;
});
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{childrenWithProps}
<Footer />
</Fragment>
);
}
You can use React's cloneElement to achieve that.
React.cloneElement(children, {
isRegisterPage: pageProps.isRegisterPage,
isLoginPage: pageProps.isLoginPage,
outHome: pageProps.outHome
})
Complete example in your case:
import Header from "../components/header";
import Footer from "../components/footer";
import React, { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{
React.cloneElement(children, {
isRegisterPage: pageProps.isRegisterPage,
isLoginPage: pageProps.isLoginPage,
outHome: pageProps.outHome
})
}
<Footer />
</Fragment>
);
}
From the answer of Lucas Raza, below is an example that uses Context API to apply themes to different components
1.Create a context File
//ThemeContex.js
import { createContext, useState } from "react";
export const ThemeContext = createContext();
export const withThemeContext = Component => {
const WrappedComp = props => {
const [darkColor,lightColor] = ["#3b3b3b", "#ddd"]
const [lightBackgoround,darkBackgoround] = ["#ececec","#1d2a35"]
const darkTheme = {
backgroundColor: darkBackgoround,
color:lightColor,
}
const lightTheme = {
backgroundColor: lightBackgoround,
color:darkColor,
}
const themes = {
darkTheme, lightTheme
}
const [theme, setTheme] = useState(lightTheme)
const children ={
theme,
themes,
setTheme,
}
return(
<StylesContext.Provider value={{...children}} >
<Component {...props} />
</StylesContext.Provider>
)
}
return WrappedComp;
}
In _app.js, import withThemeContext higher component and wrap MyApp with it when exporting it.
import { withThemeContext } from '../components'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default withThemeContext(MyApp)
You can know use theme any where in a component
import { useContext } from 'react'
import {ThemeContext} from '../components'
export default function Home() {
const { theme } =useContext(ThemeContext)
return (
<div id="home" style={theme}>
// Home logic...
</div>
)
}

Pass data from parent to child and sibling children using context api

I am trying to display fetched data in child component, using context api. But I'm getting below error on browser
TypeError: render is not a function
The above error occurred in the component:
in AppDataList (at App.js:32)
in div (at App.js:30)
in App (at src/index.js:7)
and below warning
Warning: A context consumer was rendered with multiple children, or a
child that isn't a function. A context consumer expects a single child
that is a function. If you did pass a function, make sure there is no
trailing or leading whitespace around it.
App.js
import React, { Component } from "react";
import "./App.css";
import AppDataList from "./components/AppDataList";
export const AppContext = React.createContext();
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
appData: []
};
}
fetchAppData() {
fetch(` http://localhost:4000/AppDataList`)
.then(res => res.json())
.then(res => {
console.log(res)
this.setState({
appData: res
});
});
}
componentDidMount() {
this.fetchAppData();
}
render() {
return (
<div className="App">
<AppContext.Provider>
<AppDataList />
</AppContext.Provider>
</div>
);
}
}
AppDataList.js
import React, { Component } from "react";
import { AppContext } from "../App";
export default class AppDataList extends Component {
render() {
return (
<AppContext.Consumer>
<div>{context => <p>{context.state}</p>}</div>
</AppContext.Consumer>
);
}
}
I also want to do something like
<AppContext.Provider>
<Child1 />
<Child2 />
<Child3 />
</AppContext.Provider>
and consume data in respective child component.
You have to put the value you want to pass to Consumers via the value prop in the Provider:
<Context.Provider value={{ appData }}>
Below works:
<AppContext.Consumer>
{context => <p>{context.state}</p>}
</AppContext.Consumer>
consumer looks for function, not component.
ref: Seeing "render is not a function" when trying to use Context API

How can I access to the value of Context without calling the render function? [duplicate]

This question already has answers here:
Access React Context outside of render function
(5 answers)
Closed 4 years ago.
So I need to access to the value of context without calling it as a render function.
NOT Like this:
const Child = () => {
return (
<ThemeConsumer>
{context => {return <Text>{context}</Text>}}
</ThemeConsumer>
);
};
I have this so far:
import React from 'react';
export const ThemeContext = React.createContext(0);
export const ThemeProvider = ThemeContext.Provider;
export const ThemeConsumer = ThemeContext.Consumer;
And I am using the provider like this:
render() {
const { index } = this.state;
return (
<ThemeProvider value={index}>
<TabView
key={Math.random()}
navigationState={this.state}
renderScene={this.renderScene}
renderTabBar={this.renderTabBar}
onIndexChange={this.handleIndexChange}
/>
</ThemeProvider>
);
}
Is that ok?
The main issue I have so far is that where I need that value, is not a class nor a functional component.
I need to do something like this:
import { ThemeConsumer } from '../../context/BottomNavigationTheme';
import HomeScreen from '../screens/HomeScreen';
import HomeScreen2 from '../screens/HomeScreen2';
functionToGetContextValue = () => valueFromContext;
const HomeStack = createStackNavigator({
Home: functionToGetContextValue ? HomeScreen : HomeScreen2, // HERE I NEED IT
});
HomeStack.navigationOptions = {
tabBarLabel: 'All Passengers',
tabBarIcon: ({ focused }) => <AllPassengersIcon focused={focused} />,
};
export default createBottomTabNavigator({
HomeStack,
LinksStack,
SettingsStack,
});
What should I do to access to that value?
You can with HOC. Create a function withTheme
export const withTheme = Component => props => (
<ThemeConsumer>
{context => <Component {...props} them={context} />
</ThemeConsumer>
)
Then use in any child component you want.
class TabView extends React.PureComponent {
componentDidMount() {
console.log(this.props.theme)
}
...
}
export default withTheme(TabView)
A working Context with HOC is here https://codesandbox.io/s/o91vrxlywy
If you use React hooks (ver 16.8) you can use useContext API https://reactjs.org/docs/hooks-reference.html#usecontext

React 16.3 Context API -- Provider/Consumer issues

I have been doing some experiment on React 16.3.1 ContextAPI. and I encountered into something that I couldn't fathom. I was hoping I could use your help.
Note: The problem have been solved but, its not the solution I am looking for.
Let start with first experiment on multiple components within same file Index.js.
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class AppProvider extends Component {
state = {
name: 'Superman',
age: 100
};
render() {
const increaseAge = () => {
this.setState({ age: this.state.age + 1 });
};
const decreaseAge = () => {
this.setState({ age: this.state.age - 1 });
};
return (
<Provider
value={{
state: this.state,
increaseAge,
decreaseAge
}}
>
{this.props.children}
</Provider>
);
}
}
class Person extends Component {
render() {
return (
<div className="person">
<Consumer>
{context => (
<div>
<p>I'm {context.state.name}</p>
<p>I'm {context.state.age}</p>
<button onClick={context.increaseAge}>
<span>+</span>
</button>
<button onClick={context.decreaseAge}>
<span>-</span>
</button>
</div>
)}
</Consumer>
</div>
);
}
}
class App extends Component {
render() {
return (
<AppProvider>
<div className="App">
<p>Imma Apps</p>
<Person />
</div>
</AppProvider>
);
}
}
export default App;
As result, this render out perfect without any error. I am able to see name (Superman) and age (100). I am able to increase and decrease age by 1.
As you can see, I have imported {createContext} from react then created {Provider, Consumer}. Wrapped <Provider> with state value and <Consumer>.
Next Experiment, was exact copy each component from index.js and paste them separately into their own files.
AppProvider.js
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class AppProvider extends Component {
state = {
name: 'Superman',
age: 100
};
render() {
const increaseAge = () => {
this.setState({ age: this.state.age + 1 });
};
const decreaseAge = () => {
this.setState({ age: this.state.age - 1 });
};
return (
<Provider
value={{
state: this.state,
increaseAge,
decreaseAge
}}
>
{this.props.children}
</Provider>
);
}
}
export default AppProvider;
Person.js
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class Person extends Component {
render() {
return (
<div className="person">
<Consumer>
{context => (
<div>
<p>I'm {context.state.name}</p>
<p>I'm {context.state.age}</p>
<button onClick={context.increaseAge}>
<span>+</span>
</button>
<button onClick={context.decreaseAge}>
<span>-</span>
</button>
</div>
)}
</Consumer>
</div>
);
}
}
export default Person;
App.js
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class App extends Component {
render() {
return (
<AppProvider>
<div className="App">
<p>Imma Apps</p>
<Person />
</div>
</AppProvider>
);
}
}
export default App;
As result, I am getting error - TypeError: Cannot read property 'state' of undefined.
I am unable to grasp what the exactly error was.. All I did was copy and paste each into files without changing any syntax.
Although, Alternative method was to create a new file and add syntax following...
Context.js
import { createContext } from 'react';
const Context = createContext();
export default Context;
Then go into each files (AppProvider.js. Person.js and App.js) and replace...
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();'
...into...
import Context from './Context.js';. Also replace... <Provider> into <Context.Provider> and <Consumer> into <Context.Consumer>.
And this killed the error. However, this is not the solution I am looking for. I wanted to use <Provider> tag instead of <Context.Provider>.
Question is, Why am I getting this error?
Why am I unable to use this method...
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();'
for each components in separate files so I could use <Provider> tag ?
Are there any way around to get the solution I'm looking for?
Your help is appreciated and Thanks in advance.
Your are getting TypeError: Cannot read property 'state' of undefined.
Beacuse every time you call const { Provider, Consumer } = createContext(); it creates a new object, this object need to be exported in order for consumers to consume this specific object.
So in person.js
when you try doing {context.state.age} it really does not have state on this object, you just created a new Context which is empty or rather with React internal methods and properties.
So in order to consume the same object just export it, like you did in Context.js and instead of doing:
import { createContext } from 'react';
const Context = createContext();
export default Context;
replace to:
import { createContext } from 'react';
const { Provider, Consumer } = createContext();
export { Consumer, Provider };
Then when you want to use it in other files ( meaning import it ) just call:
import { Consumer, Provider } from './Context.js';

Categories