Console.Log Not Being Called Inside React Constructor - javascript

I'm trying to add a component to a default .NET Core MVC with React project. I believe I have everything wired up to mirror the existing "Fetch Data" component, but it doesn't seem like it's actually being called (but the link to the component in my navbar does move to a new page).
The component itself...
import React, { Component } from 'react';
export class TestComponent extends Component {
static displayName = TestComponent.name;
constructor (props) {
super(props);
console.log("WHO NOW?");
this.state = { message: '', loading: true, promise: null };
this.state.promise = fetch('api/SampleData/ManyHotDogs');
console.log(this.state.promise);
}
static renderForecastsTable (message) {
return (
<h1>
Current Message: {message}
</h1>
);
}
render () {
let contents = this.state.loading
? <p><em>Loading...</em></p>
: TestComponent.renderForecastsTable(this.state.message);
return (
<div>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
{contents}
</div>
);
}
}
The App.js
import React, { Component } from 'react';
import { Route } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import { TestComponent } from './components/TestComponent';
export default class App extends Component {
static displayName = App.name;
render () {
return (
<Layout>
<Route exact path='/' component={Home} />
<Route path='/counter' component={Counter} />
<Route path='/fetch-data' component={FetchData} />
<Route path='/test-controller' component={TestComponent} />
</Layout>
);
}
}
That console.log("Who now") is never called when I inspect, and the page remains totally blank. I can't find a key difference between this and the functioning components, and google has not been much help either. Any ideas what is missing?
Edit
While troubleshooting this, I ended up creating a dependency nightmare that broke the app. Since I'm only using the app to explore React, I nuked it and started over--and on the second attempt I have not been able to reproduce the not-rendering issue.

It is advisable to use componentDidMount to make the call to the REST API with the fetch or axios.
class TestComponent extends Component{
constructor(props){
state = {promise: ''}
}
async componentDidMount () {
let promise = await fetch ('api / SampleData / ManyHotDogs');
this.setState ({promise});
console.log (promise);
}
render(){
return(
<div>{this.state.promise}</div>
);
}
}

Related

Push to new route without any further actions on the component

We use an external componet which we don't control that takes in children which can be other components or
used for routing to another page. That component is called Modulation.
This is how we are currently calling that external Modulation component within our MyComponent.
import React, {Fragment} from 'react';
import { withRouter } from "react-router";
import { Modulation, Type } from "external-package";
const MyComponent = ({
router,
Modulation,
Type,
}) => {
// Need to call it this way, it's how we do modulation logics.
// So if there is match on typeA, nothing is done here.
// if there is match on typeB perform the re routing via router push
// match happens externally when we use this Modulation component.
const getModulation = () => {
return (
<Modulation>
<Type type="typeA"/> {/* do nothing */}
<Type type="typeB"> {/* redirect */}
{router.push('some.url.com')}
</Type>
</Modulation>
);
}
React.useEffect(() => {
getModulation();
}, [])
return <Fragment />;
};
export default withRouter(MyComponent);
This MyComponent is then called within MainComponent.
import React, { Fragment } from 'react';
import MyComponent from '../MyComponent';
import OtherComponent1 from '../OtherComponent1';
import OtherComponent2 from '../OtherComponent2';
const MainComponent = ({
// some props
}) => {
return (
<div>
<MyComponent /> {/* this is the above component */}
{/* We should only show/reach these components if router.push() didn't happen above */}
<OtherComponent1 />
<OtherComponent2 />
</div>
);
};
export default MainComponent;
So when we match typeB, we do perform the rerouting correctly.
But is not clean. OtherComponent1 and OtherComponent2 temporarily shows up (about 2 seconds) before it reroutes to new page.
Why? Is there a way to block it, ensure that if we are performing router.push('') we do not show these other components
and just redirect cleanly?
P.S: react-router version is 3.0.0

How to properly render Component after this.setState in React

I have this React component
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: [],
};
}
componentDidMount() {
// get the resources from the Link props and save it into the state
this.setState({
resources: this.props.location.resources,
});
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
It gets the resources from the Link component, and that works fine. If I check out the state of the Component from the dev tools, the state looks right. And I thought with my logic this should work. So firstly, the state is empty, the component gets rendered, since the state is empty it doesn't render any components. Then, setState gets called, it gets all the resources and saves them into the state, and then the component would re-render, and it should work, but it doesn't. I'm getting a TypeError: Cannot read property 'map' of undefined error. What is the correct way to do this and how do I fix this?
Try this code:
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: this.props && this.props.location && this.props.location.resources?this.props.location.resources:[],
};
}
componentDidMount() {
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
Or use directly props
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{
this.props && this.props.location &&
this.props.location.resources
?this.props.location.resources.map(res => (
<div>test</div>
))
:null
}
</div>
);
}
}
Or use componentWillReceiveProps or getDerivedStateFromProps life cycle methods.
Check this.props.location.resources is array.
See more: https://hackernoon.com/replacing-componentwillreceiveprops-with-getderivedstatefromprops-c3956f7ce607
For first check is this.props.location.resources array, or if data type changes you can add checking, you can use lodash isArray or with js like this:
import React, { Component } from "react";
export default class ResourceForField extends Component {
constructor() {
super();
this.state = {
resources: [],
};
}
componentDidMount() {
// get the resources from the Link props and save it into the state
Array.isArray(this.props.location.resources) {
this.setState({
resources: this.props.location.resources,
});
}
}
// This component gets the id of current learningField from the url
// and the rest(like the resources) from the Link component
render() {
return (
<div>
{this.state.resources.map(res => (
<div>test</div>
))}
</div>
);
}
}
Or you can just use hooks like this:
import React, { useState, useEffect } from "react";
export default function ResourceForField({location}) {
const [ resources, setResources ] = useState([]);
useEffect(() => {
if (location && Array.isArray(location.resources)) {
setResources(location.resources)
}
}, [location]);
return (
<div>
{resources.map(res => (
<div>test</div>
))}
</div>
);
}
If the internal state of ResourceForField doesn't change and always equals to its prop, you shouldn't save the prop in the state. You can instead create a pure functional component.
Also note that there's nothing preventing you from initializing the state from the props in constructor method. i.e. you're not required to wait for the component to mount in order to access the props.
So, I'd write the following component for ResourceForField:
function ResourceForField({resources = []}) {
return (
<div>
{
resources.map(res => (<div>test</div>))
}
</div>
);
}

react-router Route component constructor is not called when props are updated in BrowserRouter component

I have a React app. I'm using react and react-router. Here's the sandbox link.
I have an App.js file like this:
import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import Items from './Items';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: []
}
}
componentDidMount() {
this.setState({ items: ['a', 'b', 'c'] });
}
render() {
const { items } = this.state;
return (
<BrowserRouter>
<div>
<Route exact path="/" render={(props) => <Items {...props} items={items} />} />
</div>
</BrowserRouter>
)
}
}
export default App;
In this file, in the componentDidMount, I'm getting data from an API, then passing it to the Items component. On the initial page load, of course items will be an empty array, and then it will eventually have content.
In my Items.js file, I have:
import React, { Component } from 'react';
class Items extends Component {
constructor(props) {
super(props);
this.items = this.props.items;
}
render() {
return (
<div>
{this.items.length}
</div>
)
}
}
export default Items;
As you can see, this.items is retrieved from the props. On initial page load, again, this is an empty array. But after the componentDidMount fires in App.js, the constructor in Items.js is not fired, so this.items is never re-populated with the items.
How can I instead fire the constructor in Items.js? I know this is a simple example, and therefore could technically be solved by simply accessing the props in the render method, but I really need the constructor to fire, because in my actual app, I have more complex logic in there.
You can use this.props directly in the render method of Items to extract the data you want.
class Items extends Component {
constructor(props) {
super(props);
}
render() {
const { items } = this.props;
return (
<div>
{items.length}
</div>
)
}
}
Since the constructor of a component is only called once, I will instead move the logic that relies on the props, to the parent component.

React-Chat-Widget props not forwarded

I am using the react-chat-widget and trying to call a function in the base class of my application from a custom component rendered by the renderCustomComponent function of the widget.
Here is the code for the base class:
import React, { Component } from 'react';
import { Widget, handleNewUserMessage, addResponseMessage, addUserMessage, renderCustomComponent } from 'react-chat-widget';
import 'react-chat-widget/lib/styles.css';
import Reply from './Reply.js';
class App extends Component {
handleNewUserMessage = (newMessage) => {
renderCustomComponent(Reply, this.correct);
}
correct = () => {
console.log("success");
}
render() {
return (
<div className="App">
<Background />
<Widget
handleNewUserMessage={this.handleNewUserMessage}
/>
</div>
);
}
}
export default App;
And here is the code for the custom component Reply:
import React, { Component } from 'react';
import { Widget, addResponseMessage, renderCustomComponent, addUserMessage } from 'react-chat-widget';
class Reply extends Component {
constructor(props) {
super(props);
}
sendQuickReply = (reply) => {
console.log(this.props); //returns empty object
//this.props.correct(); <-- should be called
};
render() {
return (
<div className="message">
<div key="x" className={"response"}onClick={this.sendQuickReply.bind(this, "xx")}>xx</div>
</div>)
}
}
export default Reply;
According to ReactJS call parent method this should work. However, when I print the this.props object it is empty, although the documentation of the renderCustomComponent method states that the second argument of the component to render are the props that the component needs (in this case the parent class function).
Where have I gone wrong?
The second parameter is considered as props, but it is expected to be an object. you would pass it like
handleNewUserMessage = (newMessage) => {
renderCustomComponent(Reply, {correct: this.correct});
}

Next.js Persistent component - Youtube embed that plays even after changing page

I am trying to create a Next.js react app. One of the requirements is that a youtube player must persist when changing pages. Only issue is that I'm not sure if it's possible with the way Next works. It seems that pages will aways re-mount regardless of the structure.
Here in my app I export Index into page.js which acts as a parent component.
<Media/> //Youtube player
page.js will always remount thus the player will reload.
page.js:
import React from 'react';
import { Provider } from 'react-redux';
import { checkLang } from '../helpers/langSupport';
import { changeLang } from '../store/actions/langAction';
import store from '../store/store';
import { Router } from '../config/router';
import Media from './youtube';
import Head from './head';
import Nav from './nav';
const childPage = (ChildPage) => {
return (
class Page extends React.Component {
componentDidMount(){
this.checkLanguage()
}
checkLanguage() {
checkLang(this.props.url, (status, result) => {
if(status){
store.dispatch(changeLang(result))
}else{
Router.pushRoute('/en'+this.props.url.asPath)
}
})
}
render() {
return (
<Provider store={store}>
<div>
<Head />
<Nav />
<ChildPage {...this.props} />
<Media/>
</div>
</Provider>
)
}
}
)
}
export default childPage;
index.js:
import React from 'react';
import { connect } from 'react-redux'
import { add, minus } from '../store/actions/countAction';
import Page from '../components/page';
export class Index extends React.Component {
render() {
return (
<div>
<div className="hero">
Count: {this.props.count.count}
<br/><br/>
<button onClick={()=>this.props.dispatch(add(1))}>Add</button>
<button onClick={()=>this.props.dispatch(minus(1))}>Minus</button>
</div>
<style jsx>{`
.hero{
margin-left: 50px;
}
`}</style>
</div>
)
}
}
export default Page(connect(state=>state)(Index));
This has now been implemented as _app.js. Check out the next.js readme to learn more.
Unfortunately this is something that does not exist in Next.js yet. Though it is actively discussed and will probably be implemented in the near future.
ReactDOM.render is called on every page change, so this is not possible today.
See discussions on:
https://github.com/zeit/next.js/issues/88
https://github.com/zeit/next.js/pull/2440
https://github.com/zeit/next.js/pull/3288

Categories

Resources