How to access Component state from <Component> body </Component> in other Component? - reactjs

I show a cool feature of react where Component state can be used from where it's body where it's being used.
Here is an example from firebase Link
<FirebaseDatabaseNode
path="user_bookmarks/"
limitToFirst={this.state.limit}
orderByValue={"created_on"}
>
{d =>
return (
<React.Fragment>
<pre>Path {d.path}</pre>
</React.Fragment>
);
}}
</FirebaseDatabaseNode>
In this example FirebaseDatabaseNode is Component and we're accessing d inside it.
I want to implement similar so I could access data of component in similar way. Here is my attempt to implement similar state access for Dashboard Component.
export default function Dashboard({
children,
user
}: {
children: ReactNode;
user: any
}) {
const { isOpen, onOpen, onClose } = useDisclosure();
const [selectedMenu, setSelectedMenu] = React.useState(LinkItems[DEFAULT_SELECTED_ITEM])
//...
}
And I want to access selectedMenu inside of Dashboard in index.js
export default function () {
return (
<Dashboard user={user}>
{selectedMenu => {
return (
<div>{selectedMenu}</div>
)
}
}
</Dashboard>
)
}
But this is not working in my case and I don't know the exact terminology.

Finally while I explore the firebase source I found out that they are using render-and-add-props Library.
Dashboard.js
import { renderAndAddProps } from 'render-and-add-props';
//...
export default function Dashboard({
children,
user
}: {
children: ReactNode;
user: any
}) {
const { isOpen, onOpen, onClose } = useDisclosure();
const [selectedMenu, setSelectedMenu] = React.useState(LinkItems[DEFAULT_SELECTED_ITEM])
//...
return (
<div>
//...
// for rendering element with props
{renderAndAddProps(children, { 'selectedMenu': selectedMenu })}
</div>
)
}
//in index
export default function () {
return (
<Dashboard user={user}>
{({selectedMenu}) => { // {({ selectedMenu }: { selectedMenu: LinkItemProps }) if you're using typescript.
return (
<div>{selectedMenu}</div>
)
}
}
</Dashboard>
)
}

Related

How to pass component constructor in React props?

How to pass component constructor in React props?
I'm a beginner for React. So below example may make it clear what I am trying to achieve.
Example:
PorkComponent:
const PorkComponent = ({ children, variant }: PorkComponentProps) => {
return (
<motion.div>
...
</motion.div>
);
};
DuckComponent:
const DuckComponent = ({ children, variant }: DuckComponentProps) => {
return (
<motion.div>
...
</motion.div>
);
};
The Lunch component will contains PorkComponent or DuckComponent Based on LunchProps.meatType.
type LunchProps = {
meatType: React.ComponentType;
};
const Lunch = (props: LunchProps) => {
return (
<props.meatType> // Here it will return PorkComponent or DuckComponent based on what props.meatType.
);
}
Sure I can use something like:
const Lunch = (props: LunchProps) => {
return (
if (props.isPork) {
< PorkComponent >
} else(props.isDuck) {
< DuckComponent >
}
....
);
}
But I don't want to have multiple IFELSE check in Lunch component. Instead, I want the caller to specify the "meat type", like <Lunch meatType= PorkComponent>.
I recently saw this someone shared on Twitter,
const MeatComponents = {
'pork': <PorkComponent />
'duck': <DuckComponent />
}
const Lunch = (props) => {
return MeatComponent[props.meatType]
}
// usage
<Lunch meatType='pork' />
<Lunch meatType='duck' />
Or you could just use,
const Lunch = ({ children }) => {
return <>{children}</>
}
// usage
<Lunch>
<PorkComponent />
</Lunch>
You can simply write :
const Lunch = (props: LunchProps) => <props.meatType />;
<Lunch meatType={DuckComponent} />
Here is an example on stackblitz
You can pass the rendered component directly instead:
// in Lunch component
return (<div>{props.meatType}</div>);
<Lunch meatType={<PorkComponent />} />
Playground
If you just want to return the given component, simply use:
return props.meatType;
you can pass component functions as props. You almost got the answer.
const Lunch = (props) => {
return (
// React.createElement(props.meatType, {}, null)
<props.meatType /> // this is valid, I have tested before replying.
);
}
// then
<Lunch meatType={PorkComponent} />
// or
<Lunch meatType={DuckComponent} />
// even this works and renders a div
<Lunch meatType="div" />

React Query - Client Provider in the _app.tsx using layouts [duplicate]

I have an auth context component where I'm wrapping my main app component, but at the same time I'm also trying to do page specific layout component per Next.js documentation here: https://nextjs.org/docs/basic-features/layouts#per-page-layouts
Am I doing this correctly, because I can't seem to be getting the data from my Context provider.
/context/AuthContext.js
const UserContext = createContext({});
export default function AuthContext({children}) {
// .. code
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
export const useUser = () => useContext(UserContext);
/_app.js
function MyApp({ Component, pageProps }) {
const getLayout = Component.getLayout || ((page) => page);
return getLayout(
<div>
<AuthContext>
<Component {...pageProps} />
</AuthContext>
</div>
);
}
export default MyApp;
/components/Project/List.js
import { useUser } from "../../context/AuthContext";
const ProjectList = () => {
const { user } = useUser();
console.log("get user data", user);
return (
<>
test
</>
);
};
export default ProjectList;
I'm trying to console log the user, but it's giving me undefined. I'm thinking it's because the way it's wrapped as a layout component? I could be doing this wrong. But I did console log inside my AuthContext for user, and the information there is correct.
/pages/projects/index.js
const Projects = () => {
// code goes here
return (
<div>
code goes here
</div>
)
}
export default Projects;
Projects.getLayout = function getLayout(page) {
return <ProjectLayout>{page}</ProjectLayout>;
};
When I remove the Projects.getLayout block of code, the data comes back, but when I add this code, data is gone.
/components/Project/Layout.js
const ProjectLayout = ({children}) => {
return (
<>
<ProjectList />
{children}
</>
}
export default ProjectLayout
With your current structure ProjectLayout isn't getting wrapped by the AuthContext, meaning you won't have access to its context.
You can modify your _app's structure and move the getLayout call around so that the context wraps it properly.
function MyApp({ Component, pageProps }) {
const getLayout = Component.getLayout || ((page) => page);
return (
<AuthContext>
{getLayout(<Component {...pageProps} />)}
</AuthContext>
);
}
Note that calling getLayout inside the Context could lead to errors if the getLayout function uses hooks or stuff that depends on a parent element.
It will be calling getLayout first and then the context, so the value of it will be initially the default (fallback) value (it's like doing foo(bar()) and expecting that foo will be called before bar).
To avoid this, return directly the component (using getLayout as a function that generates a component):
// /pages/projects/index.js
Projects.getLayout = (children) => (
// can't use hooks here, return the component immediately
<ProjectLayout>{children}</ProjectLayout>;
);
or use the layout as a component:
// /pages/projects/index.js
Projects.Layout = ({ children }) => {
return <ProjectLayout>{children}</ProjectLayout>;
};
// /pages/_app.js
export default function App({ Component, pageProps }) {
const Layout = Component.Layout || (({ children }) => children);
return (
<AuthContext>
<Layout>
<Component {...pageProps} />
</Layout>
</AuthContext>
);
}
Edit: The difference is more visible without JSX
// incorrect
return React.createElement(AuthContext, null,
// getLayout is called before AuthContext
getLayout(
React.createElement(Component, pageProps)
)
)
// correct
return React.createElement(AuthContext, null,
// Layout is called after AuthContext
React.createElement(Layout, null,
React.createElement(Component, pageProps)
)
)

What's wrong with the default value for a props of a functional component

I have no idea, what's wrong with writing the default value of props for a functional component in typescript:
Here is my code:
In Copywrite.tsx
interface Props {
siteName: string
webUrl: string
}
export const Copyright: React.FC<Props> = (
props: Props = { siteName: 'myWeb', webUrl: 'http://localhost:3000' }
) => {
return (
<div>
<p>{props.siteName}</p>
<p>{props.webUrl}</p>
</div>
)
}
In App.tsx
const App = (props: Props) => {
return (
<>
<Copyright />
</>
)
}
export default App
I got the error: Type '{}' is missing the following properties from type 'Props': siteName, webUrl TS2739
However, when I set the Props interface as:
interface Props {
siteName?: string
webUrl?: string
}
Then, my App.tsx won't show the props value on my web page.
What's wrong with my code?
Thanks very much for the help!
You need to pass the props to the component like so:
import React from 'react';
interface Props {
siteName: string;
webUrl: string;
}
export const App: React.FC = () => {
return <Copyright />;
};
export const Copyright: React.FC<Props> = (props) => {
return (
<div>
<p>{props.siteName}</p>
<p>{props.webUrl}</p>
</div>
);
};
Copyright.defaultProps = {
siteName: "myWeb",
webUrl: "http://localhost:3000"
};
The problem is that props isn't actually empty: React is supply an empty object for it.
You can destructure and set the default for each prop like below:
interface Props {
siteName?: string
webUrl?: string
}
export const Copyright: React.FC<Props> = (
{ siteName = 'myWeb', webUrl = 'http://localhost:3000' }
) => {
return (
<div>
<p>{siteName}</p>
<p>{webUrl}</p>
</div>
)
}

React Transfer internal prop of Parent to Child

<Parent><!-- has an internal prop 'json', is set from a fetch request -->
<div>
<div>
<Child /><!-- how can I send 'json here? -->
Do I have to use React context? I find it very confusing. After writing a component like that and looking back at the code I am just confused https://reactjs.org/docs/context.html
https://codesandbox.io/embed/bold-bardeen-4n66r?fontsize=14
Have a look at the code, context is not that necessary you can elevate data to parent component, update it and share it from there only.
For what I know, there are 3 or 4 alternatives:
1) Using context as you said, so declaring a provider and then consuming it with useContext() at the component where you need it. It may reduce reusability of components,
2) Lift state & props, among descendant components
const App = (props: any) => {
// Do some stuff here
return <Parent myProp={props.myProp}></Parent>;
};
const Parent = ({ myProp }: any) => {
return (
<div>
<Child myProp={myProp}></Child>
</div>
);
};
const Child = ({ myProp }: any) => {
return (
<div>
<GrandChild myProp={myProp}></GrandChild>{" "}
</div>
);
};
const GrandChild = ({ myProp }: any) => {
return <div>The child using myProp</div>;
};
export default App;
3) Using children:
const App = (props: any) => {
// Do some stuff here
return (
<Parent>
<GrandChild myProp={props.myProp}></GrandChild>
</Parent>
);
};
const Parent = (props: any) => {
return (
<div>
<Child>{props.children}</Child>
</div>
);
};
const Child = (props: any) => {
return <div>{props.children}</div>;
};
const GrandChild = ({ myProp }: any) => {
return <div>The child using myProp</div>;
};
4) Pass the GrandChild itself as a prop in the Parent, lifting it down to the proper Child and render it there. It's actually a mix of the previous 2 alternatives.
This is a simple example where you send the response through props to child. I used some sample (api) to demonstrate it.
------------------------Parent Component------------------------
import React, { Component } from "react";
import Axios from "axios";
import Child from "./Child";
let clicked = false;
class App extends Component {
state = {
parentResponse: ""
};
fetchAPI = () => {
Axios.get("https://jsonplaceholder.typicode.com/todos/1")
.then(res => {
if (res && res.data) {
console.log("Res data: ", res.data);
this.setState({ parentResponse: res.data });
}
})
.catch(err => {
console.log("Failed to fetch response.");
});
};
componentDidMount() {
this.fetchAPI();
}
render() {
return (
<div>
<Child parentResponse={this.state.parentResponse} />
</div>
);
}
}
export default App;
------------------------Child Component------------------------
import React, { Component } from "react";
class Child extends Component {
state = {};
render() {
return (
<div>
{this.props.parentResponse !== "" ? (
<div>{this.props.parentResponse.title}</div>
) : (
<div />
)}
</div>
);
}
}
export default Child;

Component in Component, React redux

Is it possible to call component in component (Like inception)
Example
Content.jsx
class Content extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.dispatch(fetchNav(this.props.match.params.tab));
}
componentDidUpdate(prevProps) {
if(this.props.match.params.tab != prevProps.match.params.tab) {
this.props.dispatch(fetchNav(this.props.match.params.tab));
}
}
render() {
const {nav, match} = this.props;
let redirect = null;
return (
<div>
<ul>
{nav.some((item, key) => {
if (this.props.location.pathname != (match.url + item.path)) {
redirect = <Redirect to={(match.url + item.path)} />;
}
return true;
})}
{redirect}
{nav.map((item, key) => {
return <li key={key}>
<Link to={match.url + item.path}>{item.name}</Link>
</li>;
})}
<Switch>
{nav.map((item, key) => {
return <Route key={key} path={`${match.url}/list/:tab`} component={Content} />;
})}
</Switch>
</ul>
</div>
);
}
}
const mapStateToProps = (state, props) => {
const {fetchNav} = state;
const {
lastUpdated,
isFetching,
nav: nav
} = fetchNav[props.match.params.tab] || {
isFetching: true,
nav: []
};
return {
nav,
isFetching,
lastUpdated
}
};
export default connect(mapStateToProps)(withStyles(appStyle)(Content));
Actually when i do this, if my route match and call same "Content" component, it says : "this.props.dispatch is not a function"
Do you think i need to create a ContentContainer that manage connect() and pass a method via props to manage change ?
Many thanks for you answers
Regards,
Thomas.
You are clearly not mapping dispatch to your props in your connect.
See the docs for redux' connect method:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
You can map the dispatch function to your props like like this:
const mapDispatchToProps = (dispatch) => ({ dispatch });
connect(mapStateToProps, mapDispatchToProps)...
Do you think i need to create a ContentContainer that manage connect() and pass a method via props to manage change ?
You don't need a container. The separation of code between a container and a (view-) component is just a pattern and not required.
As a sidenote: I would recommend to use compose to combine your HOCs, see this or this.

Resources