passing multiple props to component (context API) - reactjs

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>
);
};

Related

How to convert HOC to react custom hook

I have below code snippets, just wondering apart from passing a component to withGroupInput, do we have another way to re-use this GroupedInputWithLabel with different components? Thanks
export const GroupedInputWithLabel = (props) => {
const { required, children, fieldName } = props;
const inputComponent = (
<>
<ControlLabel htmlFor={fieldName} required={required} />
{children}
</>
);
return <GroupedInput {...props}>{inputComponent}</GroupedInput>;
};
export const withGroupInput = (props, Component) => (
<GroupedInputWithLabel {...props}>
<Component {...props} />
</GroupedInputWithLabel>
);

How to use the useHook in the component rather than passing the value returned from it as a prop to the components using react and typescript?

i want to use useHook within the component itself rather than passing it as a prop to each component using react and typescript.
what i am trying to do?
I have a useHook named useRefresh which returns isLoading state. This isLoading state is used to display a loading indicator in each of the pages.
so i have three pages and whenever this isLoading is true should display a loading indicator in these pages.
below is my code,
function App(){
const user = useGetUser();
return (
<Router>
<Switch>
<Route
path="/"
render={props: any => (
user ? (<Main {...props} />) : (
<LoginPage/>
);
)}
</Route>
</Switch>
</Router>
);
}
export function useLoad() {
const { refetch: refetchItems } = useGetItems();
const { refetch: refetchOwnedItems } = useListOwnedItems();
return async function() {
await refreshCompany();
refetchItems();
refetchOwnedItems();
};
}
function useAnother(Id: string) {
const [compId, setCompId] = React.useState(undefined);
const [isLoading, setIsLoading] = React.useState(false);
const comp = useCurrentComp(Id);
const load = useLoad();
if (comp && comp.id !== compId) {
setCompId(comp.id);
const prevCompId = compId !== undefined;
if (prevCompId) {
setIsLoading(true);
load().then(() => {
setIsLoading(false);
});
}
}
}
function Main ({user}: Props) {
useAnother(user.id);
return (
<Router>
<Switch>
<Route
path="/"
render={routeProps => (
<FirstComp {...routeProps} />
)}
/>
<Route
path="/items"
render={routeProps => (
<SecondComp {...routeProps} />
)}
/>
//many other routes like these
</Switch>
</Router>
);
}
function FirstComp () {
return(
<Wrapper>
//some jsx
</Wrapper>
);
}
function SecondComp () {
return(
<Wrapper>
//some jsx
</Wrapper>
);
}
Now i want to pass isLoading state to each of the components in Main component....so i have passed it like below,
function Main ({user}: Props) {
const isLoading = useAnother(user.id); //fetching isLoading here from useHook
return (
<Router>
<Switch>
<Route
path="/"
render={routeProps => (
<FirstComp isLoading={isLoading} {...routeProps} />
)}
/>
<Route
path="/items"
render={routeProps => (
<SecondComp isLoading={isLoading} {...routeProps} />
)}
/>
//many other routes like these
</Switch>
</Router>
);
}
function FirstComp ({isLoading}: Props) {
return(
<Wrapper>
displayIndicatorWhen(isLoading);
//some jsx
</Wrapper>
);
}
function SecondComp ({isLoading}: Props) {
return(
<Wrapper>
displayIndicatorWhen(isLoading);
//some jsx
</Wrapper>
);
}
This works. but doesnt seem like a right approach to me.. i dont want to pass this isLoading state as a prop to each of these components. there are more than 10 of them.
is there someway that i can do it other way than this. could someone help me with this. thanks.
The most common solution is to create a context that wraps the entire tree of components. This context holds the state that your hook pulls in
////LoadingContext.tsx
const LoadingContext = createContext();
const LoadingContextProvider = () => {
const [isLoading, setIsLoading] = useState(false);
return (
<LoadingContextProvider.Provider
value={{
isLoading,
setIsLoading
}}
/>
)
}
export const useLoading = () => useContext(LoadingContext);
You need to wrap the context around anything that will be calling useLoading:
import { LoadingContextProvider } from './LoadingContext' //or wherever this is relative to Main.tsx
<LoadingContextProvider>
<Router>
...(router stuff)
</Router>
</LoadingContextProvider>
Now you can call useLoading in your lower-level components.
//in another file defining a lower-level component:
import { useLoading } from '../../LoadingContext' //or wherever the context stuff is relative to this component definition
const FirstComp = () =>
const [isLoading, setIsLoading] = useLoading();
const handleClick = () => {
setIsLoading(true);
callMyApi().then(() => setIsLoading(false));
}
if(isLoading){
return <LoadingGif />
}
else{
return <div onClick={handleClick}>Click me!</div>
}
)}
What you would like to accomplish here is called global state. There are many ways to do it, but I think the simplest is the native React Context API.
All you have to do is create a ContextProvider and then use the useContext hook inside your components to access the values it provides.
Here is an example that should work for your case:
Main.js
export const LoadingContext = React.createContext(true); //creating and exporting the context
function Main ({user}: Props) {
const isLoading = useAnother(user.id); //fetching isLoading here from useHook
return (
<LoadingContext.Provider value={isLoading}> {/* providing the value to the children */}
<Router>
<Switch>
<Route
path="/"
render={routeProps => (
<FirstComp {...routeProps} />
)}
/>
<Route
path="/items"
render={routeProps => (
<SecondComp {...routeProps} />
)}
/>
//many other routes like these
</Switch>
</Router>
</LoadingContext.Provider>
);
}
export default Main;
Other components
import {LoadingContext} from './Main.js'
function FirstComp ({}: Props) {
const isLoading = useContext(LoadingContext); //accessing the value
return(
<Wrapper>
displayIndicatorWhen(isLoading);
//some jsx
</Wrapper>
);
}
function SecondComp ({}: Props) {
const isLoading = useContext(LoadingContext); //accessing the value
return(
<Wrapper>
displayIndicatorWhen(isLoading);
//some jsx
</Wrapper>
);
}

react-router-dom: How do i get url parems passed, while passing other data?

I would like to have my Clubs get data straight from its parent. In the example below. other props such as data and setData are both available. but not id which should be given by the path.
<AuthRoute path="/club/:id">
<Club data={data} setData={setData} />
</AuthRoute>
const AuthRoute = ({ children, ...props }) => {
const { isAuthenticated } = useAuth();
return (
<Route {...props}>
{isAuthenticated ? children : <Redirect to="/login" />}
</Route>
);
};
export const Club = (props) => {
console.log(props);
return <div>Hello World</div>;
};
I used useParems function in Club that worked.
const { id } = useParams();
You should be able to get the id in Club component by
export const Club = (props) => {
let club_id = props.match.params.id;
console.log('ClubId is::',club_id);
return <div>Hello World</div>;
};

How can you pass props and use HOC with React context

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;

How to pass data to child without specifying it in props

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>
)

Resources