Cannot get redux state inside the provider file - reactjs

I'm trying to get the redux state value in the same file as where I use the provider.
For some reason it seems it cannot find the value.
const MyApp = ({ Component, pageProps }: AppProps)=> {
const isDark = useSelector<ThemeState, ThemeState["isDark"]>(state => state.isDark)
const dispatch = useDispatch()
return (
<>
<Provider store={ThemeStore}>
<div className={isDark ? 'dark' : 'white'}>
<Player />
<Component {...pageProps} />
</div>
</Provider>
</>
)
}
export default MyApp
This gives an error:
Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>
When using the same useSelector and dispatch inside my nav component, it does work.
Any idea how I could make it work inside the _app.js file?

You can not use state in the provider like that, you need to go at least one layer deeper or just use what you're passing to the state directly not from calling to useSelector, try this:
function Child({ children }) {
const isDark = useSelector<ThemeState, ThemeState["isDark"]>(state => state.isDark)
return <div className={isDark ? "dark" : "white"}>{children}</div>;
}
const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<Provider store={ThemeStore}>
<Child>
<Player />
<Component {...pageProps} />
</Child>
</Provider>
);
};
export default MyApp;

Related

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

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.

Reactjs update context from children component

Starting with ReactJS and NextJS
I have the following Layout component and using context to set values from the children component
export interface AuthContextModel {
title: string;
description: string;
showSignup: boolean;
}
export const AuthContext = createContext(null);
export const AuthLayout = ({children}) => {
const [authContext, setAuthContext] = useState<AuthContextModel>();
return (
<>
<Head>
<title>Authentication</title>
<HeadComponent/>
</Head>
<AuthContext.Provider value={setAuthContext}>
<h4>{authContext?.title}</h4>
<p className="text-muted mb-4">Sign in to continue to Chatvia.</p>
{children}
</AuthContext.Provider>
</>
)
}
And the Login page extends the Layout
export default function Login(props) {
const setAuthContext = useContext(AuthContext);
useEffect(() => {
setAuthContext({
title: 'Sign In'
})
}, [])
return (
<AuthLayout>
<form onSubmit={handleSubmit}>
...
</form>
</AuthLayout>
)
}
But the setAuthContext in the Login function is giving the following error
TypeError: setAuthContext is not a function
How can I update the context variable from the children component?
When you mount the Login, that component is not a child of AuthLayout. As a result, Login will not gain access to AuthContext.
If you wanted Login to have access to AuthContext, it would need to be mounted something like this:
<AuthLayout>
<Login />
</AuthLayout>
Right now, you're mounting it like this:
<AuthLayout>
{/* Something else */}
</AuthLayout>
<Login />
The consumer (Login) is not a child of the provider (AuthLayout), so the consumer currently can't get anything from that context unless it is a child of the provider.
https://reactjs.org/docs/context.html

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