React setState odd behaviour - javascript

New to React, in the following code I am passing data between two components via the parent. Flow is from Search to the parent App then to another child Sidebar. I am able to send to both from Search to App and App to Sidebar individually but for some reason setState is not behaving as expected making the link to trigger a refresh of <Search updateMenu={this.handleSearchResult} /> as you can see in the console.log code comments below:
import React, { Component } from 'react';
import './App.css';
import Search from './Search';
import Sidebar from './Sidebar';
class App extends Component {
constructor() {
super()
this.state = {
menu: []
}
}
handleSearchResult = (array) => {
// always the correct value
console.log('in ', array);
this.setState( {menu: menuList})
// 1st call : empty
// 2nd call : previous value not showing on 1st call + new value as expected
// does not trigger <Sidebar list={this.state.menu}/>
console.log('out', this.state.menu);
}
render() {
return (
<div className="App">
// not refreshing
<Search updateMenu={this.handleSearchResult} />
<Sidebar list={this.state.menu}/>
</div>
);
}
}
export default App;

Logging this.setState(). Is not so straight forward. this.setState() is asynchronus.
Here is a reference on Medium.

Related

REACT ROUTER - Class component (only) - rendering page - JSON File

i'm working on react router project (im beginner) to improve my skillz.
my problem:
I have a JSON file (Dragon ball Z). When i click on characters (like goku) i want to show his biography.
Actually when i click on goku every biography of each characters are showed.
I know how to do it with function component (useLocation ect..) but i'm totally stuck wicth class component because i can't use hooks in it, what is the good way to do it ?
here is the project :
DBZ REACT ROUTES
Thanks
Using react-router-dom v6 we can use useParams to read the params key within function component.
With class component you can write an HOC function to archive the same thing
a higher-order component is a function that takes a component and returns a new component
import { useParams } from 'react-router-dom'
function withParams(Component) {
return props => <Component {...props} params={useParams()} />;
}
class Charbio extends React.Component {
componentDidMount() {
let { id } = this.props.params;
// get the bio by id here
}
render() {
return API.map((element) => {
return <p>{element.bio}</p>;
});
}
}
export default withParams(Charbio);

Initialize state with dynamic key based on props in reactJS

How to initialize state with dynamic key based on props? The props is a data fetched from external source (async). So the props will change when the data is succesfully downloaded. Consider a component like this.
edit: I want to make the state dynamic because I want to generate a dialog (pop up) based on the item that is clicked. the DialogContainer is basically that. visible prop will make that dialog visible, while onHide prop will hide that dialog. I use react-md library.
class SomeComponent extends React.Component {
constructor() {
super();
this.state = {};
// the key and value will be dynamically generated, with a loop on the props
// something like:
for (const item of this.props.data) {
this.state[`dialog-visible-${this.props.item.id}`] = false}
}
}
show(id) {
this.setState({ [`dialog-visible-${id}`]: true });
}
hide(id) {
this.setState({ [`dialog-visible-${id}`]: false });
}
render() {
return (
<div>
{this.props.data.map((item) => {
return (
<div>
<div key={item.id} onClick={this.show(item.id)}>
<h2> Show Dialog on item-{item.id}</h2>
</div>
<DialogContainer
visible={this.state[`dialog-visible-${item.id}`]}
onHide={this.hide(item.id)}
>
<div>
<h1> A Dialog that will pop up </h1>
</div>
</DialogContainer>
</div>
);
})}
</div>
)
}
}
// the data is fetched by other component.
class OtherComponent extends React.Component {
componentDidMount() {
// fetchData come from redux container (mapDispatchToProps)
this.props.fetchData('https://someUrlToFetchJSONData/')
}
}
The data then is shared via Redux.
However, based on my understanding so far, state can be updated based on props with componentWillReceiveProps or the new getDerivedStateFromProps (not on the constructor as above). But, how to do that on either method?
The example here only explains when the state is initialized on the constructor, and call setState on either cWRP or gDSFP. But, I want the key value pair to be initialized dynamically.
Any help/hint will be greatly appreciated. Please do tell if my question is not clear enough.
import React from 'react';
import {connect} from 'react-redux';
import {yourAction} from '../your/action/path';
class YourClass extends React.Component {
state = {};
constructor(props){
super(props);
}
componentDidMount(){
this.props.yourAction()
}
render() {
const {data} = this.props; //your data state from redux is supplied as props.
return (
<div>
{!data ? '' : data.map(item => (
<div>{item}</div>
))}
</div>
)
}
}
function mapStateToProps(state) {
return{
data:state.data //state.data if that is how it is referred to in the redux. Make sure you apply the correct path of state within redux
}
}
export default connect(mapStateToProps, {yourAction})(YourClass)
If you do this, <div>{item}</div> will change as you change the data state. The idea is to just map the redux state to your class props - you don't have to map the props back to the state. The render() automatically listens to changes in props supplied by redux. However, if you do want to somehow know redux state change in events, you can add the following functions.
componentWillReceiveProps(newProps){
console.log(newProps)
}
getDerivedStateFromProps(nextProps, prevState){
console.log(nextProps);
console.log(prevState);
}

Redux component not updating on state change?

Update: Apparently the bug is fixed. I never pushed a solution, so I'm still not sure what the problem/solution.
Essentially what's going on is that I have a child component that is being passed state from the main application component. I know that works fine, as I see the default value of the state showing up properly.
When the child mounts, it fires an ajax call to fetch some data, and then fires an action to update the state value accordingly (Other packages use this fetch call and it works fine as well). I can see all of this is working as expected by taking a look at the Redux chrome devtool. It shows the action being fired, and that the state has changed from the default value to the value it fetched.
The problem is that the page still shows that default value and not the new state value. So I'm wondering if there's an issue with calling that fetch request/state update and then expecting the component to properly update. Should I pass the state as a prop one level lower and have a component that only focuses on displaying that value? It's clear that everything is working as expected, the page is just not updating when the new state value is set.
Here's the code for child component that is not updating (had to modify for privacy purposes)
import { bindActionCreators, Component, connect, createElement, PropTypes } from 'somePackage';
import { getStatus } from 'somedirectory';
class ChildComponent extends Component {
constructor(props) {
super(props);
this.state = {
irrelevantState: false,
};
}
componentDidMount() {
this.fetchMyData();
}
fetchMyData() {
const {
boundNavActions,
} = this.props;
boundNavActions.getStatus();
}
render() {
const {
**stateImLookingAt**,
irrelevantString,
irrelevantString,
} = this.props;
return (
<div>
<div styleName="irrelevantString">
<div styleName="irrelevantString">
<a
href={ irrelevantString }
aria-label={ irrelevantString }
>
<div
spriteSheetType="irrelevantString"
name={ irrelevantString }
/>
//Would making this it's own component help?
<div styleName="thisDoesntUpdate">
{ **stateImLookingAt** }
</div>
//Would making this it's own component help?
</a>
</div>
</div>
</div>
);
}
}
ChildComponent.propTypes = {
boundNavActions: PropTypes.object,
cartCount: PropTypes.number,
};
const mapDispatchToProps = dispatch => ({
boundNavActions: bindActionCreators({
getStatus,
}, dispatch),
});
export default connect(null, mapDispatchToProps)(ChildComponent);
There's not a lot going on pertaining to this state in the parent but here's a snippet
import { connect, createElement, PropTypes } from 'somedirectory';
import ChildComponent from 'ChildComponentPackage';
import './app.css';
const AppContainer = (props) => {
const {
**stateImLookingAt**,
} = props;
return (
<div styleName="root">
<ChildComponent
**stateImLookingAt**={ **stateImLookingAt** }
/>
</div>
);
};
const mapStateToProps = state => ({
**stateImLookingAt**: state.moo.cow.**stateImLookingAt**,
});
AppContainer.propTypes = {
**stateImLookingAt**: PropTypes.number.isRequired,
};
export default connect(mapStateToProps)(AppContainer);

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!

Dynamically creating react components that don't refresh the DOM on every change

I'm working on a React app where we are rendering an unknown number of form elements passed in from another application. I have code setup to pull in all possible components and dynamically render each with props when it's needed (below), but every time I make any change (i.e. entering text in a text field, clicking a checkbox, etc.), every form element is updated. I'm unsure how to get around this because the rendering is done inside a map function, and I can't get rid of the map function because we have no idea what or how many elements will be sent in to be rendered. I am using redux, so can't adequately use shouldComponentUpdate, but I have used the {pure: true} option in connect, with no luck. Any ideas on how to do this?
import React from 'react';
import {
InputBox,
RadioGroup,
ComboBox,
CheckboxGroup,
DatePicker
} from './components';
import {connect} from "react-redux";
import * as actions from 'redux/actions/actions';
import {convertToVariable} from 'components/Utility/utils'
const componentLookup = {
"InputBox": InputBox,
"RadioGroup": RadioGroup,
"ComboBox": ComboBox,
"CheckboxGroup": CheckboxGroup,
"DateField": DatePicker
};
class FormPanel extends React.Component {
constructor(props) {
super(props);
this.logData = this.logData.bind(this);
}
logData(eventValues) {
this.props.updateDialogValues(eventValues);
}
render() {
return (
<div>
{this.props.uiContent.map((element) => {
const Tag = componentLookup[element.type];
return (
<Tag
{...element.defaults}
onValueChange={this.logData}
key={element.defaults.label}
name={element.defaults.variable || convertToVariable(element.defaults.label)}
/>
)
})}
</div>
);
}
}
function mapStateToProps(state) {
return {
dialogValues: state.app.dialogValues,
actionInfo: state.xyz.actionInfo,
actionPayload: state.xyz.actionPayload
}
}
function mapDispatchToProps(dispatch) {
return {
updateDialogValues: (dialogValues) => {
dispatch(actions.updateDialogValues(dialogValues))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps, undefined, {pure: true})(FormPanel)

Categories