How to update context state variable rendering across screens in React native? - reactjs

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.

Related

Cannot Pass State by React-native Context API

I try to pass State and SetState by using useContext.
But there someting strange: only one can pass to children compoenents.
I want to pass {isLogin, setIsLogin, name}, only name can show correctly like this:
also can run in codesandbox
codesandbox
import React, { useState, createContext, useContext } from "react";
import { Text, View, Button } from "react-native";
const AppContext = createContext();
const AppProvider = (props) => {
const [isLogin, setIsLogin] = useState(false);
const [name, setName] = useState("molly");
return (
<AppContext.Provider value={(isLogin, setIsLogin, name)}>
{props.children}
</AppContext.Provider>
);
};
const App = () => {
return (
<AppProvider>
<Home />
</AppProvider>
);
};
const Home = () => {
const storedEvents = useContext(AppContext);
console.log("storedEvents", storedEvents);
const login = () => {
storedEvents.setIsLogin(true);
};
if (!storedEvents.isLogin) {
return (
<View>
<View>
<Text>{storedEvents}</Text>
</View>
<Button title="login" onPress={() => login()} />
</View>
);
}
return (
<View>
<Text>I am Login.</Text>
</View>
);
};
export default App;
You can pass multiple values as the JSON object, so your AppProvider should look something like this.
const AppProvider = (props) => {
const [isLogin, setIsLogin] = useState(false);
const [name, setName] = useState("molly");
return (
<AppContext.Provider value={{isLogin, name, setIsLogin}}>
{props.children}
</AppContext.Provider>
);
};
Notice the change value={{isLogin, name, setIsLogin}} instead of value={(isLogin, name, setIsLogin)}
There's another issue in the Home component which will give render error. You are trying to display storedEvents object which is not allowed. So change it to text string like Not logged in or something.

useContext API State doesn't pass another component

I am trying to pass a state to another component but it doesn't work. Here is my app.js file. Can someone please advise me on what's wrong here?
I'm creating the userContext in App.js and I want to use it in other files. In this code, I'm trying to use the userContext in the Navbar component.
function App() {
const [login, setLogin] = useState(0);
return (
<userContext.Provider value={[setLogin, login]}>
<BrowserRouter>
<Navbar />
<Routes>
{...routes}
</Routes>
</BrowserRouter>
</userContext.Provider>
);
}
export default App;
const Navbar = () => {
const [login, setLogin] = useContext(userContext);
const logout = () => {
setLogin(0);
};
if (login) {
return <button onClick={() => setLogin(0}>Logout</button>
}
return <button onClick={() => setLogin(1}>Login</button>
};
You are setting the context value in the App.js as:
<userContext.Provider value={[setLogin, login]}>
And when you are using the context value you are destructuring it in different order:
const [login, setLogin] = useContext(userContext);
Which means that your setLogin function and login state get mixed up. Change the oder of the elements in the provider to:
<userContext.Provider value={[login, setLogin]}>

Passing location and pageContext from page to child components in Gatsby

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.

Prevent rerender of sibling component which initiates a useState in wrapper component

I am not very experienced with React but I have a very simple Setup.
export default function App() {
const [title, setTitle] = useState("still-empty");
const myFunction = title => {
setTitle(title);
};
return (
<div className="App">
<ComponentA myFunction={myFunction} />
<br />
<br />
<ComponentB title={title} />
</div>
);
}
const ComponentA = ({ myFunction }) => {
console.log("Rendering Component A");
return (
<div onClick={() => myFunction(Math.random() * 1000)}> Component A </div>
);
};
export default ComponentA;
const ComponentB = ({ title }) => {
return <div> Title : {title}</div>;
};
export default ComponentB;
Here is a sandbox to test this: https://codesandbox.io/s/musing-cookies-g7szr
See that if you click on "ComponentA", that exact ComponentA gets rerendered (you can see it in console) although no props are changed on this component. This is a simplified example of my real use case. In my real use case, ComponentA is a map where a lot of stuff (zoom, center)
will be reset. I want to prevent these resets and also the 1 second it takes for rerendering. Therefor I present this simplified example.
So how do I pass an information from ComponentA to ComponentB, without rerendering ComponentA itself? Thanks for helping out here.
use useCallback in Parent so that the function is not created again and again but only on initial render.
use React.memo so that when no props are changed the component wont re-render.
App
export default function App() {
const [title, setTitle] = useState("still-empty");
const myFunction = useCallback(title => {
setTitle(title);
}, []);
return (
<div className="App">
<ComponentA myFunction={myFunction} />
<br />
<br />
<ComponentB title={title} />
</div>
);
}
ComponentA
import React, { memo } from "react";
const ComponentA = ({ myFunction }) => {
console.log("Rendering Component A");
return (
<div onClick={() => myFunction(Math.random() * 1000)}> Component A </div>
);
};
export default memo(ComponentA);
Working demo is here:
https://codesandbox.io/s/affectionate-boyd-v7g2t?file=/src/App.js

How to access props in a functional HOC?

I have code similar to the following:
const HOC = (props, WrappedComponent) => {
return (
<>
<WrappedComponent />
<Icon className="props.iconStyles" />
</>
);
};
This is not actually valid code, but you can hopefully see what I'm trying to accomplish. I want to be able to use it in the following way:
<HOC iconStyles="icon-stuff">
<TheComponentIWantToWrap>
</HOC>
How can I write this correctly, so as to be able to pass props through? I think I might need to be using children here too, not sure.
It would be something like this.
const HOC = (WrappedComponent) => {
const MyComponent = props => {
return (
<>
<WrappedComponent {...props} />
<Icon className={props.iconStyles} />
</>
);
}
return MyComponent;
};
An HOC is a funtion which returns a Component and usually inject some props on it. You should separate concerns. An actual HOC should look like this
const withFoo = Component =>{
return props =>{
return <Component injectedProps='foo' />
}
}
And be called like this
const Component = withFoo(({ injectedProps }) =>{
return <jsx />
})
If you want to merge arbitrary props as well try to spread the props passed to Component
const withFoo = Wrapped =>{
return props =>{
return <Wrapped injectedProps='foo' {...props}/>
}
}
<Component propsToBeSpreaded='bar' />
If you prefer you can create an aditional layer.
HOC injects some props in a Container
Container accepts arbitrary props
Container renders children
The code
const withHoc = Container =>{
return () => <Container injected='foo' />
}
const Container = withHoc(({ children, injected, ...aditionalProps}) =>{
return(
<div {...aditionalProps}>
{ children }
</div>
)
})
And call it like
const FinalComp = () =>{
return <Container foo='foo'> <span /> </Container>
}
A higher-order component would return another component (another function in this case). A component is a function that returns JSX.
const HOC = (Component) => {
return function WrappedComponent(props) {
return (
<>
<Component {...props} />
<Icon className="props.iconStyles" />
</>
);
};
};
You could still pass down props along with a Component to be enhanced (as per your original approach which you think is wrong) It is right since -
HOCs are not part of React API. They emerge from React's nature of composition.
So your HOC usage is -
const EnhancedComponent = higherOrderComponent(WrappedComponent, anyProps);
Points to note:
Your HOC takes in a Component and returns a Component (enhanced or not)
const higherOrderComponent = (WrappedComponent, anyProps) => {
return class extends React.Component {
// Component body
}
}
Don’t Mutate the Original Component. Use Composition.
Pass Unrelated Props Through to the Wrapped Component.
const higherOrderComponent = (WrappedComponent, anyProps) => {
return class extends React.Component {
render() {
const { HOCProp, ...compProps } = this.props;
return (
<WrappedComponent
...compProps,
someProp={anyProps.someProp}
/>
);
}
}
}
Considering all this, your HOC would look like this -
const withWrappedIcon = (CompToWrap, iconStyle) => {
return (
<Icon className={iconStyle}>
<CompToWrap />
</Icon>
);
}

Resources