How do you use React context inside App.js file? - javascript

I am building a multi-language app. I am using react-intl. So far, so good. I made a state of the language with context api, so I can switch it easily. However I get this error when I try to use the state in App.js: TypeError: Object is not iterable (cannot read property Symbol(Symbol.iterator)).
Here is my context file:
import React, {useState, createContext} from 'react'
export const LanguageContext = createContext();
export const LanguageProvider = (props) => {
const [language, setLanguage] = useState('')
return (
<LanguageContext.Provider value = {[language,setLanguage]}>
{props.children}
</LanguageContext.Provider>
)
}
And here is the App.js:
function App() {
const [language, setLanguage] = useContext(LanguageContext)
return (
<LanguageProvider>
//i tried using locale={language}
<I18nProvider locale={LOCALES.language}>
<CartProvider>
<TableProvider>
<div className="App">
<Router>
<Header />
<Switch>
<Route path='/Cart' component={Cart} />
<Route path='/:group/:subGroup/:item' component={Item} />
<Route path='/:group/:subGroup' component={Items} />
<Route path='/' exact component={Home} />
<Route path='/:group' component={Groups} />
</Switch>
</Router>
</div>
</TableProvider>
</CartProvider>
</I18nProvider>
</LanguageProvider>
);
}
export default App
Here is the locale file that Im using to pass to the I18nProvider :
export const LOCALES = {
ENGLISH : 'en',
FRENCH: 'fr'
}
And where I change the context value(another component, not App.js):
const [language, setLanguage] = useContext(LanguageContext)
following line is cut from jsx:
onClick={() => setLanguage('en')}
I thinks the problem might be because I am trying to access the context before the App.js return statement, where the provider wraps the children but even if this is the case, I still don't know what might fix it. Any help would be appreciated!

I thinks the problem might be because I am trying to access the context before the App.js return statement
You're right this is the problem.
Depending on where you want to use useContext you could create an extra component that is a child of LanguageProvider. Then inside this child you are able to use useContext.
To give a simplified example:
const App = () => {
const [language, setLanguage] = useContext(LanguageContext);
useEffect(() => {
setLanguage('en');
}, []);
return <p>{language}</p>;
};
export default function AppWrapper() {
return (
<LanguageProvider>
<App />
</LanguageProvider>
);
}

I had the same problem trying to apply an authentication flow with react-navigation v5. I tried to follow the documentation as it is:
Authentication flows with react-navigation v5 But when trying to mix it with Context I run into the same issue
As in the previous Answer, I solve it in the same way.
Here it's an example where it has 3 possible Screens Stacks:
I create a component where I'll be using the context
const RootStack = () => {
const { state } = useContext(AuthContext);
return (
<Stack.Navigator>
{false ? (
<Stack.Screen name="Splash" component={SplashScreen} />
) : state.token === null ? (
<Stack.Screen
name="Authentication"
component={AuthenticationStack}
options={{
title: 'Sign in',
headerShown: false,
animationTypeForReplace: false ? 'pop' : 'push',
}}
/>
) : (
<Stack.Screen name="Home" component={AppStack} />
)}
</Stack.Navigator>
);
};
And then I insert the component inside the provider:
export default ({ navigation }) => {
return (
<AuthProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="RootStack"
component={RootStack}
options={{
headerShown: false,
}}
/>
</Stack.Navigator>
</NavigationContainer>
</AuthProvider>
);
};

Related

state do not change with useContext

hello
I am trying to make a menu toggle, where I have a variable with false as initial value, using react createContext and useContext hook, I set the initial state as true
// useMenu Context
import React, { useContext, useState } from 'react'
export const useToggle = (initialState) => {
const [isToggled, setToggle] = useState(initialState)
const toggle = () => setToggle((prevState) => !prevState)
// return [isToggled, toggle];
return { isToggled, setToggle, toggle }
}
const initialState = {
isMenuOpen: true,
toggle: () => {},
}
export const MenuContext = React.createContext(initialState)
const MenuProvider = ({ children }) => {
const { isToggled, setToggle, toggle } = useToggle(false)
const closeMenu = () => setToggle(false)
return (
<MenuContext.Provider
value={{
isMenuOpen: isToggled,
toggleMenu: toggle,
closeMenu,
}}>
{children}
</MenuContext.Provider>
)
}
export default MenuProvider
export const useMenu = () => {
return useContext(MenuContext)
}
so If true it will show the Menu if false it will show the Div where there a div
App.js
const { isMenuOpen } = useMenu()
//the providder
<MenuProvider>
<Header mode={theme} modeFunc={toggleTheme}/>
{isMenuOpen ? (
<Menu />
) : (
<Switch>
<Route path='/writing' component={Writings} />
<Route path='/meta' component={Meta} />
<Route path='/contact' component={Contact} />
<Route path='/project' component={Project} />
<Route exact path='/' component={Home} />
<Route path='*' component={NotFound} />
</Switch>
)}
<Footer />{' '}
</MenuProvider>
and when I add an onclick event the NavLink button of the menu to close it it does not work
Menu
const { closeMenu } = useMenu()
// return statement
{paths.map((item, i) => {
return (
<MenuItem
key={i}
link={item.location}
svg={item.icon}
path={item.name}
command={item.command}
onClick={closeMenu}
/>
)
})}
where did I go wrong
Issue
I suspect the issue is in App where you've a useMenu hook outside the MenuProvider used in App. This useMenu hook is using a MenuContext context but in the absence of a provider it instead uses the default initial context value.
const initialState = {
isMenuOpen: true,
toggle: () => {},
};
export const MenuContext = React.createContext(initialState);
export const useMenu = () => {
return useContext(MenuContext)
};
React.createContext
const MyContext = React.createContext(defaultValue);
Creates a Context object. When React renders a component that
subscribes to this Context object it will read the current context
value from the closest matching Provider above it in the tree.
The defaultValue argument is only used when a component does not
have a matching Provider above it in the tree. This default value can
be helpful for testing components in isolation without wrapping them.
Solution
Since I doubt you want to run/provide more than one menu provider I believe the solution is to move MenuProvider out of and wrap App to provide the context you are updating by nested components.
App.jsx
const { isMenuOpen } = useMenu();
...
<>
<Header mode={theme} modeFunc={toggleTheme}/>
{isMenuOpen ? (
<Menu />
) : (
<Switch>
<Route path='/writing' component={Writings} />
<Route path='/meta' component={Meta} />
<Route path='/contact' component={Contact} />
<Route path='/project' component={Project} />
<Route exact path='/' component={Home} />
<Route path='*' component={NotFound} />
</Switch>
)}
<Footer />
</>
index.jsx (?)
import App from './App.jsx';
...
//the provider
<MenuProvider>
<App />
</MenuProvider>

How to display a component when certain condition true using react and javascript?

The layout component is rendered on all pages.
I want to achieve the following
in /items page
*Layout component is displayed if the user is admin
* Layout component not displayed if the user is non-admin
below is my code,
function Main() {
const isAdmin = getUser();
return(
<Switch>
<Route
exact
path="/items"
render={routeProps => (
<Layout>
{isAdmin ? <Items {...routeProps} />: <NotFound/>}
</Layout>
)}
/>
<Route
exact
path="/home"
render={routeProps => (
<Layout>
<Home {...routeProps} />
</Layout>
)}
/>
</Switch>
);
}
const Layout: React.FC = ({ children }) => (
<>
<TopBar />
{children}
<BottomBar />
</>
);
As you see from the above code, the Layout component is displayed in all pages and is used as a wrapper for other routes too like for /home
Now I don't want the Layout component to be displayed only in /items page if a user is not admin
What I have tried?
const Layout: React.FC = ({ children }) => {
const history = useHistory();
const isItemsPath = history.location.pathname.includes('/items');
const isAdmin = getUser();
return (
<>
{!isItemsPath && <TopBar />
{children}
{!isItemsPath && <BottomBar />
</>
);
}
But this will not display TopBar and BottomBar if the items page even if the user is admin. how can I modify the condition
such that TopBar and BottomBar are displayed in all pages except items page if not admin.
could someone help me with this? thanks.
};
In your layout component you can use conditional rendering. We can check if the page is isItemsPath first, if it is items path and user is not admin then we do not show the Topbar and BottomBar, for all other pages we show them
const Layout: React.FC = ({ children }) => {
const history = useHistory();
const isItemsPath = history.location.pathname.includes('/items');
const isAdmin = getUser();
return !(isItemsPath && !isAdmin) ?
<>
{children}
</> : (
<>
<TopBar />
{children}
<BottomBar />
</>
);
}
What about you change the condition in Route?
<Route
exact
path="/items"
render={routeProps => (
{isAdmin ? <Layout>
<Items {...routeProps} />
</Layout>
: <NotFound/>}
)}
/>
If I understand correctly, you might be looking for something along the lines of this:
const Layout: React.FC = ({ children }) => {
const history = useHistory();
const isItemsPath = history.location.pathname.includes('/items');
const isAdmin = getUser();
return (
<>
{(!isItemsPath || isAdmin) && <TopBar />
{children}
{(!isItemsPath || isAdmin) && <BottomBar />
</>
);
Then you should be able to remove your isAdmin condition in your Main component.
You have some choice to doing this, but I think the best way is using HOC if you have to repeat checking the user is admin or not, pass your component to HOC and in HOC component check if a user is an admin or not. You can use this HOC component for all of your components. In HOC component use conditional rendering. Something like this :
function checkAdmin(WrappedComponent, selectData) {
const isAdmin = getUser();
render() {
return ({isAdmin} ? <WrappedComponent /> : <div></div>)
}
}

createSwitchNavigator with React Navigation v5?

I my old version I make something like this:
going to splash screen, if user is connected go to App, else go to login.
And I can navigate into screen by using this.props.navigation.navigate("Register")
SplashScreen.js :
componentDidMount(){
firebase.auth().onAuthStateChanged(user => {
this.props.navigation.navigate(user ? "App" : "Login")
});
}
in App.js
const Container = createAppContainer(
createSwitchNavigator(
{
Splash: SplashScreen,
Login: LoginScreen,
Register: RegisterScreen,
App: AppContainer,
},
{
initialRouteName: "Splash",
}
)
);
//Other code
render(){
return (<Container/>)
}
Now I try to use react Navigation v5 but everything seem to be more complicated.
My App.js look like this :
export default function App() {
const [isDarkTheme, setIsDarkTheme] = React.useState(false);
const theme = isDarkTheme ? CombinedDarkTheme : CombinedDefaultTheme; // Use Light/Dark theme based on a state
function toggleTheme() {
// We will pass this function to Drawer and invoke it on theme switch press
setIsDarkTheme(isDark => !isDark);
}
return (
<PaperProvider theme={theme}>
<NavigationContainer theme={theme}>
<Drawer.Navigator
drawerContent={props => <DrawerContent {...props} toggleTheme={toggleTheme}/>}
>
<Drawer.Screen
name="HomeDrawer"
component={MainTabScreen}
/>
<Drawer.Screen
name="SettingsScreen"
component={SettingsStackScreen}
/>
</Drawer.Navigator>
</NavigationContainer>
</PaperProvider>
);
}
How I'm suppose to do something like this but with PaperProvider ?
You could simply do your API Call and depending on the result, display or not the routes. Something like :
import { useEffect } from 'react';
export default function App() {
const [isDarkTheme, setIsDarkTheme] = React.useState(false);
const [isAuthed, setIsAuthed] = React.useState(false);
useEffect(async () => setIsAuthed(await getIsAuthed()));
const theme = isDarkTheme ? CombinedDarkTheme : CombinedDefaultTheme; // Use Light/Dark theme based on a state
function toggleTheme() {
// We will pass this function to Drawer and invoke it on theme switch press
setIsDarkTheme((isDark) => !isDark);
}
const defineRoutes = () => {
if (!isAuthed) {
return <Drawer.Screen name='Login' component={Login} /> // Don't know your login comp name, but you got the idea
}
// Don'tknow if Fragment exists on react native and is usable here, just try
return (
<Fragment>
<Drawer.Screen name='HomeDrawer' component={MainTabScreen} />
<Drawer.Screen name='SettingsScreen' component={SettingsStackScreen} />
</Fragment>
);
};
return (
<PaperProvider theme={theme}>
<NavigationContainer theme={theme}>
<Drawer.Navigator
drawerContent={(props) => (
<DrawerContent {...props} toggleTheme={toggleTheme} />
)}
>
{defineRoutes()}
</Drawer.Navigator>
</NavigationContainer>
</PaperProvider>
);
}

Right way to use react router v4 in the react application

I am new to the react js. I have gone through lots of the tutorials for using the react router.
So, the way I am using is like this
**index.js**
ReactDOM.render(<BrowserRouter>
<App />
</BrowserRouter>, document.getElementById('root'));
App.js
const store = configureStore()
class App extends React.Component {
render() {
return (
<Provider store={store}>
<div>
<Main />
</div>
</Provider>
)
}
Main.js
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';
import PrivateRoute from '../../privateRoute/component/PrivateRoute';
const LandingScreen = () => {
return (
<div>LandingScreen is theere</div>
)
}
const LoginComponent = () => {
return (
<div>LoginComponent</div>
)
}
export default class Main extends Component {
render() {
return (
<Router history={history}>
<Switch>
<PrivateRoute exact path="/" component={LandingScreen} />
<Route exact path="/login" component={LoginComponent} />
</Switch>
</Router>
)
}
}
In privateRoute.js
const PrivateRoute = ({ component: Component, isFetching, hasUserLogIn, path, ...rest }) => {
return localStorage.getItem("access_token") ?
(
<Route
{...rest}
path={path}
component={Component}
/>
)
:
(
<Redirect
to={{
pathname: "/login",
state: { from: path }
}}
/>
)
};
So this way I have added the routes in my project.
So, I am confused weather I am using it in the right way or not.
Can any one suggest me or help me with this ?
Your set up seems good! Only thing I would note is that you're using <Router> to wrap your routes in one file. And then nesting that same Router in another file with BrowserRouter . This seems a bit duplicate.
Regarding your localStorage issue. Try setting up your PrivateRoute like this
const PrivateRoute = ({ component: Component, auth, ...rest}) => {
return(
<Route
{...rest}
//route has a render prop that lets you create a component in-line with the route
render = {props =>
localStorage.getItem("access_token") ? (
<Component {...props} />
) : (
<Redirect to="/login"/>
)
}
/>
)
}

Pass props through a higher order component from a Route

I have a problem with my Higher Order Component. I am trying to pass props from a <Layout /> component down a route (React Router v4). The components specified in the routes are wrapped by a HOC, but the props that I pass never reaches the component.
Also, I can't use the HOC without using export default () => MyHOC(MyComponent). I can't figure out why, but that might have something to do with it?
Layout.js
const Layout = ({ location, initialData, routeData, authenticateUser }) => (
<Wrapper>
<Container>
<Switch>
// how do I get these props passed through the HOC? render instead of component made no difference.
<Route exact path="/pages/page-one" component={() => <PageOne routeData={routeData} title="PageOne" />} />
<Route exact path="/pages/page-two" component={() => <PageTwo routeData={routeData} title="PageTwo" />} />
<Route component={NotFound} />
</Switch>
</Container>
</Wrapper>
)
export default Layout
Page.js
// I've tried swapping to (WrappedComponent) => (props) without success
const Page = (props) => (WrappedComponent) => {
const renderHeader = props.header
? <Header title={props.headerTitle} />
: false
return (
<Wrapper>
{renderHeader}
<Container withHeader={props.header}>
<WrappedComponent />
</Container>
</Wrapper>
)
}
export default Page
PageOne.js
class PageOne extends React.Component {
render() {
return (
<Content>
<Title>{this.props.title}</Title> // <----- not working!
{JSON.stringify(this.props.routeData, null, 4)} // <---- not working!
</Content>
)
}
}
export default () => Page({ header: true, headerTitle: 'header title' })(PageOne)
// does not work without () => Page
// when using just export default Page I get the "Invariant Violation: Element type is invalid:
// expected a string (for built-in components) or a class/function (for composite components)
// but got: object. Check the render method of Route." error.
You need one more arrow to make your Page to be a HOC. It takes params, wrapped component and has to return a component. Yours were rendering after getting WrappedComponent
const Page = (props) => (WrappedComponent) => (moreProps) => {
const renderHeader = props.header
? <Header title={props.headerTitle} />
: false
return (
<Wrapper>
{renderHeader}
<Container withHeader={props.header}>
<WrappedComponent {...moreProps} />
</Container>
</Wrapper>
)
}
Now you can use it like this
export default Page({ header: true, headerTitle: 'header title' })(PageOne)

Categories