Here my ReactJS' snippet:
const LoaderModal = dynamic(
() => import("~/modal/Loader/Loader"),
{ ssr: false }
)
export default class MyApp extends Component {
state={
displayLoader:false
}
componentDidMount(){
let specificChangeStart= Router.events.on('routeChangeStart', () => {
console.log("routeChangeStart")
this.setState({displayLoader:true})
})
let specificComplete = Router.events.on('routeChangeComplete', () => {
console.log("routeChangeComplete")
this.setState({displayLoader:false})
})
let specificError= Router.events.on('routeChangeError',() => {
console.log("routeChangeError")
this.setState({displayLoader:false})
})
}
render(){
let { Component, pageProps }=this.props
let {displayLoader}=this.state
return(
<LayoutContextProvider >
<Component {...pageProps}/>
{
displayLoader &&
<LoaderModal/>
}
</LayoutContextProvider>
)
}
}
I am using nextjs version 9.1.4.
As you can see the Router's events are stored in the componentDidMount() component lifecycle's stage. I have stored them in variable to make them specific from other declarations in my app. I have also tried without assigning them, the both method fail so far.
How can I make the Router works, is there a subtlety to be inform of here?
Related
I use MobX to control my ReactJS state/components and I also use an async call through MobX in order to retrieve the data, this is typically called in my header throuhg componentDidMount().
(I already know my code isn't the cleanest and most likely has errors, cut me some slack as I'm learning/coding this completely on my own at this point with no educational background in programming)
import React from 'react';
import { Layout, Row, Col, Menu, Avatar, Tag } from 'antd';
import { inject, observer } from 'mobx-react';
import Icon from '#ant-design/icons';
const { Header } = Layout;
const { SubMenu } = Menu;
#inject('store')
#observer
class PageHeader extends React.Component {
componentDidMount() {
this.props.store.getOrders();
}
Say for instance I was not in the Application, but I was still logged in through my LocalStorage data, and I went to a page "http://localhost/orders/123456". 123456 would be my order ID and this page would display it's details. Now considering I was not on the page, the DOM wasn't rendered, right? Right... But I'm still logged in through my LocalStorage, so when I visit the page - it's rendering blank because MobX has to wait for the API call to retrieve the data. I need to be able to pull this data and make sure it's rendered on the page, so I some how need the API to be retrieve before rendering to ensure it's pull the data out of MobX with the specified OrderID, in this case 123456.
Below is two ways I've made my componentDidMount
#inject('store')
#observer
class LoadPage extends React.Component {
state = {
visible: false,
ordernum: this.props.match.params.id,
orderkey: null,
}
componentDidMount() {
document.title = this.props.match.params.id;
route_ordernum = this.props.match.params.id;
if (this.props.store.orders.length === 0) {
fetch('http://localhost:5000')
.then(res1 => res1.json())
.then(data => this.props.store.setOrders(data))
.then(this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
}))
}
if (this.props.store.orders.length > 0) {
this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
})
}
console.log(this.state.orderkey)
}
render() {
Example #2
componentDidMount() {
document.title = this.props.match.params.id;
route_ordernum = this.props.match.params.id;
if (this.props.store.orders.length === 0) {
this.props.store.getOrders().then(dataloads => {
this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
})
})
}
if (this.props.store.orders.length > 0) {
this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
})
}
console.log(this.state.orderkey)
}
I'm just passing through my MobX and using two separate classes to create my state now.
#inject('store')
#observer
class LoadMain extends React.Component {
render() {
return(
this.props.store.orders.length === 0 ? <Content style={{ backgroundColor: "#ffffff" }}><center><Spinner /></center></Content> : <OrderPage ordernum={this.props.match.params.id} />
);
}
}
Can we dynamically import hooks based on the value passed to the component?
For eg.
App.js
<BaseComponent isActive />
BaseComponent.js
if(props.isActive) {
// import useActive (custom Hook)
}
I donot want these(custom hook) files to be imported and increase the size of BaseComponent even when the props contain falsey values.
You can dynamically import hooks as it is just a function (using require), but you shouldn't because you can't use hooks inside conditions.
See Rules of Hooks
Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
If you want conditionally use a hook, use the condition in its implementation (look for example at skip option of useQuery hook from Apollo GraphQL Client).
const useActive = (isUsed) => {
if (isUsed) {
// logic
}
}
You should extract the logic inside the useActive hook and dynamically import it instead of dynamically importing the hook since you should not call Hooks inside loops, conditions, or nested functions., checkout Rules of Hooks:
Let's say your useActive hook was trying to update the document title (in real world, it has to be a big chunk of code that you would consider using dynamic import)
It might be implemented as below:
// useActive.js
import { useEffect } from "react";
export function useActive() {
useEffect(() => {
document.title = "(Active) Hello World!";
}, []);
}
And you tried to use it in the BaseComponent:
// BaseComponent.js
function BaseComponent({ isActive }) {
if (isActive) { // <-- call Hooks inside conditions ❌
import("./useActive").then(({ useActive }) => {
useActive();
});
}
return <p>Base</p>;
}
Here you violated the rule "don't call Hooks inside conditions" and will get an Invalid hook call. error.
So instead of dynamically import the hook, you can extract the logic inside the hook and dynamically import it:
// updateTitle.js
export function updateTitle() {
document.title = "(Active) Hello World!"
}
And you do the isActive check inside the hook:
// BaseComponent.js
function BaseComponent({ isActive }) {
useEffect(() => {
if (!isActive) return;
import("./updateTitle").then(({ updateTitle }) => {
updateTitle();
});
}, [isActive]);
return <p>Base</p>;
}
It works fine without violating any rules of hooks.
I have attached a CodeSandbox for you to play around:
You could create a Higher Order Component that fetches the hook and then passes it down as a prop to a wrapped component. By doing so the wrapped component can use the hook without breaking the rules of hooks, eg from the wrapped component's point of view, the reference to the hook never changes and the hook gets called everytime the wrapped component renders. Here is what the code would look like:
export function withDynamicHook(hookName, importFunc, Component) {
return (props) => {
const [hook, setHook] = useState();
useEffect(() => {
importFunc().then((mod) => setHook(() => mod[hookName]));
}, []);
if (!hook) {
return null;
}
const newProps = { ...props, [hookName]: hook };
return <Component {...newProps} />;
};
}
// example of a Component using it:
const MyComponent = ({useMyHook}) => {
let something = useMyHook();
console.log(something)
return <div>myHook returned something, see the console to inspect it </div>
}
const MyComponentWithHook = withDynamicHook('useMyHook', () => import('module-containing-usemyhook'), MyComponent)
To whoever encountered it as well: You can't use Hooks inside dynamically imported components(however, apparently if you does not use hooks even the first example works):
instead of:
const useDynamicDemoImport = (name) => {
const [comp, setComp] = useState(null);
useEffect(() => {
let resolvedComp = false;
import(`#site/src/demos/${name}`)
.then((m) => {
if (!resolvedComp) {
resolvedComp = true;
setComp(m.default);
}
})
.catch(console.error);
return () => {
resolvedComp = true;
};
}, []);
return comp;
};
const DemoPreviewer: FC<DemoPreviewerProps> = (props) => {
comp = useDynamicDemoImport(props.name);
return (
<Paper sx={{ position: "relative" }}>
{comp}
</Paper>
);
};
export default DemoPreviewer
use React Lazy instead and render the component later
const useDynamicDemoImport = (name) => {
const Comp = React.lazy(() => import(`#site/src/demos/${name}`));
return comp;
};
const RootDemoPreviewer: FC<DemoPreviewerProps> = (props) => {
console.log("RootDemoPreviewer");
return (
<React.Suspense fallback={<div>Loading...</div>}>
<DemoPreviewer {...props} />
</React.Suspense>
);
};
const DemoPreviewer: FC<DemoPreviewerProps> = (props) => {
const Comp = useDynamicDemoImport(props.name);
return (
<Paper sx={{ position: "relative" }}>
<Comp />
</Paper>
);
};
export default RootDemoPreviewer
import React, { Component } from 'react';
import {withProvider} from './TProvider'
import ThreeCardMap from './ThreeCardMap';
class Threecard extends Component {
constructor() {
super();
this.state = {
newlist: []
}
}
componentDidMount(){
this.props.getList()
this.setState({newlist: [this.props.list]})
}
// componentDidUpdate() {
// console.log(this.state.newlist);
// }
render() {
const MappedTarot = (this.state.newlist.map((list, i) => <ThreeCardMap key={i} name={list.name} meaningup={list.meaning_up} meaningdown={list.meaning_rev}/>);
return (
<div>
<h1>Three Card Reading</h1>
<div>{ MappedTarot }</div>
</div>
)
}
}
export default withProvider(Threecard);
Hi, I'm trying to create a page that takes data from a tarot card API (https://rws-cards-api.herokuapp.com/api/v1/cards/search?type=major). Unfortunately by the time the data comes in, my map function has already fired. I'm asking to see if there is a way to have the map function wait until the data hits before it fires. Thanks!
Edit: getList function in the Context:
getList = () => {
console.log('fired')
axios.get('https://vschool-cors.herokuapp.com?url=https://rws-cards-api.herokuapp.com/api/v1/cards/search?type=major').then(response =>{
this.setState({
list: response.data
})
}).catch(error => {
console.log(error);
})
}
this.props.getList() is an async function. You are setting the list right after that call which is not correct.
You need to set it in the getList promise then() block.
getList() is an async function and update data for the parent component. So, my solution is just watching the list from the parent component if they updated or not, through getDerivedStateFromProps
class Threecard extends Component {
constructor() {
super();
this.state = {
newlist: []
}
}
// Set props.list to this.state.newList and watch the change to update
static getDerivedStateFromProps(nextProps, prevState) {
return {
newlist: nextProps.list
}
}
componentDidMount(){
this.props.getList()
// Removed this.setState() from here.
}
render() {
const MappedTarot = (this.state.newlist.map((list, i) => <ThreeCardMap key={i} name={list.name} meaningup={list.meaning_up} meaningdown={list.meaning_rev}/>);
return (
<div>
<h1>Three Card Reading</h1>
<div>{ MappedTarot }</div>
</div>
)
}
}
export default withProvider(Threecard);
I am using React-Redux, in a connected component and I want to test if a particular component is rendered. In order for that component to render 2 things must be true:
ListUsers must be an empty array
The securityMode should be basic.
I have already defined the securityMode in my component Props, with no problem. But the ListUsers prop, is coming through redux.
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
This is my component logic that should be tested:
renderNoResourceComponent = () => {
const { usersList, securityMode } = this.props;
const { selectedGroups } = this.state;
const filteredData = filterUserData(usersList, selectedGroups);
if (filteredData && filteredData.length === 0 && securityMode === 'BASIC') {
return (
<div className="center-block" data-test="no-resource-component">
<NoResource>
.............
</NoResource>
</div>
);
}
return null;
};
And this is the test I wrote:
describe('BASIC securityMode without Data', () => {
const props = {
securityMode: 'BASIC',
listUsers: () => {},
usersList: [] // This is the redux prop
};
it('should render NoResource component', () => {
const wrapper = shallow(<UsersOverviewScreen {...props} />);
const renderUsers = wrapper.find(`[data-test="no-resource-component"]`);
expect(renderUsers).toHaveLength(1);
});
});
But I get an error saying the userLists is not defined. How do I pass this redux prop so my component would pass. `I also need that prop for another set of tests, that needs data, which I need to mock.
Can someone guide me through this? Thank you..
What you want to do is export the component before its connocted to Redux and pass all the props it needs manually:
export class UsersOverviewScreen extends Component {
// ... your functions
render() {
return (
// ... your componont
);
}
}
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
export default connect(mapStateToProps)(UsersOverviewScreen);
Now, in your tests you can import { UsersOverviewScreen } form 'path/to/UsersOverviewScreen';. You can create the props and pass it to the component like this:
const mockUsersLists = jest.fn(() => usersList || []);
const wrapper = shallow(<UsersOverviewScreen {...props} usersList={mockUsersLists} />);
I've made a dashboard in React. It has no active updating, no buttons, fields or drop-downs. It will be deployed on a wall TV for viewing. All panels (9 total) are updated through the API call. The initial call (seen below) works, and all JSON data is fetched and the dashboard is initially updated.
BOTTOM LINE PROBLEM: I need to call the API every 30 sec to 1 minute after the initial call to check for updates.
I have attempted "setInterval" inside the componentDidMount() as suggested by people on here answering others' questions and I get an error "await is a reserved word". I've read about forceUpdate() which seems logical for my use case given what the facebook/react page says about it. But, I've read on here as well to stay away from that...
The code below is a light version of the code I'm using. I've removed many of the components and imports for brevity's sake. Any help would be greatly appreciated.
import React, { Component } from 'react';
import Panelone from './Components/Panelone';
import Paneltwo from './Components/Paneltwo';
class App extends Component {
state = {
panelone: [],
paneltwo: []
}
async componentDidMount() {
try {
const res = await fetch('https://api.apijson.com/...');
const blocks = await res.json();
const dataPanelone = blocks.panelone;
const dataPaneltwo = blocks.paneltwo;
this.setState({
panelone: dataPanelone,
paneltwo: dataPaneltwo,
})
} catch(e) {
console.log(e);
}
render () {
return (
<div className="App">
<div className="wrapper">
<Panelone panelone={this.state} />
<Paneltwo paneltwo={this.state} />
</div>
</div>
);
}
}
export default App;
Move the data fetch logic into a seperate function and invoke that function using setInterval in componentDidMount method as shown below.
componentDidMount() {
this.loadData()
setInterval(this.loadData, 30000);
}
async loadData() {
try {
const res = await fetch('https://api.apijson.com/...');
const blocks = await res.json();
const dataPanelone = blocks.panelone;
const dataPaneltwo = blocks.paneltwo;
this.setState({
panelone: dataPanelone,
paneltwo: dataPaneltwo,
})
} catch (e) {
console.log(e);
}
}
Below is a working example
https://codesandbox.io/s/qvzj6005w
In order to use await, the function directly enclosing it needs to be async. According to you if you want to use setInterval inside componentDidMount, adding async to the inner function will solve the issue. Here is the code,
async componentDidMount() {
try {
setInterval(async () => {
const res = await fetch('https://api.apijson.com/...');
const blocks = await res.json();
const dataPanelone = blocks.panelone;
const dataPaneltwo = blocks.paneltwo;
this.setState({
panelone: dataPanelone,
paneltwo: dataPaneltwo,
})
}, 30000);
} catch(e) {
console.log(e);
}
}
Also instead of using setInterval globally, you should consider using react-timer-mixin. https://facebook.github.io/react-native/docs/timers.html#timermixin
For those looking for functional components. You can update the state every n time by creating a setInterval and calling this in the useEffect hook. Finally call the clearInterval method in the clean up function
import React, { useEffect, useState } from "react";
import Panelone from "./Components/Panelone";
import Paneltwo from "./Components/Paneltwo";
function App() {
const [state, setState] = useState({
panelone: [],
paneltwo: [],
});
const getData = async () => {
try {
const res = await fetch("https://api.apijson.com/...");
const blocks = await res.json();
const dataPanelone = blocks.panelone;
const dataPaneltwo = blocks.paneltwo;
setState({
panelone: dataPanelone,
paneltwo: dataPaneltwo,
});
} catch (e) {
console.log(e);
}
};
useEffect(() => {
const intervalCall = setInterval(() => {
getData();
}, 30000);
return () => {
// clean up
clearInterval(intervalCall);
};
}, []);
return (
<div className="App">
<div className="wrapper">
<Panelone panelone={state} />
<Paneltwo paneltwo={state} />
</div>
</div>
);
}
export default App;
I figured I'd chime in with a slightly revised approach that uses recursion via a setTimeout call within the function block. Works the same...maybe slightly cleaner to have the function call itself from within, instead of doing this elsewhere in your code?
This article explains the reasoning in a bit more depth...but I've been using this approach for several dashboards at work - does the job!
Would look something like this:
class MyComponent extends React.Component
//create the instance for your interval
intervalID;
constructor(props) {
super(props);
this.state = {
data: [],
loading: false,
loadingMap: false,
//call in didMount...
componentDidMount() {
this.getTheData()
}
getTheData() {
//set a loading state - good practice so you add a loading spinner or something
this.setState({loading: true}), () => {
//call an anonymous function and do your data fetching, then your setState for the data, and set loading back to false
this.setState({
data: fetchedData,
loading: false
)} }
//Then call the function again with setTimeout, it will keep running at the specified //interval...5 minutes in this case
this.intervalID = setTimeout(
this.getTheData.bind(this),
300000
);
}
}
//Important! Be sure to clear the interval when the component unmounts! Your app might crash without this, or create memory leaks!
componentWillUnmount() {
clearTimeout(this.intervalID);
}
Sorry if the formatting got a little off. Haven't tried this with Hooks yet but I think you'd have a similar implementation in a useEffect call? Has anyone done that yet?
I have seen around a lot of complications about this. No need to have it in the lifecycles or in state or promisses.
In here, the service api is just a simple axios api call
This is my full implementation as I use it with context api(omitting some private code).
In my case I just care about the status response in the api since I know what I need to change. But the api can be really anything you need for/from data-wise.'
export class MyContextApiComponent ..... {
private timeout: ReturnType<typeof setInterval> | undefined
...
...
...
public statsPolling = (S_UUID: string) => {
if (!this.timeout) {
this.timeout = setInterval( () => {
this.statsPolling(S_UUID)
}, 3000)
}
this.state.api.StatisticsService.statsPolling(S_UUID)
.then(res => {
if (res.hasDescStats) {
clearInterval(this.timeout)
this.setState(prevState => ({
...prevState,
...
...
}))
}
})
.catch(e => console.warn('', e))
}
...
...
}
/// in another file in service is the api call itself with axios just checking on the server reply status
export class Statistics implements IStatistics {
public statsPolling: StatsPolling = async S_UUID => {
return axios
.get<{ hasDescStats: boolean }>(`/v2/api/polling?query=${S_UUID}`)
.then(res => {
if (res.status === 200) {
return { hasDescStats: true }
} else {
return { hasDescStats: false }
}
})
}
}
Answer
You can create a function for the componentDidMount code.
import React, { Component } from 'react';
import Panelone from './Components/Panelone';
import Paneltwo from './Components/Paneltwo';
class App extends Component {
state = {
panelone: [],
paneltwo: []
}
code = async () => {
try {
const res = await fetch('https://api.apijson.com/...');
const blocks = await res.json();
const dataPanelone = blocks.panelone;
const dataPaneltwo = blocks.paneltwo;
this.setState({
panelone: dataPanelone,
paneltwo: dataPaneltwo,
})
} catch(e) {
console.log(e);
}
}
componentDidMount() {
}
render () {
return (
<div className="App">
<div className="wrapper">
<Panelone panelone={this.state} />
<Paneltwo paneltwo={this.state} />
</div>
</div>
);
}
}
export default App;
then make a componentDidUpdate
import React, { Component } from 'react';
import Panelone from './Components/Panelone';
import Paneltwo from './Components/Paneltwo';
class App extends Component {
state = {
panelone: [],
paneltwo: []
}
code = async () => {
try {
const res = await fetch('https://api.apijson.com/...');
const blocks = await res.json();
const dataPanelone = blocks.panelone;
const dataPaneltwo = blocks.paneltwo;
this.setState({
panelone: dataPanelone,
paneltwo: dataPaneltwo,
})
} catch(e) {
console.log(e);
}
}
componentDidMount() {
this.code()
}
componentDidUpdate(){
this.code()
}
render () {
return (
<div className="App">
<div className="wrapper">
<Panelone panelone={this.state} />
<Paneltwo paneltwo={this.state} />
</div>
</div>
);
}
}
export default App;