react-navigation wrap root AppContainer with react Context - reactjs

I'm looking for the way to manage global state across my react-native app using react-navigation. I tried to implement basic React Context, which I wanted to wrap around the react-navigation's createAppContainer() method but it didn't work.
I ended up wrapping an app container from index.js file using Context's HOC, but it seems like react-navigation has a problem with re-rendering of nested components, when Context's state is changed. I can access my Context from nested Components but they just aren't re-rendered when context state is changed.
My index.js file looks like:
import { AppRegistry } from "react-native";
import App from "./src/App";
import { withAppContextProvider } from "./src/AppContext";
import { name as appName } from "./app.json";
AppRegistry.registerComponent(appName, () => withAppContextProvider(App));
My context class looks like:
// for new react static context API
export const AppContext = createContext({});
// create the consumer as higher order component
export const withAppContext = ChildComponent => props => (
<AppContext.Consumer>
{context => <ChildComponent {...props} global={context} />}
</AppContext.Consumer>
);
// create the Provider as higher order component (only for root Component of the application)
export const withAppContextProvider = ChildComponent => props => (
<AppContextProvider>
<ChildComponent {...props} />
</AppContextProvider>
);
export class AppContextProvider extends Component {
state = {
isOnline: true
};
handleConnectivityChange = isOnline => {
this.setState({ isOnline });
};
componentDidMount = async () => {
NetInfo.isConnected.addEventListener(
"connectionChange",
this.handleConnectivityChange
);
};
componentWillUnmount() {
NetInfo.isConnected.removeEventListener(
"connectionChange",
this.handleConnectivityChange
);
}
render() {
return (
<AppContext.Provider
value={{
...this.state
}}
>
{this.props.children}
</AppContext.Provider>
);
}
}
my App.js file looks like:
const HomeStack = createStackNavigator(
{
Home: HomeScreen,
Cities: CitiesScreen
},
getStackConfig({ initialRouteName: "Home" })
);
const SettingsStack = createStackNavigator(
{
Settings: SettingsScreen
},
getStackConfig({ initialRouteName: "Settings" })
);
export default createAppContainer(
createBottomTabNavigator(
{
Home: HomeStack,
Settings: SettingsStack
}
)
);
CitiesScreen component example:
import { AppContext } from "../AppContext";
class CitiesScreen extends Component {
static contextType = AppContext;
render() {
return (
<View style={styles.container}>
<Text>This value should change on isOnline update: {this.context.isOnline}</Text>
</View>
);
}
}
Now, when I'm accessing Context, from for example CitiesScreen component, I'm currently able to get the value of isOnline state of context but whenever I switch my internet connection (on android emulator) on/off, the context state is changed but the component isn't re-rendered and my shouldComponentUpdate() method isn't triggered. Any help to make this work?

In my case, I downgraded React from 16.8 to 16.5.0, react navigation version is 3.
I'm still investigating but that's a temporary solution for now.

Related

How should the new context api work with React Native navigator?

I created a multiscreen app using React Navigator following this example:
import {
createStackNavigator,
} from 'react-navigation';
const App = createStackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
});
export default App;
Now I'd like to add a global configuration state using the new builtin context api, so I can have some common data which can be manipulated and displayed from multiple screens.
The problem is context apparently requires components having a common parent component, so that context can be passed down to child components.
How can I implement this using screens which do not share a common parent as far as I know, because they are managed by react navigator?
You can make it like this.
Create new file: GlobalContext.js
import React from 'react';
const GlobalContext = React.createContext({});
export class GlobalContextProvider extends React.Component {
state = {
isOnline: true
}
switchToOnline = () => {
this.setState({ isOnline: true });
}
switchToOffline = () => {
this.setState({ isOnline: false });
}
render () {
return (
<GlobalContext.Provider
value={{
...this.state,
switchToOnline: this.switchToOnline,
switchToOffline: this.switchToOffline
}}
>
{this.props.children}
</GlobalContext.Provider>
)
}
}
// create the consumer as higher order component
export const withGlobalContext = ChildComponent => props => (
<GlobalContext.Consumer>
{
context => <ChildComponent {...props} global={context} />
}
</GlobalContext.Consumer>
);
On index.js wrap your root component with context provider component.
<GlobalContextProvider>
<App />
</GlobalContextProvider>
Then on your screen HomeScreen.js use the consumer component like this.
import React from 'react';
import { View, Text } from 'react-native';
import { withGlobalContext } from './GlobalContext';
class HomeScreen extends React.Component {
render () {
return (
<View>
<Text>Is online: {this.props.global.isOnline}</Text>
</View>
)
}
}
export default withGlobalContext(HomeScreen);
You can also create multiple context provider to separate your concerns, and use the HOC consumer on the screen you want.
This answer takes in consideration react-navigation package.
You have to wrap your App component with the ContextProvider in order to have access to your context on both screens.
import { createAppContainer } from 'react-navigation'
import { createStackNavigator } from 'react-navigation-stack'
import ProfileContextProvider from '../some/path/ProfileContextProvider'
const RootStack = createStackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
});
const AppContainer = createAppContainer(RootStack)
const App = () => {
return (
<ProfileContextProvider>
<AppContainer />
</ProfileContextProvider>);
}
https://wix.github.io/react-native-navigation/docs/third-party-react-context/
As RNN screens are not part of the same component tree, updating the values in the shared context does not trigger a re-render across all screens. However you can still use the React.Context per RNN screen component tree.
If you need to trigger a re-render across all screens, there are many popular third party libraries such as MobX or Redux.

Get navigation props in an independent component React Native

My app.js file looks like this
export default class App extends React.Component {
render() {
return (
<Root style={{
flex: 1
}}>
<FcmHandler/>
</Root>
)
}
}
The Root component is where the entire app resides along with all the functionality, the FcmHandler is where I handle functionality related to notifications etc. Within the FcmHandler I have a method that gets a callback when a notification is clicked, inside this callback I need to navigate to a specific screen in the app based on the notification click.
The problem is using the current code above the FcmHandler component never even gets initialized.
If I try something like this
export default class App extends React.Component {
render() {
return (
<View style={{
flex: 1
}}>
<Root/>
<FcmHandler/>
</View>
)
}
}
the FcmHandler component gets called but I do not have any access to navigation props which reside inside the <Root/> component.
The <Root/> component consists of the following
const ArticleStack = StackNavigator(
{
...
}
);
const SettingsStack = StackNavigator({
...
});
export const Root = StackNavigator({
Articles: {
screen: ArticleStack
},
Settings: {
screen: SettingsStack
},
}, {
mode: 'modal',
headerMode: 'none'
});
The basic goal I am trying to achieve is, when a notification is click, irrespective of which screen the app is currently on I should be able to navigate to a particular screen. I do not want to write the navigation code in every screen component that I have, that seems redundant.
You can follow this official guide to create your navigation service. Then use the navigation service in FcmHandler instead of navigation prop. This way there is no need to put FcmHandler as a child of the navigator.
If you are using redux or mobx, it's better to move your navigation state to the store for easier access. For redux, there is an official integration guide. For mobx, you can try this.
For react-navigation users, a really cool way is to create your own Navigation Service
You can initialize your Navigation Service module, during the time initializing your navigation store as mentioned in their docs
<AppNavigator navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
addListener,
})} />
// Just add another line to config the navigator object
NavigationService.configNavigator(dispatch) <== This is the important part
NavigationService.js
import { NavigationActions } from 'react-navigation'
let config = {}
const configNavigator = nav => {
config.navigator = nav
}
const reset = (routeName, params) => {
let action = NavigationActions.reset({
index: 0,
key: null,
actions: [
NavigationActions.navigate({
type: 'Navigation/NAVIGATE',
routeName,
params,
}),
],
})
config.navigator(action)
}
const navigate = (routeName, params) => {
let action = NavigationActions.navigate({
type: 'Navigation/NAVIGATE',
routeName,
params,
})
config.navigator(action)
}
const navigateDeep = actions => {
let action = actions.reduceRight(
(prevAction, action) =>
NavigationActions.navigate({
type: 'Navigation/NAVIGATE',
routeName: action.routeName,
params: action.params,
action: prevAction,
}),
undefined
)
config.navigator(action)
}
const goBack = () => {
if (config.navigator) {
let action = NavigationActions.back({})
config.navigator(action)
}
}
export default {
configNavigator,
navigateDeep,
navigate,
reset,
goBack,
}
Explanation :
The config initializes the navigator's dispatch object whenever your redux-navigation gets initialzed, therefore you can dispatch any navigation action, wrt the method's present in the Service Component.
Use
NavigationServices.navigate('ScreenName')
Update:
React Navigation now provides a HOC wrapper withNavigation, that passes the navigation prop into a wrapped component.
It's useful when you cannot pass the navigation prop into the component directly, or don't want to pass it in case of a deeply nested child.
Usage is well mentioned in their docs.
After a bit of research, the easiest way I found was to follow their official documentation:
I created a RootNavigation.js file in the ./misc folder;
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
I imported it into App.js and created a reference to it in the return function:
import React from 'react'
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { navigationRef } from './misc/rootNavigation'; <- navigationRef is imported
…
const Stack = createStackNavigator();
function App() {
return (
<Provider store={store}>
<NavigationContainer ref={navigationRef}> <— reference to navigationRef
<Stack.Navigator>
…
<Stack.Screen
name="Screen"
component={Screen}
options={{
title: “Hello”,
headerLeft: () => <ScreenButton/>
}} />
</Stack.Navigator>
</NavigationContainer>
</Provider>
);
}
export default App
I called it inside the ScreenButton component
import React, { Component } from 'react'
…
import * as RootNavigation from '../misc/rootNavigation'; <—- imported
class RoomButton extends Component {
constructor(props) {
super(props)
}
render() {
return (
<TouchableOpacity onPress={
() => {RootNavigation.navigate( 'RoomSelectorScreen' ) <—- called here
…
</TouchableOpacity>
)
}
}

React New Context API - Access Existing Context across Multiple Files

All the examples I've seen of the new Context API in React are in a single file, e.g. https://github.com/wesbos/React-Context.
When I try to get it working across multiple files, I'm clearly missing something.
I'm hoping to make a GlobalConfiguration component (the MyProvider below) create and manage the values in the context, ready for any child component (MyConsumer below) read from it.
App.js
render() {
return (
<MyProvider>
<MyConsumer />
</MyProvider>
);
}
provider.js
import React, { Component } from 'react';
const MyContext = React.createContext('test');
export default class MyProvider extends Component {
render() {
return (
<MyContext.Provider
value={{ somevalue: 1 }}>
{this.props.children}
</MyContext.Provider >
);
}
}
consumer.js
import React, { Component } from 'react';
const MyContext = React.createContext('test');
export default class MyConsumer extends Component {
render() {
return (
<MyContext.Consumer>
{(context) => (
<div>{context.state.somevalue}</div>
)}
</MyContext.Consumer>
);
}
}
Unfortunately that fails with this in the console:
consumer.js:12 Uncaught TypeError: Cannot read property 'somevalue' of undefined
Have I completely missed the point? Is there documentation or an example of how this works across multiple files?
I think the problem that you are running into is that you are creating two different contexts, and trying to use them as one. It is the Context created by React.createContext that links Provider and Consumer.
Make a single file (I'll call it configContext.js)
configContext.js
import React, { Component, createContext } from "react";
// Provider and Consumer are connected through their "parent" context
const { Provider, Consumer } = createContext();
// Provider will be exported wrapped in ConfigProvider component.
class ConfigProvider extends Component {
state = {
userLoggedIn: false, // Mock login
profile: { // Mock user data
username: "Morgan",
image: "https://morganfillman.space/200/200",
bio: "I'm Mogran—so... yeah."
},
toggleLogin: () => {
const setTo = !this.state.userLoggedIn;
this.setState({ userLoggedIn: setTo });
}
};
render() {
return (
<Provider
value={{
userLoggedIn: this.state.userLoggedIn,
profile: this.state.profile,
toggleLogin: this.state.toggleLogin
}}
>
{this.props.children}
</Provider>
);
}
}
export { ConfigProvider };
// I make this default since it will probably be exported most often.
export default Consumer;
index.js
...
// We only import the ConfigProvider, not the Context, Provider, or Consumer.
import { ConfigProvider } from "./configContext";
import Header from "./Header";
import Profile from "./Profile";
import "./styles.css";
function App() {
return (
<div className="App">
<ConfigProvider>
<Header />
<main>
<Profile />
</main>
<footer>...</footer>
</ConfigProvider>
</div>
);
}
...
Header.js
import React from 'react'
import LoginBtn from './LoginBtn'
... // a couple of styles
const Header = props => {
return (
... // Opening tag, etc.
<LoginBtn /> // LoginBtn has access to Context data, see file.
... // etc.
export default Header
LoginBtn.js
import React from "react";
import Consumer from "./configContext";
const LoginBtn = props => {
return (
<Consumer>
{ctx => {
return (
<button className="login-btn" onClick={() => ctx.toggleLogin()}>
{ctx.userLoggedIn ? "Logout" : "Login"}
</button>
);
}}
</Consumer>
);
};
export default LoginBtn;
Profile.js
import React, { Fragment } from "react";
import Consumer from "./configContext"; // Always from that same file.
const UserProfile = props => {...}; // Dumb component
const Welcome = props => {...}; // Dumb component
const Profile = props => {
return (
<Consumer>
...
{ctx.userLoggedIn ? (
<UserProfile profile={ctx.profile} />
) : (<Welcome />)}
...
</Consumer>
...
Reading the source code of React-Context, they do
<MyContext.Provider value={{
state: this.state,
}}>
and
<MyContext.Consumer>
{(context) => <p>{context.state.age}</p>}
So if you do
<MyContext.Provider value={{ somevalue: 1 }}>
{this.props.children}
</MyContext.Provider>
You should get somevalue like that
<MyContext.Consumer>
{(context) => <div>{context.somevalue}</div>}
</MyContext.Consumer>
EDIT
What if you create a file called myContext.js with:
const MyContext = React.createContext('test');
export default MyContext;
and then import it like :
import MyContext form '<proper_path>/myContext';
As of right now, the two context you created in the files are not the same even thought the name is the same. You need to export the context that you created in one of the files, and use that through out.
so something like this, in your provider.js file:
import React, { Component } from 'react';
const MyContext = React.createContext();
export const MyContext;
export default class MyProvider extends Component {
render() {
return (
<MyContext.Provider
value={{ somevalue: 1 }}>
{this.props.children}
</MyContext.Provider >
);
}
}
then in your consumer.js file
import MyContext from 'provider.js';
import React, { Component } from 'react';
export default class MyConsumer extends Component {
render() {
return (
<MyContext.Consumer>
{(context) => (
<div>{context.somevalue}</div>
)}
</MyContext.Consumer>
);
}
}
I'm gonna throw my solution into the pot - it was inspired by #Striped and simply just renames the exports into something that makes sense in my head.
import React, { Component } from 'react'
import Blockchain from './cloudComputing/Blockchain'
const { Provider, Consumer: ContextConsumer } = React.createContext()
class ContextProvider extends Component {
constructor(props) {
super(props)
this.state = {
blockchain: new Blockchain(),
}
}
render() {
return (
<Provider value={this.state}>
{this.props.children}
</Provider>
)
}
}
module.exports = { ContextConsumer, ContextProvider }
Now it's easy to implement a ContextConsumer into any component
...
import { ContextConsumer } from '../Context'
...
export default class MyComponent extends PureComponent {
...
render() {
return (
<ContextConsumer>
{context => {
return (
<ScrollView style={blockStyle.scrollView}>
{map(context.blockchain.chain, block => (
<BlockCard data={block} />
))}
</ScrollView>
)
}}
</ContextConsumer>
)
}
I'm SO done with redux!
TLDR; Demo on CodeSandbox
My current method of solving the same problem is to use the Unstated library, which as a convenient wrapper around the React Context API. "Unstated" also provides dependency injection allow the creating of discrete instances of a container; which is handy for code reuse and testing.
How to Wrap a React/Unstated-Context as a Service
The following skeleton API Service holds state properties such as loggedIn, as well as two service methods: login() and logout(). These props and methods are now available throughout the app with a single import in each file that needs the context.
For example:
Api.js
import React from "react";
// Import helpers from Unstated
import { Provider, Subscribe, Container } from "unstated";
// APIContainer holds shared/global state and methods
class APIContainer extends Container {
constructor() {
super();
// Shared props
this.state = {
loggedIn: false
};
}
// Shared login method
async login() {
console.log("Logging in");
this.setState({ loggedIn: true });
}
// Shared logout method
async logout() {
console.log("Logging out");
this.setState({ loggedIn: false });
}
}
// Instantiate the API Container
const instance = new APIContainer();
// Wrap the Provider
const ApiProvider = props => {
return <Provider inject={[instance]}>{props.children}</Provider>;
};
// Wrap the Subscriber
const ApiSubscribe = props => {
return <Subscribe to={[instance]}>{props.children}</Subscribe>;
};
// Export wrapped Provider and Subscriber
export default {
Provider: ApiProvider,
Subscribe: ApiSubscribe
}
App.js
Now the Api.js module can be used as global provide in App.js:
import React from "React";
import { render } from "react-dom";
import Routes from "./Routes";
import Api from "./Api";
class App extends React.Component {
render() {
return (
<div>
<Api.Provider>
<Routes />
</Api.Provider>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Pages/Home.js:
Finally, Api.js can subscribe to the state of the API from deep within the React tree.
import React from "react";
import Api from "../Api";
const Home = () => {
return (
<Api.Subscribe>
{api => (
<div>
<h1>🏠 Home</h1>
<pre>
api.state.loggedIn = {api.state.loggedIn ? "👍 true" : "👎 false"}
</pre>
<button onClick={() => api.login()}>Login</button>
<button onClick={() => api.logout()}>Logout</button>
</div>
)}
</Api.Subscribe>
);
};
export default Home;
Try the CodeSandbox demo here: https://codesandbox.io/s/wqpr1o6w15
Hope that helps!
PS: Someone bash me on the head quick if I'm doing this the wrong way. I'd love to learn different/better approaches. - Thanks!

Using React context to maintain user state

I'm trying to use React's context feature to maintain information about the user throughout the application (e.g. the user ID, which will be used in API calls by various pages). I'm aware that this is an undocumented and not recommended over Redux, but my application is pretty simple (so I don't want or need the complexity of Redux) and this seems like a common and reasonable use case for context. If there are more acceptable solutions for keeping user information globally throughout the application, though, I'm open to using a better method.
However, I'm confused about how it's to be used properly: once the user logins in through the AuthPage (a child of the ContextProvider), how do I update the context in ContextProvider so it can get to other components, like the FridgePage? (Yes, context is technically not supposed to be updated, but this is a one-time operation -- if anyone knows a way to do this when ContextProvider is initialized, that would be more ideal). Does the router get in the way?
I've copied the relevant components here.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter, Route, Switch } from 'react-router-dom';
import Layout from './components/Layout.jsx';
import AuthPage from './components/AuthPage.jsx';
import ContextProvider from './components/ContextProvider.jsx';
ReactDOM.render(
<ContextProvider>
<HashRouter>
<Switch>
<Route path="/login" component={AuthPage} />
<Route path="/" component={Layout} />
</Switch>
</HashRouter>
</ContextProvider>,
document.getElementById('root')
);
ContextProvider.jsx
import React from 'react';
import PropTypes from 'prop-types';
export default class ContextProvider extends React.Component {
static childContextTypes = {
user: PropTypes.object
}
// called every time the state changes
getChildContext() {
return { user: this.state.user };
}
render() {
return(
<div>
{ this.props.children }
</div>
);
}
}
AuthPage.jsx
import React from 'react';
import PropTypes from 'prop-types';
import AuthForm from './AuthForm.jsx';
import RegisterForm from './RegisterForm.jsx';
import Api from '../api.js';
export default class AuthPage extends React.Component {
static contextTypes = {
user: PropTypes.object
}
constructor(props) {
super(props);
this.updateUserContext = this.updateUserContext.bind(this);
}
updateUserContext(user) {
console.log("Updating user context");
this.context.user = user;
console.log(this.context.user);
}
render() {
return (
<div>
<AuthForm type="Login" onSubmit={Api.login} updateUser={this.updateUserContext} />
<AuthForm type="Register" onSubmit={Api.register} updateUser={this.updateUserContext} />
</div>
);
}
}
Layout.jsx
import React from 'react';
import Header from './Header.jsx';
import { Route, Switch } from 'react-router-dom';
import FridgePage from './FridgePage.jsx';
import StockPage from './StockPage.jsx';
export default class Layout extends React.Component {
render() {
return (
<div>
<Header />
<Switch>
<Route exact path="/stock" component={StockPage} />
<Route exact path="/" component={FridgePage} />
</Switch>
</div>
);
}
}
FridgePage.jsx (where I want to access this.context.user)
import React from 'react';
import PropTypes from 'prop-types';
import Api from '../api.js';
export default class FridgePage extends React.Component {
static contextTypes = {
user: PropTypes.object
}
constructor(props) {
super(props);
this.state = {
fridge: []
}
}
componentDidMount() {
debugger;
Api.getFridge(this.context.user.id)
.then((fridge) => {
this.setState({ "fridge": fridge });
})
.catch((err) => console.log(err));
}
render() {
return (
<div>
<h1>Fridge</h1>
{ this.state.fridge }
</div>
);
}
}
Simple state provider
auth module provides two functions:
withAuth - higher order component to provide authentication data to components that need it.
update - function for updating authentication status
How it works
The basic idea is that withAuth should add auth data to props that are being passed to a wrapped component.
It is done in three steps: take props that being passed to a component, add auth data, pass new props to the component.
let state = "initial state"
const withAuth = (Component) => (props) => {
const newProps = {...props, auth: state }
return <Component {...newProps} />
}
One piece that is missing is to rerender the component when the auth state changes. There are two ways to rerender a component: with setState() and forceUpdate(). Since withAuth doesn't need internal state, we will use forceUpdate() for rerendering.
We need to trigger a component rerender whenever there is a change in auth state. To do so, we need to store forceUpdate() function in a place that is accesible to update() function that will call it whenever auth state changes.
let state = "initial state"
// this stores forceUpdate() functions for all mounted components
// that need auth state
const rerenderFunctions = []
const withAuth = (Component) =>
class WithAuth extends React.Component {
componentDidMount() {
const rerenderComponent = this.forceUpdate.bind(this)
rerenderFunctions.push(rerenderComponent)
}
render() {
const newProps = {...props, auth: state }
return <Component {...newProps} />
}
}
const update = (newState) => {
state = newState
// rerender all wrapped components to reflect current auth state
rerenderFunctions.forEach((rerenderFunction) => rerenderFunction())
}
Last step is to add code that will remove rerender function when a component is going to be unmounted
let state = "initial state"
const rerenderFunctions = []
const unsubscribe = (rerenderFunciton) => {
// find position of rerenderFunction
const index = subscribers.findIndex(subscriber);
// remove it
subscribers.splice(index, 1);
}
const subscribe = (rerenderFunction) => {
// for convinience, subscribe returns a function to
// remove the rerendering when it is no longer needed
rerenderFunctions.push(rerenderFunction)
return () => unsubscribe(rerenderFunction)
}
const withAuth = (Component) =>
class WithAuth extends React.Component {
componentDidMount() {
const rerenderComponent = this.forceUpdate.bind(this)
this.unsubscribe = subscribe(rerenderComponent)
}
render() {
const newProps = {...props, auth: state }
return <Component {...newProps} />
}
componentWillUnmount() {
// remove rerenderComponent function
// since this component don't need to be rerendered
// any more
this.unsubscribe()
}
}
// auth.js
let state = "anonymous";
const subscribers = [];
const unsubscribe = subscriber => {
const index = subscribers.findIndex(subscriber);
~index && subscribers.splice(index, 1);
};
const subscribe = subscriber => {
subscribers.push(subscriber);
return () => unsubscribe(subscriber);
};
const withAuth = Component => {
return class WithAuth extends React.Component {
componentDidMount() {
this.unsubscribe = subscribe(this.forceUpdate.bind(this));
}
render() {
const newProps = { ...this.props, auth: state };
return <Component {...newProps} />;
}
componentWillUnmoount() {
this.unsubscribe();
}
};
};
const update = newState => {
state = newState;
subscribers.forEach(subscriber => subscriber());
};
// index.js
const SignInButton = <button onClick={() => update("user 1")}>Sign In</button>;
const SignOutButton = (
<button onClick={() => update("anonymous")}>Sign Out</button>
);
const AuthState = withAuth(({ auth }) => {
return (
<h2>
Auth state: {auth}
</h2>
);
});
const App = () =>
<div>
<AuthState />
{SignInButton}
{SignOutButton}
</div>;
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
playground: https://codesandbox.io/s/vKwyxYO0
here is what i did for my project:
// src/CurrentUserContext.js
import React from "react"
export const CurrentUserContext = React.createContext()
export const CurrentUserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = React.useState(null)
const fetchCurrentUser = async () => {
let response = await fetch("/api/users/current")
response = await response.json()
setCurrentUser(response)
}
return (
<CurrentUserContext.Provider value={{ currentUser, fetchCurrentUser }}>
{children}
</CurrentUserContext.Provider>
)
}
export const useCurrentUser = () => React.useContext(CurrentUserContext)
and then use it like this:
setting up the provider:
// ...
import { CurrentUserProvider } from "./CurrentUserContext"
// ...
const App = () => (
<CurrentUserProvider>
...
</CurrentUserProvider>
)
export default App
and using the context in components:
...
import { useCurrentUser } from "./CurrentUserContext"
const Header = () => {
const { currentUser, fetchCurrentUser } = useCurrentUser()
React.useEffect(() => fetchCurrentUser(), [])
const logout = async (e) => {
e.preventDefault()
let response = await fetchWithCsrf("/api/session", { method: "DELETE" })
fetchCurrentUser()
}
// ...
}
...
the full source code is available on github: https://github.com/dorianmarie/emojeet
and the project can be tried live at: http://emojeet.com/
You don't update the context, you update the ContextProvider's state which will re render the children and populate the context through getChildContext; in your context you can place functions that when called update the provider's state. Make sure you also create a high order component(HOC) named something like withAuthContext that would read the context and turned it into props for a child component to consume, much like withIntl from react-intl or withRouter from react-router among many others, this will make the development of your components simpler and context independent as if at some point you decide to just move to redux you won't have to deal with context just replace the HOC with connect and mapStateToProps.
I think I wouldn't use the context to achieve this.
Even if your app is simple (and I understand you don't want to use Redux), it's a good practice to separate the model from the view.
Consider implementing a very simple Flux architecture: create a store and dispatch actions every time you have to change the model (eg. storing user). Your views just have to listen for the store event and update their DOM.
https://facebook.github.io/flux/docs/in-depth-overview.html#content
Here's a boilerplate with a tiny helper to manage Flux : https://github.com/christianalfoni/flux-react-boilerplate/blob/master/package.json

Could not find "store" in either the context or props when showing modal

This is my start point of the app:
const App = () => (
<Provider store={store}>
<StackNav />
</Provider>
);
AppRegistry.registerComponent('MyApp', () => App);
For the StackNav I have the following configuration:
const routeConfiguration = {
TO_LOGIN: { screen: Login },
TO_HOME: { screen: Home },
};
const stackNavigatorConfiguration = {
headerMode: 'screen',
navigationOptions: {
header: null,
}
};
Inside Login screen I show a modal inside which I cannot use the connect method of react-redux. This is the full error:
Here is how I am trying to use it:
class SignModal extends Component {
}
export default connect()(SignModal);
What can be the reason? Thank you in advanced!
Modal will exist outside the current component hierarchy. The pattern is called a Portal. This means that it cannot access the React context, hence cannot find the Redux store. Currently there is no API in React Native to fix this. You need to explicitly pass the store as a prop.
Probably better to just connect the component which has the Modal, and pass needed values as props to inner component.
Have you tried
export const mapStateToProps = (state) => {
return {
//what you want from the store
}
}
and then
export default connect(mapStateToProps)(SignModal);
This is possible. Use your modal as a view component and create a connected container component.
// components/Modal.js
class Modal extends Component {
render() {
<Modal />
}
}
export default Modal
// containers/ModalContainer
import Modal from "../components/Modal"
const mapState = state => ...
const mapDispatch = dispatch => ...
export default connect(mapState, mapDispatch)(Modal)
Import Modal from "../containers/ModalContainer" when you use it.

Resources