Next-Auth & React.Component - reactjs

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

Related

covert global token (function to class component)

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

useEffect not setting data to state in functional component

I have functional component wrapped with HOC. Its returns some props after api call. How do I set the state in my child component(functional).
const withEditHoc = (WrappedComponent, actioneffects) => {
class HOC extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
};
}
executeAllActions = async (data, id) => {
await Promise.all(data.map(act => this.props.dispatch(act(id)))).then(() =>
this.setState({ loading: false }),
);
};
componentDidMount = () => {
const editpageId = this.props.match.params.id;
this.executeAllActions(actioneffects, editpageId);
};
render() {
console.log(this.state.loading);
return (
<React.Fragment>
<Loading loading={this.state.loading}>
<WrappedComponent {...this.props} />
</Loading>
</React.Fragment>
);
}
}
return HOC;
This is my HOC Structure. After the api call the data will be in redux.
I am getting a prop for my functional component using mapToStateProp.(react version 16.3)
Please any suggestion for this.
Functional component
function ProjectDetails(props) {
const [projectValue, setValue] = useState({});
const [proData, setProData] = useState({ ...props.project });
useEffect(() => {
setProData({ props.project });//Here I need to set my data, Iam not able to set data here.
}, []);
return <div>{JSON.stringify(props.project)}</div>;
}
function mapStateToProps(state) {
return {
project: state.projects.project,
};
}
const projectDetailsWithHocLoading = withEditHoc(ProjectDetails, [actions.apiCall()]);
export default connect(mapStateToProps, null)(projectDetailsWithHocLoading);
I am a beginner to react. Please suggest a good way
mapStateToProps created for class components.
because you are using hooks, you should use useSelector hook
import { useSelector } from 'react-redux';
function ProjectDetails(props) {
const [projectValue, setValue] = useState({});
const proData = useSelector(state => state.projects.project)
return <div>{JSON.stringify(proData)}</div>;
}
const projectDetailsWithHocLoading = withEditHoc(ProjectDetails,actions.apiCall()]);
export default projectDetailsWithHocLoading;

Why can't I access the context's methods or properties

I am trying to use React's context api to manage a global state. When I try to invoke contextual methods or access contextual proprties, I get errors saying "this.context.setUser function does not exist" or "undefined".
I have however been able to hard code values into the state of the context and retreive the hardcoded value.
Feed Context
import React, { Component } from 'react'
const FeedContext = React.createContext({
Feed: [],
user: '',
error: null,
setError: () => {},
clearError: () => {},
setFeed: () => {},
setUser: () => {}
})
export default FeedContext
export class FeedProvider extends Component {
state = {
feed: [],
error: null,
user: ''
};
setUser = user => {
this.setState({ user })
}
setFeed = Feed => {
this.setState({ Feed })
}
setError = error => {
console.error()
this.setState({ error })
}
clearError = () => {
console.log('context is accessed')
this.setState({ error: null })
}
render() {
const value = {
feed: this.state.feed,
error: this.state.error,
setError: this.setError,
clearError: this.clearError,
setFeed: this.setFeed,
setUser: this.setUser
}
return (
<FeedContext.Provider value={value}>
{this.props.children}
</FeedContext.Provider>
)
}
}
AccountPanel.js
import React from 'react';
import FeedContext from "../../contexts/FeedContext";
// functional component
class AccountPanel extends React.Component {
static contextType = FeedContext
renderUserInfo(){
const { user = [] } = this.context;
//this returns "undefined"
console.log(user.user)
//this returns "user.setUser() is not a function"
user.setUser('newUser')
//this returns ' '
this.context.setUser('y')
console.log(user)
}
render(){
return (
<section>
{ this.renderUserInfo() }
AccountPanel
</section>
)
}
}
export default AccountPanel;
I would like to be able to update the contextual state/user via this.context.setUser('newUser), then consume that value in my navbar component
File App.js
import React, { Component } from 'react';
import AccountPanel from "./components/AccountPanel";
import { FeedProvider } from './components/FeedContext';
class App extends Component {
render() {
return (
<div className="App">
<FeedProvider>
<AccountPanel />
</FeedProvider>
</div>
);
}
}
export default App;
File : FeedContext.js
import React, { Component } from 'react'
const FeedContext = React.createContext({
Feed: [],
user: '',
error: null,
setError: () => {},
clearError: () => {},
setFeed: () => {},
setUser: () => {}
})
export default FeedContext
export class FeedProvider extends Component {
constructor(props){
super(props);
this.state = {
feed: [],
error: null,
user: "11"
};
}
setUser = user => {
console.log(`setting usr fns called for username: ${user}`);
this.setState({ user });
}
setFeed = Feed => {
this.setState({ Feed })
}
setError = error => {
console.error()
this.setState({ error })
}
clearError = () => {
console.log('context is accessed')
this.setState({ error: null })
}
componentDidMount(){
console.log('FeedProvider:componentDidMount');
}
render() {
let value1 = {
Feed:this.state.feed,
user:this.state.user,
error:this.state.error,
setError:this.setError,
clearError:this.clearError,
setFeed:this.setFeed,
setUser:this.setUser
}
return (
<FeedContext.Provider value={value1}>
{this.props.children}
</FeedContext.Provider>
)
}
}
File : AccountPanel.js
import React from 'react';
import FeedContext from "./FeedContext";
// functional component
class AccountPanel extends React.Component {
static contextType = FeedContext
// return BlogPost component html/(JSX)
componentDidMount(){
console.log('AccountPanel:componentDidMount');
console.log(this.context);
const value = this.context;
//this returns "undefined"
console.log(value.user)
//this returns "user.setUser() is not a function"
console.log(value.setUser);
value.setUser('newUser');
}
render(){
const value = this.context;
console.log(`Value of new User is : ${value.user}`);
return (
<section>
AccountPanel
</section>
)
}
}
export default AccountPanel;
Hope This helps :)

How to update the state of new context provider after ajax success

New to React, trying to update the state which is initialized inside the new react context provider, after the API call is success. I am using React 16.3 .
Not able to update the state value, followed documented steps but still failed to achieve.
This is what I tried:
HTML:
<MyProvider>
<MyConsumer>
{context => (
{context.updateInitialData(this.props)}
)}
</MyConsumer>
</MyProvider>
js:
import React, { Component } from 'react';
const MyContext = React.createContext();
export const MyConsumer = HeaderContext.Consumer;
export class MyProvider extends Component {
state = {
data: null,
updateInitialData: this.updateInitialData
};
updateInitialData = () => {
this.setState({data: this.state.data})
}
render() {
return (
<MyContext.Provider
value={{
state: this.state,
updateInitialData: this.updateInitialData
}}
>
{this.props.children}
</MyContext.Provider>
);
}
}
The problem is that now even if you, correctly set the state using updateInitialData, you are actually calling the function in render which will then call setState triggering a re-render and continuing the cycle. What you need is instead to write the HOC and update the initialData in lifecycle method
import React, { Component } from 'react';
const MyContext = React.createContext();
export const MyConsumer = MyContext.Consumer;
export class MyProvider extends Component {
// you don't need to store handler in state since you are explicitly passing it as a context value
state = {
data: null
};
updateInitialData = (data) => { // getting data from passed value
this.setState({data: data})
}
render() {
return (
<MyContext.Provider
value={{
state: this.state,
updateInitialData: this.updateInitialData
}}
>
{this.props.children}
</MyContext.Provider>
);
}
}
HOC:
const withContext = (Component) => {
return class App extends React.Component {
render() {
return (
<MyConsumer>
{context => (<Component {...this.props} context={context} />)}
</MyConsumer>
)
}
}
}
and then you would use it like
class Consumer extends React.Component {
componenDidMount() {
this.props.context.updateInitialData(this.props.data);
}
render() {
}
}
export default withContext(Consumer);
and thne
<MyProvider>
<Consumer data={this.props}/>
</MyProvider>
I'm not sure whether you copy-pasted it wrong but you don't update your state with data provided to the handler:
updateInitialData = () => {
this.setState({data: this.state.data}) // ??? its doing nothing
}
try:
updateInitialData = (data) => {
this.setState({ data })
}

calling redux connect on a decorator?

I am trying to call connect on a decorator that returns a react class
const SetLanguageFromPage = () => {
return WrappedComponent =>
class setLang extends React.Component {
static propTypes = {
pathContext: PropTypes.shape({
language: PropTypes.string.isRequired
})
};
componentDidMount() {
const currentLanguage = i18n.language;
const pageLanguage = this.props.pathContext.language;
// First request
if (!currentLanguage) {
i18n.language = pageLanguage;
}
// Only update on language change
if (currentLanguage !== pageLanguage) {
i18n.changeLanguage(pageLanguage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
};
};
const mapStateToProps = (state) => { return{...} }
const mapDispatchToProps = (dis) => { return{...} }
export default connect(...)(SetLanguageFromPage);
but when I then use the decorator on another react class I get this error...
Uncaught TypeError: Cannot call a class as a function
which I suppose is from connect changing my function to a react class. Is there any way to accomplish what I am trying to do? I would really like to be able to call actions to set the state from within this decorator, but I can't see how I can get at the store to call dispatch or map the dispatch to the props...
I am using https://www.gatsbyjs.org/ for this, so the general method has the store instantiated in a way where I cannot access is directly
You get an error, because you are trying to pass and HOC to connect, whereas it expects a React component. You can instead connect the returned component inside the HOC, which is what you essentially want to do
const SetLanguageFromPage = () => {
return WrappedComponent => {
class SetLang extends React.Component {
static propTypes = {
pathContext: PropTypes.shape({
language: PropTypes.string.isRequired
})
};
componentDidMount() {
const currentLanguage = i18n.language;
const pageLanguage = this.props.pathContext.language;
// First request
if (!currentLanguage) {
i18n.language = pageLanguage;
}
// Only update on language change
if (currentLanguage !== pageLanguage) {
i18n.changeLanguage(pageLanguage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
};
return connect(mapStateToProps, mapDispatchToProps)(SetLang);
}
};
const mapStateToProps = (state) => { return{...} }
const mapDispatchToProps = (dis) => { return{...} }
export default SetLanguageFromPage;

Resources