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
Related
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));
I get the pathname in the WorldPage component and pass this value to the context.jsx in which I want to request data using the pathname.
However, I cannot get the correct value in the componentDidMount() method.
console.log(this.state.tab) should be /world, but still /home.
import axios from "axios";
export const Context = React.createContext();
export class Provider extends Component {
state = {
news_list: [],
tab: "/home",
tabChange: (tabName) => {
if (this.state.tab !== tabName) {
this.setState({
tab: tabName,
});
}
},
};
componentDidMount() {
console.log(this.state.tab);
axios
.get(this.state.tab)
.then((res) => {
console.log(res.data);
this.setState({
news_list: res.data,
});
// console.log(this.state.news_list);
})
.catch((err) => console.log(err));
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
import React, { Component } from "react";
import News from "../News/News";
import { Consumer } from "../../context";
export default class WorldPage extends Component {
render() {
const tabName = window.location.pathname;
return (
<Consumer>
{(value) => {
const { tabChange } = value;
tabChange(tabName);
console.log(tabName);
return (
<React.Fragment>
<News />
</React.Fragment>
);
}}
</Consumer>
);
}
}
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 :)
I'm trying to create a session handler, for all the components where i need to determine if a user is logged in. however if i add a console.log(authUser) in withAuthentication it returns a user, but however not in the SideBar. Here it always be null. What am i doing wrong?
withAuthentication
const withAuthentication = (Component) => {
class WithAuthentication extends React.Component {
componentDidMount() {
const { onSetAuthUser } = this.props;
firebase.auth.onAuthStateChanged(authUser => {
authUser
? onSetAuthUser(authUser)
: onSetAuthUser(null);
});
}
render() {
return (
<Component />
);
}
}
const mapDispatchToProps = (dispatch) => ({
onSetAuthUser: (authUser) => dispatch({ type: 'AUTH_USER_SET', authUser }),
});
return connect(null, mapDispatchToProps)(WithAuthentication);
}
export default withAuthentication;
App
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="app">
<SideBar />
<div>
<Navigation/>
<BrowserRouter>
<Switch>
<Route exact path={routes.LANDING} component={() => <LandingPage />} />
<Route render={() => <h3>No Match</h3>} />
</Switch>
</BrowserRouter>
</div>
</div>
)
}
}
export default withAuthentication(App);
SideBar
const INITIAL_STATE = {
sideBarOpen: false
};
class SideBar extends Component {
constructor(props) {
super(props.authUser);
console.log(props)
this.state = { ...INITIAL_STATE };
}
render() {
const {
sideBarOpen,
authUser
} = this.state;
const {
children,
} = this.props;
return (
{authUSer ?
'authed' :
'not authed'
}
)
}
}
const mapStateToProps = (state) => ({
authUser: state.sessionState.authUser
});
export default connect(mapStateToProps)(SideBar);
reducer
const INITIAL_STATE = {
authUser: null,
};
const applySetAuthUser = (state, action) => ({
...state,
authUser: action.authUser
});
function sessionReducer(state = INITIAL_STATE, action) {
switch(action.type) {
case 'AUTH_USER_SET' : {
return applySetAuthUser(state, action);
}
default : return state;
}
}
const rootReducer = combineReducers({
sessionState: sessionReducer
});
export default rootReducer;
As your mapStateToProps setup
const mapStateToProps = (state) => ({
authUser: state.sessionState.authUser
});
You need to get authUser from component props not from component state.
So your code should be const authUser = this.props.authUser.
Or your way const {authUser} = this.props
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>;