How should I render multi-state component, which fetches API? - javascript

I am using React with Node.js.
I have a component ItemList, which fetches some API in the componentDidMount() method, because it allows me to easily render a "loading state".
I need to pass a state to this component, which would change the API's url using a toggle button. This toggle button is an individual component (ToggleButton). These two components are siblings and I am using parent as a way for these components to communicate.
I thought Context is perfect for this kind of job. The issue is, that using React's Context is just re-rendering (calling the function render() of a ItemList and not remounting the component, thus not calling the componentDidMount() method or even constructing the component again).
export const ToggleContext = React.createContext({
switched: false
});
export default class Items extends React.Component
{
constructor(props)
{
super(props)
this.state = {
switched: false
}
this.toggleSwitch = () => {
this.setState(state => ({
switched: !state.switched
}))
}
}
render()
{
console.log(this.state.switched)
return (
<>
<div class="page-header">
<div class="container-fluid">
<h2 class="h5 mb-0">Items</h2>
</div>
</div>
<section class="py-0">
<div class="container-fluid">
<ToggleContext.Provider value={this.state.switched}>
<ToggleButton callFunc={this.toggleSwitch}/>
<ItemList loadWeb={this.state.switched}/>
</ToggleContext.Provider>
</div>
</section>
</>
)
}
}
ItemList component is heavily inspirated by React docs
I am succesfully getting the changed state through ToggleButton in parent component, sending it to ItemList and picking it up as props.loadWeb, I am just not sure if my implementation is wrong or even if what I demand is possible with Context.
Is it possible to reconstruct the whole component using context, should I use refs, sould I fetch the API in the render() method, etc.?

So it appears that you need your componentDidMount called on every toggle of ItemList. But as you noted, it does not remount every time. As such, using componentDidUpdate inside your ItemList is more appropriate to toggle that API link.
Docs: https://reactjs.org/docs/react-component.html#componentdidupdate
I also encourage you to check out this article on using Hooks vs Classes: https://blog.bitsrc.io/6-reasons-to-use-react-hooks-instead-of-classes-7e3ee745fe04

What you are doing seems right, I would say, you could probably simplify (in my opinion) by using function components along with hooks.
import React, {useState} from 'react';
const Items = () => {
const [toggled, toggle] = useState(false);
return (
<div class="container-fluid">
<div class="page-header">
<h2 class="h5 mb-0">Items</h2>
</div>
<section class="py-0">
<ToggleButton callFunc={toggle(!toggled)}/>
<ItemList loadWeb={toggled}/>
</section>
</div>
);
};

Related

Adding componentDidMount to React component results in syntax error

my home component has:
const Home = () => (
<div>
<Head title="Home" />
<Nav />
<div className="container o_block u_blue">
<div className="notification">
This container is <strong>centered</strong> on desktop.
</div>
</div>
</div>
)
export default Home
and I'm trying to add some DOM manipulation into that component:
componentDidMount() {
let burger = document.querySelector('.burger');
let nav = document.querySelector('#'+burger.dataset.target);
burger.addEventListener('click', function(){
burger.classList.toggle('is-active');
nav.classList.toggle('is-active');
});
}
const Home = () => (
<div>
<Head title="Home" />
<Nav />
<div className="container o_block u_blue">
<div className="notification">
This container is <strong>centered</strong> on desktop.
</div>
</div>
</div>
)
export default Home
but unfortunately I am getting a:
SyntaxError: Unexpected token, expected ";" (8:20)
what am I doing wrong and where should I put the method?
Home is a presentational component in your code. Which means a presentational component is like a pure function in Java Script. A Presentational component doesn’t call any React life cycle method and doesn’t modify React state. It only takes props and returns jsx elements. This is also called as stateless component in react.
If you want to play with React life cycle methods then you should go with statefull component.
componentDidMount is one of React life cycle method so it’s not accessible in presentational or functional components in React.
Edit:
If you want to do DOM manipulation before component initial render then do DOM manipulation in componentWillMount() method but please see this method is deprecated in latest React versions.
If you want to do DOM manipulation after first render then do that in componentDidMount() method. This is the one wr you also make axios calls and do setState accordingly. I would recommend you to go with componentDidMount().
import React, { Component} from "react";
export default class Home extends Component {
componentDidMount() {
let burger = document.querySelector('.burger');
let nav = document.querySelector('#'+burger.dataset.target);
burger.addEventListener('click', function(){
burger.classList.toggle('is-active');
nav.classList.toggle('is-active');
});
}
render(){
return(
<div>
<Head title="Home" />
<Nav />
<div className="container o_block u_blue">
<div className="notification">
This container is <strong>centered</strong> on desktop.
</div>
</div>
</div>
)
}
}
Please excuse me if there are any typo error because I am answering in my mobile
You need to transform your component into a class-based component, like this :
export default class Home extends React.Component {
render() {
// Return the JSX code
}
componentDidMount() {
// Your init code
}
}
But I really suggest you to take a look at the official React doc as it's a fairly simple mistake.

this.setState React bind issue

I am attempting to pass the changePage class method into a child component called SideBar. When the changePage method is then triggered by an onClick event in the child component I receive the following error:
Uncaught TypeError: this.SetState is not a function
From what I could find in other similar posts I need to bind the changePage method to this. I have done that but I still can't manage to get is to work.
I also saw many suggestions to use ES6 arrow functions for my methods but I get the exact same error message if I do.
I'm still quite new at web development and any help would be appreciated.
Parent Component called Main:
import React from 'react';
import Content from './Content';
import Sidebar from './Sidebar';
export default class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedPage: 'home',
pages: ['home','about','skills','contact'],
};
this.changePage = this.changePage.bind(this);
}
changePage(page) {
console.log(page);
this.SetState({
selectedPage: page,
pages: ['home','about','skills','contact']
});
};
render() {
return (
<div>
<div id="sidebar" className="side-bar">
<Sidebar
changePage={this.changePage}
selectedPage={this.state.selectedPage}
pages={this.state.pages}
/>
</div>
<div id="main" className="main-content">
<Content
selectedPage={this.state.selectedPage}
/>
</div>
</div>
)
}
}
Child Component:
import React from 'react';
export default class Sidebar extends React.Component {
render() {
console.log("content props",this.props);
const buttons = this.props.pages.map(button =>
<span
className='nav-button'
id={button}
key={button}
onClick={() => this.props.changePage(button)}
>
<img src={`./app/images/${button}.svg`} />
</span>
);
return (
<div>
<span>
<img className='headshot' src='./app/images/headshot.jpg' />
</span>
<div className='nav-container'>
{buttons}
</div>
</div>
)
}
}
You have syntax error: this.SetState. Change it to this.setState.
The typo in setState was causing the error, but you may want consider a cleaner way to bind the parent this on the changePage prop. Your way works, but if you changed sidebar Component inclusion to:
<Sidebar
changePage={ (page) => this.changePage(page)}
selectedPage={this.state.selectedPage}
pages={this.state.pages}
/>
That may make it clearer what is going on (changePage prop is a function which takes one parameter and passes that parameter to instance method this.changePage), and removes the binding gymnastics in the constructor.

Higher Order Component (self) wrapping in React

Good Evening !!
My question title might be off, but here is the problem i'm trying to address. I have a component (Content) which listens for DOM event and call Board (instance which facilitate communication between Containers and their consuming Applications).
import Board from '../Board';
class Content extends React.Component {
constructor(props){
super(props);
}
componentDidMount(){
window.addEventListener("message", this.handleCheck, false);
}
componentWillUnmount(){
window.removeEventListener("message", this.handleCheck, false);
}
handleCheck =(event) => {
const { board } = this.props;
board.call({
size: event.detail.size,
panel: event.detail.panel,
.....
})
}
render(){
return null
}
}
I can consume/call Content component as mentioned below,
import Manager from '../../Manager'
const Example = () => (
<div>
<Manager>
<Content pageType="A4" />
</Manager>
</div>
);
The Manager component utilizes the Board API to manage call requests, and maintains the state of it's children. The component provided as children to Manager should also support Board prop.
In Example component i would like to call <Content pageType="A4" /> instead of wrapping with <Manager> and somehow use the <Manager> within the Content component definition (inside the Content component to leverage Manager). i.e
const Example = () => (
<div>
<Content pageType="A4" />
</div>
);
Pretty sure you are just looking for the basic HOC implemenation...
function withManager(Component) {
class WithManager extends React.Component {
...withManagerStuff
render() {
return <Component/>
}
}
return WithManager;
}
and then where you want to use your components with the shared HOC (ContentWithManager) you can do something like - module.exports = withManager(Content)
This stuff gets complex quickly.
I may be off, as I am slightly confused with what you are trying to do. However, I think you need to pass the wrapped (child) component to the wrapping (parent) component.
Here are two HOC examples of how to do this:
Note: Examples use redux and react-router, but the same pattern should
work without redux or react-router.
Redirect HOC: redirect.hoc.js
Redirect HOC Example: trans.app.container.js
<!-- Redirect HOC Example Code -->
<Route component={RedirectHoc(MainContentContainer)} />
Authorization HOC: authorization.hoc.js
<!-- Authorization HOC Example Code -->
<Route exact path="/beer/add" component={AUTHORIZE_ADMIN(BeerAddComponent)}/>

Display Component based on another component lifecycle

I have recently encountered an issue regarding the usage of one of my costum components. I have created a "Chargement" (Loading in French) Component for a project I am working on.
This component is a simple circular spinner with a dark background that when displayed, informs the user that an action is going on.
import React, {Fragment} from 'react';
import { CircularProgress } from 'material-ui/Progress';
import blue from 'material-ui/colors/blue';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
const styles = theme => ({
chargement: {
position: 'fixed',
left: '50%',
top: '50%',
zIndex: 1
}
});
class Chargement extends React.Component {
render () {
const { classes } = this.props;
if (this.props.chargement) {
return (
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
);
} else {
return null;
}
}
}
const mapStateToProps = (state) => {
return {
chargement: state.App.chargement
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
}, dispatch);
};
Chargement.propTypes = {
classes: PropTypes.object.isRequired
};
let ChargementWrapped = withStyles(styles)(Chargement);
export default connect(mapStateToProps, mapDispatchToProps)(ChargementWrapped);
This component is displayed based on a boolean variable in my redux Store called "chargement".
It works like a charm whenever I am using it to make api call and load data. However, one of the components in my Web App takes quite a bit of time to render (1-2 seconds). This component renders a pretty big list of data with expansion panels. I tried to set my display variable based on the componentWillMount and componentDidMount functions.
class ListView extends React.Component {
componentWillMount () {
this.props.setChargement(true);
}
componentDidMount () {
this.props.setChargement(false);
}
However with this particular case the "chargement" component never displays.
I also tried to create a "Wrapper Component" in case the issue came from my "chargement" component being somewhat related to the re-rendered component as a children. :
export default class AppWrapper extends React.Component {
render () {
return (
<Fragment>
<Reboot />
<EnTete />
<Chargement />
<App />
</Fragment>
);
}
}
The "App " component is the one that takes a few seconds to render and that I am trying to implement my "chargement" component for. I am pretty sure this as to do with the component lifecycle but everything I tried so far failed.
My current stack is : React with Redux and MaterialUi
What am I missing ?
Thanks for your help!
Ps: You might want to check the explanation and precision I added on the main answer comments as they provide further context.
Not sure if I understood correctly, but I think the problem is simply your API call takes more time than your component mounting cycle, which is totally normal. You can solve the problem by rearranging a bit the places where to put the IO.
Assuming you are making the API call from AppWrapper, dispatch the Redux action in componentDidMount i.e. fetchListItems(). When the API call resolves, the reducer should change its internal loading value from true to false. Then, AppWrapper will receive chargement as a prop and its value will be false. Therefore, you should check what this value is in AppWrapper's render method. If the prop is true, you render the Chargement component or else, render ListView.
Also, try always to decouple the IO from the view. It's quite likely that you'll need to reuse Chargement in other situations, right? Then, make it a simple, generic component by just rendering the view. Otherwise, if you need to reuse the component, it will be coupled to one endpoint already. For this, you can use a Stateless Functional Component as follows:
const Chargement = () =>
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
I found a way to fix my issue that does not involve the use of the "chargement" component like I had initially planned. The issue revolved around the usage of Expansion Panels from the Material-Ui-Next librairy.
The solution I found is the following :
Instead of trying to show a Loading component while my list rendered, I reduced the render time of the list by not rendering the ExpansionDetail Component unless the user clicked to expand it.
This way, the list renders well under 0.2 seconds on any devices I've tested. I set the state to collapsed: false on every panel inside the constructor.
class ListItem extends React.Component {
constructor (props) {
super(props);
this.state = {
collapsed: false
};
this.managePanelState = this.managePanelState.bind(this);
}
managePanelState () {
if (this.state.collapsed) {
this.setState({collapsed: false});
} else {
this.setState({collapsed: true});
}
}
Then I use the onChange event of the expansion panel to switch the state between collapsed and not on every ListItemDetail element.
<ExpansionPanel onChange={() => this.managePanelState()}>
I guess sometimes the solution isn't where you had initially planned.
Thanks to everyone who took time to look into my problem!

Push state updates directly to conditionally rendered React component?

I'm building a React app and have a tab section, where clicking on a tab will render a specific component.
First, my parent component:
class Interface extends Component {
constructor(props) {
super(props);
this.chooseTab = this.chooseTab.bind(this);
this.state = {
current: 'inventory',
inventory: [],
skills: [],
friends: [],
notifications: {}
};
}
chooseTab(tabID) {
this.setState({ current: tabID });
chooseComponent(tabID) {
if (tabID === 'skills') return Skills;
else if (tabID === 'inventory') return Inventory;
else if (tabID === 'friends') return FriendsList;
}
render() {
const tabID = this.state.current;
const CustomComponent = this.chooseComponent(tabID);
return (
<div className='column' id='interface'>
<div className='row' id='tabs'>
<ActiveTab
current={this.state.current}
tabID='skills'
chooseTab={this.chooseTab}
/>
<ActiveTab
current={this.state.current}
tabID='inventory'
chooseTab={this.chooseTab}
/>
<ActiveTab
current={this.state.current}
tabID='friends'
chooseTab={this.chooseTab}
/>
</div>
<TabBody>
<CustomComponent
data={this.state[tabID]}
notifications={this.state.notifications}
/>
</TabBody>
</div>
);
}
}
Which renders three ActiveTab's and one TabBody:
const ActiveTab = (props) => {
const isActive = props.tabID === props.current ? 'active' : 'inactive';
return (
<button
className={`active-tab ${isActive}`}
onClick={() => props.chooseTab(props.tabID)}
>{props.tabID}
</button>
);
};
const TabBody = (props) => {
return (
<div className='tab-body'>
{props.children}
</div>
);
};
This works fine, and it's clearly an intended way of handling this issue. However, I'd like to be able to move the notifications state object into my FriendsList component (since it's unique to friends) and also trigger a setState in it from another component even if FriendsList is not the component currently rendered by the TabBody (i.e., unmounted).
I'm currently triggering remote state changes using a globally available actions closure where a specific action and setState is defined in the ComponentWillMount() lifecycle method of the target element, and it's executed from whatever component is activating the remote state change. I've left those out of Interface for brevity.
How would you handle this? Is my only option to leave notifications in Interface, define actions there, and let React handle passing props down? Or is there a way to build my tab components and conditional rendering so I can trigger state changes from a separate component to a non-displayed component in one of the tabs, i.e move notifications and its corresponding action to FriendsList?
I've passed through a problem similar than yours weeks ago, if you are not decided to adopts some state manager like Redux, MobX or even Flux I think you should pass props down to their child's.

Categories