covert global token (function to class component) - reactjs

I am trying to convert a function into a class component. The following code was part of the main function as
Const ProductDetail = () => {
const {
token: { colorBgContainer },
} = theme.useToken();
const { Content } = Layout;
const navigate = useNavigate();
return ( <Layout>
<Content> ....
This has been converted into the following.
class ProductDetail extends Component {
constructor(props) {
super(props);
...
}
Render() {
return ( <Layout>
<Content> ....
..); } } export default withRouter(ProductDetail);
However, I dont know how to convert the following. How do I transform this?
const {
token: { colorBgContainer },
} = theme.useToken();
const { Content } = Layout;

Antd token is provided through a hook so the only way to get it is using function component. In your case, you need to convert back your class component to function or create a HOC to wrap your component and provide token as props
const withToken = (Component) => {
return () => {
const {
token: { colorBgContainer },
} = theme.useToken();
return <Component token={token} />;
};
};
export default withToken;
And define Content outside of Component. Your code will be
const { Content } = Layout;
class ProductDetail extends Component {
constructor(props) {
super(props);
//...
}
render() {
const { token } = this.props;
return (
<Layout>
<Content>
...
</Content>
</Layout>
);
}
}
export default withRouter(withToken(ProductDetail));

Related

Value of context is undefiend

I'm a newbie in react, and I'm struggling with something that should be easy.
When a user logs in, I want to save their context and display/hide things on the nav bar.
However, the value of the context is undefined when I pass it to children.
A guide to Context can be found here
Here is my code src/App.js:
export default class App extends Component {
static displayName = App.name;
render() {
return (
<Layout>
<Route exact path='/' component={Login} />
<Route path='/fetch-data' component={FetchData} />
</Layout>
);
}
}
src/userContext:
import React from 'react';
const userContext = React.createContext({ user: {} });
export { userContext };
src/components/Layout.js:
import { userContext } from '../userContext';
export class Layout extends Component {
static displayName = Layout.name;
constructor(props) {
super(props);
this.state = {
user: { 'test123': 'test456' }
};
this.logout = this.logout.bind(this);
}
componentDidMount() {
// get and set currently logged in user to state
}
logout() {
this.setState({ user: {} });
}
render() {
const value = {
user: this.state.user,
logoutUser: this.logout
}
return (
<div>
<userContext.Provider value={value}>
<NavMenu />
<Container>
{this.props.children}
</Container>
</userContext.Provider>
</div>
);
}
}
src/components/NavMenu.js:
import { userContext } from '../userContext';
export class NavMenu extends Component {
static displayName = NavMenu.name;
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of MyContext */
}
constructor (props) {
super(props);
this.toggleNavbar = this.toggleNavbar.bind(this);
this.state = {
collapsed: true
};
}
toggleNavbar () {
this.setState({
collapsed: !this.state.collapsed
});
}
render() {
if (true) {
return (
<header>
<userContext.Consumer>
{({ value }) => {
console.log('test')
console.log(value);//------------>undefiened
console.log(this.context)
}}
</userContext.Consumer>
...
Is there something I'm missing here
Seems like it can work.
I think the problem is your destructuring value from render props : )
Try:
<userContext.Consumer>
{(value) => {
console.log("test")
console.log(value)
console.log(this.context)
return <b>HELLO</b>
}}
</userContext.Consumer>
Reference for Render props: https://reactjs.org/docs/render-props.html

how to use functional mobX store in react class components?

here is my init store function:
import React, { FC } from 'react';
import { useLocalObservable } from 'mobx-react';
import { TestStore, ITestStore } from './TestStore';
interface IStoreContext {
testStore: ITestStore;
}
export const StoreContext = React.createContext<IStoreContext>({} as IStoreContext);
export const StoreProvider: FC = ({ children }) => {
const testStore = useLocalObservable(TestStore);
const stores = {
testStore,
};
return <StoreContext.Provider value={stores}>{children}</StoreContext.Provider>;
};
export const useRootStore = () => {
const rootStore = React.useContext(StoreContext);
if (!rootStore) {
throw new Error('useStore must be used within a StoreProvider');
}
return rootStore;
};
and this is how i use it:
const { testStore } = useRootStore();
But I can't use the hook inside the class component.
So how get the store inside the class component ?
thanks.
You can create a Higher-Order Component that uses your hook and passes the result to the wrapped class component.
Example
function withRootStore(Component) {
return function WrappedComponent(props) {
const rootStore = useRootStore();
return <Component {...props} rootStore={rootStore} />;
}
}
// ...
class MyComponent extends React.Component {
render() {
const { testStore } = this.props.rootStore;
// ...
}
}
export default withRootStore(MyComponent);

Next-Auth & React.Component

Next-Auth has the following example which is great for functions, however I have a class which I need to run const { data: session } = useSession() in it. I am wondering how can I convert it to make it valid in a class?
export default function AdminDashboard() {
const { data: session } = useSession()
// session is always non-null inside this page, all the way down the React tree.
return "Some super secret dashboard"
}
AdminDashboard.auth = true
I tried to add session: useSession() to the following constructor but it did not work.
My Class
export default class AdminDashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
areas:[],
areasid:[],
users: [],
isLoading: true,
isAreaLoading: true,
session: useSession() // THIS DID NOT WORK
};
this.checkAnswer = this.checkAnswer.bind(this);
}
}
AdminDashboard.auth = true
based on the answer below. I changed the script to be like this
const withSession = (Component) => (props) => {
const session = useSession()
// if the component has a render property, we are good
if (Component.prototype.render) {
return <Component session={session} {...props} />
}
// if the passed component is a function component, there is no need for this wrapper
throw new Error(
[
"You passed a function component, `withSession` is not needed.",
"You can `useSession` directly in your component.",
].join("\n")
)
}
export default class NewCampaign extends React.Component {
render(){
const { data: session, status } = this.props.session;
const { isLoading, users, areas, areasid, isAreaLoading } = this.state;
return (
<React.Fragment></React.Fragment>
)}
}
const ClassComponentWithSession = withSession(NewCampaign)
NewCampaign.auth = false;
NewCampaign.getLayout = function getLayout(page) {
return (
<Dashboard>
{page}
</Dashboard>
)
}
However, I am getting Cannot destructure property 'data' of 'this.props.session' as it is undefined.
You should use getSession and just await the result.
async function myFunction() {
const session = await getSession()
// session available here
}
You can use it both on client and the server.
If you want to use the useSession() hook in your class components you can do so with the help of a higher order component or with a render prop.
Higher Order Component
import { useSession } from "next-auth/react"
const withSession = (Component) => (props) => {
const session = useSession()
// if the component has a render property, we are good
if (Component.prototype.render) {
return <Component session={session} {...props} />
}
// if the passed component is a function component, there is no need for this wrapper
throw new Error(
[
"You passed a function component, `withSession` is not needed.",
"You can `useSession` directly in your component.",
].join("\n")
)
}
// Usage
class ClassComponent extends React.Component {
render() {
const { data: session, status } = this.props.session
return null
}
}
const ClassComponentWithSession = withSession(ClassComponent)
Render Prop
import { useSession } from "next-auth/react"
const UseSession = ({ children }) => {
const session = useSession()
return children(session)
}
// Usage
class ClassComponent extends React.Component {
render() {
return (
<UseSession>
{(session) => <pre>{JSON.stringify(session, null, 2)}</pre>}
</UseSession>
)
}
}

Pass array to component

I'm getting a problem trying to send an array that I get from my DB, from a component to another.
I mean, I'm obtaining JSON data and then that data has to be passed to another component.
I was thinking about using the map method.
I checked my console and I noticed that I get the correct data from the DB but when I'm trying to send it by the state, in the class that I want to get the info is null
Class which gets the objects:
import React from 'react'
import { withRouter } from 'react-router-dom'
import MovieList from './MovieList'
import MoviesService from '../services/MoviesService'
class MovieListGet extends React.Component {
constructor (props) {
super(props)
this.state = {
movies: []
}
}
async componentDidMount () {
await this._getMovies()
}
async _getMovies () {
const response = await MoviesService.getMovies()
this.setState({ movies: response.data })
console.log(this.state.movies)
}
async _deleteMovie (id) {
}
_navigateToCreateMovies () {
// Adrress
}
render () {
return (
<div>
<MovieList
movies = {this.state.movies}
onCreateMovie={this._navigateToCreateMovies.bind(this)}
onDelete={this._deleteMovie.bind(this)}
/>
</div>
)
}
}
export default withRouter(MovieListGet)
Class that has to get the array
/* eslint-disable react/prop-types */
import React from 'react'
import {...} from '#material-ui/core'
import { ... } from '#material-ui/icons'
class MovieList extends React.Component {
constructor (props) {
super(props)
const { movies } = props
this.state = {
_movies: []
}
}
componentDidMount () {
console.log(this.props.movie)
this.setState({ _movies: this.props.movies })
}
_renderMovies () {
if (!this.state._movies || this.state._movies.length < 1) {
return (
// Something
)
} else {
return this.state._movies.map(m => (
// something
)
)
}
}
render () {
return (
// Something
)
}
}
export default (MovieList)
MoviesListGet should pass the prop like this:
<MovieList
movies={this.state.movies}
onCreateMovie={this._navigateToCreateMovies.bind(this)}
onDelete={this._deleteMovie.bind(this)}
/>
Then in MoviesList component you can use it like this in you renderMovies method:
class MovieList extends React.Component {
renderMovies () {
if (!this.props.movies || this.props.movies.length < 1) {
return (
// Something
)
} else {
return this.props.movies.map(m => (
// something
)
)
}
}
render () {
return (
// Something
)
}
}
export default MovieList;

Lazy Loading Routes in React With Typescript AsyncComponent

I'm trying to lazy load routes in React by implementing the AsyncCompoment class as documented here Code Splitting in Create React App. Below is the es6 asyncComponent function from the tutorial:
import React, { Component } from "react";
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null
};
}
async componentDidMount() {
const { default: component } = await importComponent();
this.setState({
component: component
});
}
render() {
const C = this.state.component;
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
I've written this function in typescript and can confirm that components are indeed being loaded lazily. The issue I face is that they are not being rendered. I was able to determine that the component object is always undefined in the componentDidMount hook:
//AsyncComponent.tsx
async componentDidMount() {
const { default: component } = await importComponent();
this.setState({
component: component
});
}
The object being returned from the importComponent function has the following properties:
{
MyComponent: class MyComponent: f,
__esModule: true
}
I modified the componentDidMount method to take the first property of this object, which is the MyComponent class. After this change my project is now lazy loading the components and rendering them properly.
async componentDidMount() {
const component = await importComponent();
this.setState({
component: component[Object.keys(component)[0]]
});
}
My best guess is that I have not written this line properly in typescript:
const { default: component } = await importComponent();
I'm calling the asyncComponent method like so:
const MyComponent = asyncComponent(()=>import(./components/MyComponent));
Anyone know how to implement the AsyncComponent in typescript? I'm not sure if simply getting the 0 index on the esModule object is the correct way to do it.
// AsyncComponent.tsx
import * as React from "react";
interface AsyncComponentState {
Component: null | JSX.Element;
};
interface IAsyncComponent {
(importComponent: () => Promise<{ default: React.ComponentType<any> }>): React.ComponentClass;
}
const asyncComponent: IAsyncComponent = (importComponent) => {
class AsyncFunc extends React.PureComponent<any, AsyncComponentState> {
mounted: boolean = false;
constructor(props: any) {
super(props);
this.state = {
Component: null
};
}
componentWillUnmount() {
this.mounted = false;
}
async componentDidMount() {
this.mounted = true;
const { default: Component } = await importComponent();
if (this.mounted) {
this.setState({
component: <Component {...this.props} />
});
}
}
render() {
const Component = this.state.Component;
return Component ? Component : <div>....Loading</div>
}
}
return AsyncFunc;
}
export default asyncComponent;
// Counter.tsx
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
interface CounterState {
currentCount: number;
}
class Counter extends React.Component<RouteComponentProps<{}>, CounterState> {
constructor() {
super();
this.state = { currentCount: 0 };
}
public render() {
return <div>
<h1>Counter</h1>
<p>This is a simple example of a React component.</p>
<p>Current count: <strong>{this.state.currentCount}</strong></p>
<button onClick={() => { this.incrementCounter() }}>Increment</button>
</div>;
}
incrementCounter() {
this.setState({
currentCount: this.state.currentCount + 1
});
}
}
export default Counter;
//routes.tsx
import * as React from 'react';
import { Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import asyncComponent from './components/AsyncComponent';
const AsyncCounter = asyncComponent(() => import('./components/Counter'));
export const routes = <Layout>
<Route exact path='/' component={Home} />
<Route path='/counter' component={AsyncCounter} />
</Layout>;

Resources