Ok i got components imported as
import Payment from './pages/payment';
import Chat from './pages/chat';
Now I am using Drawer component and using it together with Navigator my renderScene become something like this
if( route.id == 'payment'){
return <Drawer xx={} yy={} and a lot more >
<Payment navigator={navigator} />
</Drawer>
}
if(route.id == 'chat'){
return <Drawer xx={} yy={} and a lot more >
<Chat navigator={navigator} />
</Drawer>
}
Those lengthy Drawer code are being used again and again. I want to store that <Payment navigator={navigator} > or the other into a variable and then return that with Drawer only once.
How can i store it and return it with Drawer?
Thanks
Not sure if you are asking this but what about something like:
const routes = {
payment: Payment,
chat: Chat
...
}
And then, just:
const Scene = routes[route.id];
return (
<Drawer>
<Scene navigator={navigator}/>
</Drawer>
)
Here you have 3 options:
// 1. Group the drawer props in an object
const drawerProps = {
xx: ...,
yy: ...
};
<Drawer {...drawerProps}>
<Chat navigator={navigator} />
</Drawer>
// 2. Define a wrapper object that populates the common Drawer props
const CustomDrawer = ({ children }) => (
<Drawer xx={} yy={} and a lot more>
{children}
</Drawer>
);
// 3. Define a wrapper object that populates the common Drawer props with default props. (Can be
// overriden.)
const CustomDrawer = ({
xx='XX',
yy='YY',
children
}) => (
<Drawer xx={xx} yy={yy} and a lot more>
{children}
</Drawer>
);
EDIT: I missunderstood your question, for storing the inner part you just have to assign it to a varible and use it.
const routes = {
chat: <Chat navigator={navigator} />,
payment: <Payment navigator={navigator} />,
}
<Drawer {...drawerProps}>
{ routes[route.id] }
</Drawer>
I propose this solution with a React hook (React v16.8+).
The useMemo returns a component according to the route agument passed to the switch. The useMemo is updated each time one of the internal variables (passed as a second argument as route) is updated.
import React, { useState, useMemo } from 'react';
export default function App ({
route,
navigator
}) {
const [route, setRoute] = useState('payment');
const mainContent = useMemo(() => {
return () => {
switch (route) {
case 'payment':
return (
<Payment navigator={navigator} />
);
case 'chat':
return (
<Chat navigator={navigator} />
);
}
}
}, [route, navigator])
return (
<Drawer xx={} yy={} and a lot more >
{ mainContent() }
</Drawer>
);
}
Related
new to React Native.
I'm using Context hook state to use an array in two tab screens of an application.
One of the screens displays the context array in the form of a FlatList, the other one inserts items into the array. What would be the proper way to rerender when the array changes?
Here's the code:
APP.JS
const App = () => {
const Tab = createBottomTabNavigator();
return(
<NavigationContainer>
<Tab.Screen name="HomeScreen" component={Home} />
<Tab.Screen name="DiscoverScreen" component={Discover} />
</NavigationContainer>
);
}
export default function AppWrapper() {
return (
<AuthProvider>
<App />
</AuthProvider>
);
}
AUTHCONTEXT.JS
const AuthContext = createContext()
const AuthProvider = ({ children }) => {
const [array, setArray] = useState([]);
return (
<AuthContext.Provider value={{ array, setArray }}>
{children}
</AuthContext.Provider>
)
}
export {AuthContext, AuthProvider}
HOME.JS
export default Home = () => {
const {array} = useContext(AuthContext);
return(
<View>
<FlatList
data={array}
renderItem={(item)=>{
return(
<Text>{item}</Text>
)}}
/>
</View>
)
}
DISCOVER.JS
export default Discover = () => {
const {setArray} = useContext(AuthContext);
setArray((currentArray)=>[...currentArray,'test']);
return(
<View></View>
);
}
You shouldn't invoke setArray unconditionally in Discover function component. If you need to add value to the array when component appears, use useEffect hook (and don't forget to setup second argument, probably it will be empty array, otherwise you get into infinite loop and component crash with an error)
export default Discover = () => {
const {setArray} = useContext(AuthContext);
useEffect(() => {
setArray((currentArray)=>[...currentArray,'test']);
}, [])
return(
<View></View>
);
}
And you don't need to worry about rerender the component, React handle this for you. Once setArray invokes it automatically rerender all component that use AuthContext and their children.
<SimpleDialog
selectedValue={selectedValue}
open={open}
onClose={handleClose}
title={title}
componentBody={componentBody}
/>
I would like to pass the '' handleclose '' to the component (componentBody), but that component comes to me as '' props '', is there a way to do that?
If you want to pass a component to another component you should use high order components or HOC, and wrap children components like layouts, like below:
function App() {
return (
<div className='App'>
<HOC message={"from App"}>
<ComponentB name='Reza' func={() => alert("alert message")} />
</HOC>
</div>
);
}
const ComponentB = ({ name, func }) => {
return <h1 onClick={func}>name is : {name}</h1>;
};
const HOC = ({ children, message }) => {
return (
<>
{message}
<br />
{children}
</>
);
};
export default App;
In your codebase you can do it like below:
<SimpleDialog
selectedValue={selectedValue}
open={open}
title={title}
>
<componentBody onClose={handleClose}/>
</SimpleDialog>
I want to change with a toggle in navbar which theme the app will apply, I can update normally the context with the consumer and provider of defaultTheme, but my app didn't update this information.
I've console logged some components to see if they're receiving my context updates, and all is normal, but in my App.tsx, the context only send the first state, and all updates isn't received by it
context.js
const Context = createContext({
defaultTheme: dark,
toggleTheme: () => {},
});
export function ThemeContextProvider({ children }) {
const [theme, setTheme] = useState(dark);
function toggleTheme() {
setTheme(theme === dark ? light : dark);
}
return (
<Context.Provider value={{ defaultTheme: theme, toggleTheme }}>
{children}
</Context.Provider>
)
}
export function useTheme() {
const theme = useContext(Context)
return theme;
}
App.tsx
function App() {
const { defaultTheme } = useTheme();
return (
<ThemeContextProvider>
{defaultTheme.title === 'dark' ? (
<ThemeProvider theme={dark}>
<GlobalStyle />
<Routes />
</ThemeProvider>
) : (
<ThemeProvider theme={light}>
<GlobalStyle />
<Routes />
</ThemeProvider>
) }
</ThemeContextProvider>
);
}
Navbar.tsx
const { colors } = useContext(ThemeContext);
const { defaultTheme, toggleTheme } = useTheme();
return (
<div id='navbar'>
<div className='navbar-container'>
<div className='theme-switcher'>
{ defaultTheme.title === 'dark' ? <RiMoonClearFill /> : <RiMoonClearLine />}
<Switch
onChange={toggleTheme}
checked={defaultTheme.title === 'light'}
checkedIcon={true}
uncheckedIcon={false}
height={10}
width={40}
handleDiameter={20}
offHandleColor={colors.main}
onHandleColor={colors.text}
offColor={colors.background}
onColor={colors.main}
/>
{ defaultTheme.title === 'light' ? <FaSun /> : <FaRegSun />}
</div>
...
App.tsx is not wrapped within ThemeContextProvider so you cant access that context value inside App.tsx.
Its context value is only accessible to children components where ThemeContextProvider is wrapped around.
So i suggest you to move this whole chunk to a new component and call useTheme() inside that child component.
<ThemeProvider theme={defaultTheme.title === 'dark' ? dark : light}>
<GlobalStyle />
<Routes />
</ThemeProvider>
And i have made changes to your conditional rendering to make to more compact and readable.
My page layout for a Gatsby site looks like this.
const Container = ({location, children, pageContext}) => {
return (
<>
<Header location={location} />
<Breadcrumbs pageContext={pageContext} />
{children}
<Footer />
</>
)
}
I need to pass location and pageContext from the page to the child components. I have tried to add location and pageContext to the DataProvider like this:
export const DataContext = React.createContext();
const DataProvider = ({ children, location, pageContext }) => {
return (
<DataContext.Provider value={{
location,
pageContext
}}>
{children}
</DataContext.Provider>
)
};
export default DataContext
export { DataProvider }
Then I use DataProvider in gatsby-ssr.js and gatsby-browser.js like this:
export const wrapRootElement = ({ element }) => (
<ThemeProvider theme={theme}>
<DataProvider>
{element}
</DataProvider>
</ThemeProvider>
);
In the child component:
const HeaderLinks = () => {
return (
<DataContext.Consumer>
{
context => (
<Menu
theme="light"
mode="horizontal"
selectedKeys={[context.location.pathname]}
>
<Menu.Item key={key}>
<Link to={url}>{name}</Link>
</Menu.Item>
</Menu>
)
}
</DataContext.Consumer>
)
}
But it doesn't seem to work, as it is not getting updated when I move to another page. (I also have wrapPageElement with Container, may be that's reasons.)
How can I pass location and pageContext to the child components? Is it better to use React Context or simply pass them as props? If I should use React Context, how can I correct my code to make it work?
Instead of using wrapRootElement to use ContexProvider you can make use of wrapPageElement where you can get the page props and pass them on to the DataProvider. This will make sure that pageContext and location change on each page
export const wrapRootElement = ({ element }) => (
<ThemeProvider theme={theme}>
{element}
</ThemeProvider>
);
export const wrapPageElement = ({ element, props }) => (
<DataProvider value={props}>
{element}
</DataProvider>
);
export const DataContext = React.createContext();
const DataProvider = ({ children, value }) => {
const {location, pageContext} = value;
return (
<DataContext.Provider value={{
location,
pageContext
}}>
{children}
</DataContext.Provider>
)
};
export default DataContext
export { DataProvider }
I ended up using useLocation from #reach/router to return location in child components. And I simply pass pageContext as a prop to <Breadcrumbs />, as it is used only once and is not passed down to any child components.
<ThemeContext.Consumer>
{({theme, toggleTheme}) => (
<button
onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
I got the above code from here.
Is there an alternative way of writing the second line? The curly brackets mixed with parentheses is kind of weird in my opinion, and I'd like to know if there's an alternative way of writing it.
useContext
You can use useContext hook, which lets you read the context and subscribe to its changes, and which is really convenient when you want to subscribe to the changes of multiple contexts:
function ThemeTogglerButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
const { ... } = useContext(AnotherContext);
return (
<button
onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
);
}
useContext
HOC
The second approach is to create an HOC, which lets you inject the context as props to your components:
withTheme.jsx (HOC) :
export const withTheme = Component => props => (
<ThemeContext.Consumer>
{contextProps => <Component {...props} {...contextProps} />}
</ThemeContext.Consumer>
);
ThemeTogglerButton.jsx:
import { withTheme } from "./withTheme"
const ThemeTogglerButton = ({ theme, toggleTheme }) => {
return (
<button onClick={toggleTheme} style={{ backgroundColor: theme.background }}>
Toggle Theme
</button>
);
}
export default withTheme(ThemeTogglerButton);
App.jsx:
import ThemeTogglerButton from "./ThemeTogglerButton";
const App = () => (
<ThemeContext.Provider value={...}>
<ThemeTogglerButton />
</ThemeContext.Provider>
)