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!
Related
so I am new to React. Loving it so far. However, I am having a basic question which doesn't have a clear answer right now.
So, I am learning how to lift the state of a component.
So here's a reproducible example.
index.js
import React from "react";
import ReactDOM from "react-dom"
import {Component} from "react";
// import AppFooter from "./AppFooter";
import AppContent from "./AppContent";
import AppHeader from "./AppHeader";
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.bundle.min'
import './index.css'
class App extends Component{
constructor(props) {
super(props);
this.handlePostChange = this.handlePostChange.bind(this)
this.state = {
"posts": []
}
}
handlePostChange = (posts) => {
this.setState({
posts: posts
})
}
render() {
const headerProps = {
title: "Hi Keshav. This is REACT.",
subject: "My Subject is Krishna.",
favouriteColor: "blue"
}
return (
<div className="app">
<div>
<AppHeader {...headerProps} posts={this.state.posts} handlePostChange={this.handlePostChange}/>
<AppContent handlePostChange={this.handlePostChange}/>
</div>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById("root"))
I am trying to lift the state of posts which is changed in AppContent to AppHeader.
Here's my AppContent.js and AppHeader.js
// AppContent.js
import React, {Component} from "react";
export default class AppContent extends Component{
state = {
posts: []
}
constructor(props) {
super(props); // constructor
this.handlePostChange = this.handlePostChange.bind(this)
}
handlePostChange = (posts) => {
this.props.handlePostChange(posts)
}
fetchList = () => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((response) =>
response.json()
)
.then(json => {
// let posts = document.getElementById("post-list")
this.setState({
posts: json
})
this.handlePostChange(json)
})
}
clickedAnchor = (id) => {
console.log(`Clicked ${id}`)
}
render() {
return (
<div>
<p>This is the app content.</p>
<button onClick={this.fetchList} className="btn btn-outline-primary">Click</button>
<br/>
<br/>
<hr/>
<ul>
{this.state.posts.map((item) => {
return (
<li id={item.id}>
<a href="#!" onClick={() => this.clickedAnchor(item.id)}>{item.title}</a>
</li>
)
})}
</ul>
<hr/>
<p>There are {this.state.posts.length} entries in the posts.</p>
</div>
)
}
}
// AppHeader.js
import React, {Component, Fragment} from "react";
export default class AppHeader extends Component{
constructor(props) {
super(props); // constructor
this.handlePostChange=this.handlePostChange.bind(this)
}
handlePostChange = (posts) => {
this.props.handlePostChange(posts)
}
render() {
return (
<Fragment>
<div>
<p>There are {this.props.posts.length} posts.</p>
<h1>{this.props.title}</h1>
</div>
</Fragment>
)
}
}
So here's the main question. As we see, that I am calling the dummy posts api and trying to show the titles of the json object list returned by it.
The posts state is actually updated in AppContent and is shared to AppHeader by lifting it to the common ancestor index.js
However, here's what I have observed.
When I keep this code running using npm start I see that anytime I make a change in any place, it refreshes. I was under the impression that it renders the whole page running on localhost:3000.
Say here's my current situation on the web page:
Now, say I make a change in just AppContent.js, then here's how it looks then:
In here, we see that it's still showing 100 posts in case of AppHeader. Is this expected that react only reloads the component and not the whole page. When I refresh the whole page, it shows 0 posts and 0 posts in both the places. Now have I made a mistake in writing the code ? If yes, how do I fix this ?
Thank you.
In case the question is not clear please let me know.
In here, we see that it's still showing 100 posts in case of AppHeader. Is this expected that react only reloads the component and not the whole page.
It's not React, per se, that's doing that. It's whatever you're using to do hot module reloading (probably a bundler of some kind, like Webpack or Vite or Rollup or Parcel or...). This is a very handy feature, but yes, it can cause this kind of confusion.
Now have I made a mistake in writing the code ?
One moderately-signficant one, a relatively minor but important one, and a couple of trivial ones:
posts should either be state in App or AppContent but not both of them. If it's state in both of them, they can get out of sync — as indeed you've seen with the hot module reloading thing. If you want posts to be held in App, fetch it there and provide it to AppContent as a property. (Alternatively you could remove it from App and just have it in AppContent, but then you couldn't show the total number of posts in App.)
When you're rendering the array of posts, you need to have a key on each of the li items so that React can manage the DOM nodes efficiently and correctly.
There's no need to wrap a Fragment around a single element as you are in AppHeader.
If you make handlePostChange an arrow function assigned to a property, there's no reason to bind it in the constructor. (I would make it a method instead, and keep the bind call, but others like to use an arrow function and not bind.)
There's no reason for the wrapper handlePostChange functions that just turn around and call this.props.handlePostChange; just use the function you're given.
Two issues with your fetch call:
You're not checking for HTTP success before calling json. This is a footgun in the fetch API I describe here on my very old anemic blog. Check response.ok before calling response.json.
You're ignoring errors, but should report them (via a .catch handler).
When hiddenLogo changes value, the component is re-rendered. I want this component to never re-render, even if its props change. With a class component I could do this by implementing sCU like so:
shouldComponentUpdate() {
return false;
}
But is there a way to do with with React hooks/React memo?
Here's what my component looks like:
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import ConnectedSpringLogo from '../../containers/ConnectedSpringLogo';
import { Wrapper, InnerWrapper } from './styles';
import TitleBar from '../../components/TitleBar';
const propTypes = {
showLogo: PropTypes.func.isRequired,
hideLogo: PropTypes.func.isRequired,
hiddenLogo: PropTypes.bool.isRequired
};
const Splash = ({ showLogo, hideLogo, hiddenLogo }) => {
useEffect(() => {
if (hiddenLogo) {
console.log('Logo has been hidden');
}
else {
showLogo();
setTimeout(() => {
hideLogo();
}, 5000);
}
}, [hiddenLogo]);
return (
<Wrapper>
<TitleBar />
<InnerWrapper>
<ConnectedSpringLogo size="100" />
</InnerWrapper>
</Wrapper>
);
};
Splash.propTypes = propTypes;
export default Splash;
As G.aziz said, React.memo functions similarly to pure component. However, you can also adjust its behavior by passing it a function which defines what counts as equal. Basically, this function is shouldComponentUpdate, except you return true if you want it to not render.
const areEqual = (prevProps, nextProps) => true;
const MyComponent = React.memo(props => {
return /*whatever jsx you like */
}, areEqual);
React.memo is same thing as React.PureComponent
You can use it when you don't want to update a component that you think is static so, Same thing as PureCompoment.
For class Components:
class MyComponents extends React.PureCompoment {}
For function Components:
const Mycomponents = React.memo(props => {
return <div> No updates on this component when rendering </div>;
});
So it's just creating a component with React.memo
To verify that your component doesn't render you can just
activate HightlightUpdates in react extension and check your components reaction on
rendering
We can use memo for prevent render in function components for optimization goal only. According React document:
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.
According to react documentation:- [https://reactjs.org/docs/react-api.html][1]
React. memo is a higher order component. If your component renders the
same result given the same props, you can wrap it in a call to React.
memo for a performance boost in some cases by memoizing the result.
This means that React will skip rendering the component, and reuse the
last rendered result.
For practical understanding I came across these two videos they are very good if you wanna clear concepts also, better to watch so it'll save your time.
Disclaimer:- This is not my YouTube channel.
https://youtu.be/qySZIzZvZOY [ useMemo hook]
https://youtu.be/7TaBhrnPH78 [class based component]
I want to organize my code into components and containers folder structure. I will be using Redux with actions and reducers.
What do you think that StartButton is rather component or container? It will not be connected to the redux store, but it has its own state and some decision logic, so maybe it isn't so dumb...
I know my question has something to do with opinions, but perhaps someone can provide me with some insights and what's regarded as best practice.
Here's my StartButton component:
import React, { Component } from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import './style.css';
class StartButton extends Component {
constructor() {
super();
this.state = {
startWasClicked: false,
};
}
handleStartButton = () => {
this.setState({ startWasClicked: true });
};
beerListingView() {
if (this.state.startWasClicked) {
return <div>YES! It was clicked!</div>;
}
// Else return just single <div />
return <div />;
}
render() {
return (
<div>
<div className="StartButton-container">
<RaisedButton
label="Start Here"
className="StartButton-main"
onClick={this.handleStartButton}
/>
</div>
{this.beerListingView()}
</div>
);
}
}
export default StartButton;
In my apps, containers refer to react components that are connected to the Redux store.
components are all the others. Most of them make use of the React state to toggle UI stuff for example. Thats perfectly fine.
Check out https://github.com/react-boilerplate/react-boilerplate for example
I'm building a desktop app using React and Electron.
Since it's growing fast, I realized I need some kind of state management like Redux to avoid passing many properties between components.
I started reading Redux official documentation but still cannot figure out how to implement it in my case. I'm stuck!
For example, I have a main App component that renders many sub-components. One of them has a button. When clicked, it should dispatch an "event" to the store so the main App can act in consequence. How can I accomplish that?
I cannot find the concept of events and I've hit a wall on how to even start using Redux.
Why events? Because it seems silly to me to dispatch an action and modify app state in this case. I just want to inform the root component to dispatch an action based on a user action.
User interacts with a presentational component that should tell a container component to make an API call or start capturing audio/camera for example.
For what I know up to now, the only way to accomplish this is to mutate state so another component listening for changes detects a special value that means "hey, let's do this", then mutate state again to say "hey, I'm doing this", and when it's done state changes again with "hey, it's done".
Can someone point me in the right direction please?
User interacts with a presentational component that should tell a container component to make an API call or start capturing audio/camera for example.
Perhaps your container component is doing more than it should. Consider a situation where React components do no more than two things:
Display DOM elements based on props
Handle user input (dispatch events)
If you were not using redux and wanted to make an API call when clicking a button, that might look something like:
class App extends Component {
state = { data: {} }
makeAPICall() {
fetch(url).then(data => this.setState({ data }))
}
render() {
<Child
data={this.state.data}
makeAPICall={this.makeAPICall}
/>
}
}
let Child = ({ data, makeAPICall }) => (
<button onClick={makeAPICall}>Call API!</button>
)
The App component is responsible for storing global state and handling events, but we have to pass down that state and App's handlers through the component tree, quite possibly through components that will never themselves use those props.
By adding Redux your application now has a much better place to handle side effects like API calls or turning a camera on. Middleware!
Let this (crappy) illustration help you:
So now instead your App component can be just a normal presentational component like all of the others, simply displaying data based on store props and handling any user input / dispatching actions if need be. Let's update the above example using the thunk middleware
// actions.js
export let makeAPICall = () => {
return dispatch => {
fetch(url).then(data => dispatch({
type: 'API_SUCCESS',
payload: data,
})).catch(error => dispatch({ type: 'API_FAIL', payload: error }))
}
}
// Child.js
import { connect } from 'react-redux'
import { makeAPICall } from './actions'
let Child = ({ dispatch }) => (
<button onClick={() => dispatch(makeAPICall())}>Call API!</button>
)
export default connect()(Child)
Thinking about React applications this way is very powerful. The separation of concerns is very well laid out. Components display stuff and handle events. Middleware takes care of any side effects (if there need to be any) and the store simply is an object that will cause React to re-render in case its data changes.
UPDATE: "The Modal Problem"
React apps may have some global stuff like modals and tooltips. Don't think about the "open modal" event.. think "what is the current modal content?".
A modal setup may look something along these lines:
// modalReducer.js
function reducer (state = null, action) {
if (action.type === 'UPDATE_MODAL') {
return action.payload
}
// return default state
return state
}
// App.js
let App = connect(state => ({ modal: state.modal }))(
props =>
<div>
<OtherStuff />
<Modal component={props.modal} />
</div>
)
// Modal.js
let Modal = props =>
<div
style={{
position: 'fixed',
width: '100vw', height: '100vh',
opacity: props.component ? 1 : 0,
}}
>
{props.component}
</div>
// Child.js
let Child = connect()(props =>
<button onClick={e =>
dispatch({
type: 'UPDATE_MODAL'
payload: <YourAwesomeModal />
})
}>
Open your awesome modal!
</button>
)
This is just an example, but would work great! when state.modal is null your Modal has 0 opacity and won't show. When you dispatch UPDATE_MODAL and pass in a component, the modal will show whatever you dispatch and change the opacity to 1 so you can see it. Later you can dispatch { type: 'UPDATE_MODAL', payload: null } to close the modal.
Hopefully this gives you some things to think about!
Definitely read this answer by Dan. His approach is similar but stored modal "metadata" vs the component itself which lends itself better to Redux fanciness like time travel etc.
Is the reason you think it seems silly because you don't want your presentational components to be redux-aware? If so mapDispatchToProps and bindActionCreators might help tidy things up, for example:
// App.js
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { someAction } from './actions';
import Button from './Button';
const App = ({ onButtonClick }) => (
<div>
Hello.
<Button onClick={onButtonClick}>Click me.</Button>
</div>
);
export default connect(null, dispatch => {
return bindActionCreators({
onButtonClick: someAction
}, dispatch);
})(App);
// Button.js
import React from 'react';
export default Button = ({ onClick, children }) => <button onClick={onClick}>{children}</button>;
As you can see only the connected container component is aware of the action, the Button (and even the App) are unaware that click triggers an action.
For what it's worth, I had a similar problem (click a button elsewhere in the tree and cause a map to reset its viewport) and solved it with a simple incremental key.
Button dispatches action:
export const RESET_MAP = "RESET_MAP";
export const resetMap = () => {
return {
type: RESET_MAP,
};
};
In reducer:
case RESET_MAP:
return Object.assign({}, state, {
setvar: state.setvar + 1
});
In map:
static getDerivedStateFromProps(newProps, state) {
var newState = null;
if (newProps.setvar !== state.setvar) {
newState = {
setvar: newProps.setvar,
[other magic to reset the viewport]
}
}
return newState;
Say I have a comp that is inside of a Scene (react-native-router-flux). It lets people choose their favorite fruits.
import React, {Component} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {MKCheckbox} from 'react-native-material-kit';
var styles = StyleSheet.create({});
export default class PickAFruit extends Component {
render() {
console.log(this.props.fruits);
return (
<View>
{
this.props.fruits.map((x)=> {
return (
<View key={x.key}>
<Text>{x.key}</Text>
<MKCheckbox checked={this.props.checked} key={x.key} onCheckedChange={(e) => {
this.props.update(e, '' + x.key)
}}/>
</View>
)
})
}
</View>
)
}
}
In the parent comp I'm loading the list of fruits from an API in the didMount:
componentDidMount() {
ApiInst.getFruits().then((fruits) => {
console.log(fruits);
console.log(this.props.fruits);
this.props.fruits = fruits;
});
}
I'm also setting a default fruits array in the parent class. It seems like the properties won't load via the API though, the list of fruit is always the "unknown" value, never the new values. Do I need to load the list of fruits before the Profile scene is loaded? When is the correct time to set properties for a component if they will come from an API?
setState seems like the easy answer but these settings don't "feel" like state, they feel like properties that would be injected at build-time (i.e. when the component is built, not the app). Is this a distinction without a real difference?
You can't modify props. Props are passed from parent to child component, and only the parent can change them.
Use setState instead:
this.setState({fruits: fruits});
And access them from state:
<PickAFruit fruits={this.state.fruits} />
You may also want to set a default state in the component constructor:
constructor(props) {
super(this);
this.state = {fruits: null};
}
this.props.fruits = fruits;
won't effect child component, and to be honest - I'm not sure it will work at all. If you don't want to use flux architecture I think the best solution is to update parent's state on componentDidMount() and pass it as props to child component:
componentDidMount() {
ApiInst.getFruits().then((fruits) => {
this.setState({fruits: fruits});
});
}
render() {
return (
<PickAFruit fruits={this.state.fruits} />
);
}
Every state change will invokre render() method, so after API call PickAFruit component will be rerendered, with fruits passed as a props.