I need to do something like this
const CreateActivity = (props) => (
<AuthUserContext.Consumer>
{authUser =>
<CreateActivityShow email={authUser.email} {...props}/>
}
</AuthUserContext.Consumer>
const authCondition = (authUser) => !!authUser;
export default withAuthorization(authCondition)(CreateActivity);
this way I'm using my HOC component correctly with createActivity but on CreateActivityShow this.props only has this.props.email and not url parameters I should have with this.props.match...
I tried this way
export default props => (
<AuthUserContext.Consumer>
{authUser =>
<CreateActivityShow {...props} email={authUser.email}/>
}
</AuthUserContext.Consumer>
)
now I have the props, but I don't know how can I use my HOC here
is there a way to do both at same time?
edit:
I've tried this
export default withAuthorization(authCondition)( props => (
<AuthUserContext.Consumer>
{authUser =>
<CreateActivityShow {...props} email={authUser.email}/>
}
</AuthUserContext.Consumer>
))
Now I have again my component wrapped by withAuthorization, but props are not being passed now and I don't know why...
this is my HOC
const withAuthorization = (authCondition) => (Component) => {
class WithAuthorization extends React.Component {
componentDidMount() {
firebase.auth.onAuthStateChanged(authUser => {
if (!authCondition(authUser)) {
this.props.history.push(routes.SIGN_IN);
}
});
}
render() {
return (
<AuthUserContext.Consumer>
{authUser => authUser ? <Component /> : null}
</AuthUserContext.Consumer>
);
}
}
return withRouter(WithAuthorization);
}
export default withAuthorization;
Yeah, so the problem is in the WithAuthorization component where you are not passing the props received by HOC to the Component that is rendered. You would write it like
const withAuthorization = (authCondition) => (Component) => {
class WithAuthorization extends React.Component {
componentDidMount() {
firebase.auth.onAuthStateChanged(authUser => {
if (!authCondition(authUser)) {
this.props.history.push(routes.SIGN_IN);
}
});
}
render() {
return (
<AuthUserContext.Consumer>
{authUser => authUser ? <Component {...this.props}/> : null}
</AuthUserContext.Consumer>
);
}
}
return withRouter(WithAuthorization);
}
export default withAuthorization;
Related
I am working on a site that has a piece a global state stored in a file using zustand. I need to be able to set that state in a class component. I am able to set the state in a functional component using hooks but I'm wondering if there is a way to use zustand with class components.
I've created a sandbox for this issue if that's helpful:
https://codesandbox.io/s/crazy-darkness-0ttzd
here I'm setting state in a functional component:
function MyFunction() {
const { setPink } = useStore();
return (
<div>
<button onClick={setPink}>Set State Function</button>
</div>
);
}
my state is stored here:
export const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink }))
}));
how can I set state here in a class componet?:
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={
{
/* setPink */
}
}
>
Set State Class
</button>
</div>
);
}
}
A class component's closest analog to a hook is the higher order component (HOC) pattern. Let's translate the hook useStore into the HOC withStore.
const withStore = BaseComponent => props => {
const store = useStore();
return <BaseComponent {...props} store={store} />;
};
We can access the store as a prop in any class component wrapped in withStore.
class BaseMyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { setPink } = this.props.store;
return (
<div>
<button onClick={setPink}>
Set State Class
</button>
</div>
);
}
}
const MyClass = withStore(BaseMyClass);
Seems that it uses hooks, so in class you can work with the instance:
import { useStore } from "./store";
class MyClass extends Component {
render() {
return (
<div>
<button
onClick={() => {
useStore.setState({ isPink: true });
}}
>
Set State Class
</button>
</div>
);
}
}
Create a React Context provider that both functional and class-based components can consume. Move the useStore hook/state to the context Provider.
store.js
import { createContext } from "react";
import create from "zustand";
export const ZustandContext = createContext({
isPink: false,
setPink: () => {}
});
export const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink }))
}));
export const ZustandProvider = ({ children }) => {
const { isPink, setPink } = useStore();
return (
<ZustandContext.Provider
value={{
isPink,
setPink
}}
>
{children}
</ZustandContext.Provider>
);
};
index.js
Wrap your application with the ZustandProvider component.
...
import { ZustandProvider } from "./store";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<ZustandProvider>
<App />
</ZustandProvider>
</StrictMode>,
rootElement
);
Consume the ZustandContext context in both components
MyFunction.js
import React, { useContext } from "react";
import { ZustandContext } from './store';
function MyFunction() {
const { setPink } = useContext(ZustandContext);
return (
<div>
<button onClick={setPink}>Set State Function</button>
</div>
);
}
MyClass.js
import React, { Component } from "react";
import { ZustandContext } from './store';
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={this.context.setPink}
>
Set State Class
</button>
</div>
);
}
}
MyClass.contextType = ZustandContext;
Swap in the new ZustandContext in App instead of using the useStore hook directly.
import { useContext} from 'react';
import "./styles.css";
import MyClass from "./MyClass";
import MyFunction from "./MyFunction";
import { ZustandContext } from './store';
export default function App() {
const { isPink } = useContext(ZustandContext);
return (
<div
className="App"
style={{
backgroundColor: isPink ? "pink" : "teal"
}}
>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<MyClass />
<MyFunction />
</div>
);
}
If you aren't able to set any specific context on the MyClass component you can use the ZustandContext.Consumer to provide the setPink callback as a prop.
<ZustandContext.Consumer>
{({ setPink }) => <MyClass setPink={setPink} />}
</ZustandContext.Consumer>
MyClass
<button onClick={this.props.setPink}>Set State Class</button>
This worked out pretty well for me.
:
import React, { Component } from "react";
import { useStore } from "./store";
class MyClass extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div>
<button
onClick={
useStore.getState().setPink() // <-- Changed code
}
>
Set State Class
</button>
</div>
);
}
}
export default MyClass;
I like to create a high order component similar to redux connect:
function connectZustand(useStore, selector) {
return (Component) =>
React.forwardRef((props, ref) => <Component ref={ref} {...props} {...useStore(selector, shallow)} />);
}
eg:
import React, { Component } from 'react';
import create from 'zustand';
import shallow from 'zustand/shallow';
function connectZustand(useStore, selector) {
return (Component) =>
React.forwardRef((props, ref) => <Component ref={ref} {...props} {...useStore(selector, shallow)} />);
}
const useStore = create((set) => ({
isPink: false,
setPink: () => set((state) => ({ isPink: !state.isPink })),
}));
class MyClass extends Component {
render() {
const { setPink } = this.props;
return (
<div>
<button onClick={() => setPink()}>Set State Class</button>
</div>
);
}
}
const MyClassWithZustand = connectZustand(useStore, (state) => ({ setPink: state.setPink }))(MyClass);
export default function Test() {
const isPink = useStore((state) => state.isPink);
return (
<>
<MyClassWithZustand />
{isPink ? 'Is Pink' : 'Is Not Pink'}
</>
);
}
import { copilot, walkthroughable, CopilotStep } from 'react-native-copilot';
class DashboardContent extends Component {
state ={
secondStepActive: true
};
componentDidMount() {
this.props.start()
this.props.copilotEvents.on('stepChange', this.handleStepChange);
}
handleStepChange = step => {
console.log(`Current step is: ${step.name}`);
};
render() {
return (
<View> ...... <View/>
);
}
}
export default copilot({
animated: true,
overlay: 'svg',
})(DashboardContent);
I am using the react-native-copilot library for a walkthrough. I wish to trigger the this.props.start() function which starts the walkthrough using a button from my NavBar component - The _infoPage function in the code below should trigger the function basicaly.
The code for the Navbar is :
class NavBar extends Component {
state ={
isModalVisible: false,
email:'',
emailError: false,
emailErrorMessage: '',
};
_profileEdit() {
Actions.profileedit();
}
_notificationsPage(){
Actions.notifications();
}
_infoPage = () =>{
this.props.toggleTour();
}
toggleModal = () => {
this.setState({isModalVisible: !this.state.isModalVisible});
};
render() {
const {index, routes} = this.props.tabs;
console.log(index);
return (
<SafeAreaView>
<View style={styles.container}>
<StatusBar />
<TouchableOpacity onPress={this._infoPage}>
<MaterialCommunityIcons name="information-outline" size={24} color="#979797" style={{padding:10}}/>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
}
function mapStateToProps(state){
return {
tabs : state.tabs
}
}
function mapDispatchToProps(dispatch){
return {
changeCounter : (index) => dispatch({type:'PAGE_CHANGED',payload: index}),
toggleTour: () => dispatch({
type: 'TOUR_OPENED'
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NavBar);
I was thinking of putting this.props.start() inside a function and calling the function from another component.
How do I go about this?
Here is my App.js
export default class App extends Component {
render() {
return (
<Provider store = {store}>
<Routes />
</Provider>
);
}
}
The call for the NavBar is in Routes:
export default class Routes extends Component {
render() {
return (
<Router navBar={TopNavbar}>
<Scene key="root">
...
</Scene>
</Router>
);
}
}
You could pass the function as a prop in the other component, for example in your render function you could do the following:
render() {
return (
<View>
<YourComponent startTutorial={this.props.start}></YourComponent>
</View>
);
}
Then in YourComponent call this.props.startTutorial(); from a button or any event.
** Update
So in your case you can do the following:
export default class App extends Component {
render() {
return (
<Provider store = {store}>
<Routes /*here*/ startTutorial={this.props.start} />
</Provider>
);
}
}
export default class Routes extends Component {
/*here*/
CTopNavbar = ({ children }) => (
<TopNavbar startTutorial={this.props.startTutorial}>
{children}
</TopNavbar>
);
render() {
return (
<Router navBar={/*here*/CTopNavbar}>
<Scene key="root">
...
</Scene>
</Router>
);
}
}
Then in NavBar run this.props.startTutorial(); in your button.
I am trying to convert this class to a functional component but no luck. Can anyone
give me direction?
import { Redirect, Route, RouteProps } from 'react-router'
function mapStateToProps (state: AppState, ownProps: RouteProps): MappedProps {
const user = state.auth.user
return {
External: user ? user.External : true,
}
}
type InnerRouteProps = RouteProps & MappedProps
class MyInnerRoute extends React.Component<InnerRouteProps> {
render () {
const {
props: { External, component, render, children, ...rest },
} = this
return (
<Route
{...rest}
render={props => {
if (External) {
return <Redirect to={'/'} />
}
if (component) {
const Component = component
return <Component {...props} />
}
if (render) {
return render(props)
}
return children
}}
/>
)
}
}
export default connect(mapStateToProps)(MyInnerRoute)
It should look like this. Not sure where the const props will be here..
const MyInnerRoute = () => {
return (
<Route
{...rest}
render={props => {
if (External) {
return <Redirect to={'/'} />
}
if (component) {
const Component = component
return <Component {...props} />
}
if (render) {
return render(props)
}
return children
}}
/>
)
}
export default MyInnerRoute
Your props will come in through the function parameters, like so:
const MyInnerRoute = (props: InnerRouteProps) => {...
And then you can use them just like you did in your class before.
const { External, component, render, children, ...rest } = props
This should do the work.
const MyInnerRoute = ({ External, component, render, children, ...rest }) => {
return (
<Route
{...rest}
render={props => {
if (External) {
return <Redirect to={'/'} />
}
if (component) {
const Component = component
return <Component {...props} />
}
if (render) {
return render(props)
}
return children
}}
/>
)
}
export default MyInnerRoute
I have two <AuthConsumer>,<PeopleConsumer>
and It is belongs to HOC like this:
const withAuth = WrappedComponent => {
return () => (
<AuthConsumer>{props => { console.log(props); return <WrappedComponent auth={props} />}}</AuthConsumer>
);
};
Using like this is works I can get auth as a props.
export default withAuth(withRouter(LoginPage));
but, when I tired export default withPeople(withAuth(withRouter(LoginPage))); is not works I can't get auth, people as props.
So I looked up official document it says:
use like this to passing multiple props from contextAPI
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
So I tried this, but looks ugly:
const withTest = WrappedComponent => {
return () => (
<AuthConsumer>
{auth => (
<PeopleConsumer>
{people => (
<WrappedComponent auth={auth} people={people} />
)}
</PeopleConsumer>
)}
</AuthConsumer>
)
}
In my case is there are better way to providing multiple props?
Please let me know if you need info.
Thank you.
const withAuth = WrappedComponent => {
return (props) => (
<AuthConsumer>{auth => { console.log(props, auth); return <WrappedComponent {...props} auth={auth />}}</AuthConsumer>
);
};
Recently we got Context support in react.
Lets take next example:
<Consumer>{value => <Child value={value} />}</Consumer>
How do i make a component that sends "value" same way to its child?
I mean
<MyComponent>{value => ...}</MyComponent>
You make your component to use render props callback pattern like
class MyComponent extends React.Component {
state = {
value: 'abc'
}
render() {
return React.Children.only(this.props.children)(this.state.value)
}
}
and then you can use it like
<MyComponent>{value => ...}</MyComponent>
maybe a higher order component (HOC)?
function withContext(Component) {
return WithContext = (props) => (
<Consumer>
{
value => <Component {...props} value={value} />
}
</Consumer>
)
}
let MyComponent = ({ value }) => (
<div>
{value} // if value is something that can be rendered
</div>
)
MyComponent = withContext(MyComponent);
or with render props:
const MyComponent = (props) => (
<Consumer>
{value => props.children(value)}
</Consumer>
)
const example = (
<MyComponent>
{value => <div>{value}</div>} // children prop has to be function now
</MyComponent>
)