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);
Related
I'm currently learning react and redux and stumbled into a problem i can't really get my head around. Trying to implement the same functionality
as in this article: https://medium.com/#yaoxiao1222/implementing-search-filter-a-list-on-redux-react-bb5de8d0a3ad but even though the data request from the rest api i'm working with is successfull i can't assign the local state in my component to my redux-state, in order to be able to filter my results. Heres my component:
import React from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as fetchActions from '../../actions/fetchActions'
import Stafflist from './Stafflist'
class AboutPage extends React.Component {
constructor(props) {
super(props)
this.state = {
search: '',
currentlyDisplayed: this.props.store.posts
}
this.updateSearch = this.updateSearch.bind(this)
}
updateSearch(event) {
let newlyDisplayed = this.state.currentlyDisplayed.filter(
(post) => {
return (
post.name.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
|| post.role.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
)}
)
console.log(newlyDisplayed)
this.setState({
search: event.target.value.substr(0, 20),
currentlyDisplayed: newlyDisplayed
})
}
render() {
return (
<div className="about-page">
<h1>About</h1>
<input type="text"
value={this.state.search}
onChange={this.updateSearch}
/>
//component for rendering my list of posts.
<Stafflist posts={this.state.currentlyDisplayed} />
</div>
)
}
}
// this is here i assign my api data to this.props.store.posts
function mapStateToProps(state, ownProps) {
return {
store: state
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(fetchActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AboutPage)
Comparing how i assign my stores state to my local component with how it works in the article, it seems to be done in the same way. Mine:
this.state = {
search: '',
currentlyDisplayed: this.props.store.posts
}
article:
this.state = {
searchTerm: '',
currentlyDisplayed: this.props.people
}
within react devtools i can see my data in as it should be in the store, but it won't work to assign it to my local state within the component in order to perform the filtering, and i don't really know how to debug this. My state in the local component just says
State
currentlyDisplayed: Array[0]
Empty array
also if i change
<Stafflist posts={this.state.currentlyDisplayed} />
to
<Stafflist posts={this.props.store.posts} />
the list renders as it should :)
Reducer:
import * as types from '../actions/actionTypes'
import initialState from './initialState'
export default function postReducer(state = initialState.posts, action) {
switch(action.type) {
case types.FETCH_POSTS_SUCCESS:
return action.posts.data.map(post => {
return {
id: post.id,
name: post.acf.name,
role: post.acf.role
}
})
default:
return state
}
}
Any ideas?
The problem with your code is that you do not handle how to get newly received props to your state. This means that when you receive the data from your api call only the props are updated while component state is not.
If you look carefully at the referenced article, in the onInputChange method they recalculate the state from the props whereas your updateState method only filters from the local state.
Setting the state in the constructor only ensures that the props are copied on the initial mount of the component. At that point in time the store only contains the initial state (initialState.posts in your reducer code). This is the price of keeping both component state and store; you must think of how to keep the two in sync after the initial load.
One solution is to update the state in componentWillReceiveProps:
componentWillReceiveProps(nextProps){
const nextFiltered = nextProps.store.posts.filter(your filtering code here);
this.setState({currentlyDisplayed: nextFiltered});
}
This will update your state whenever the component receives new props. Note react has phased out componentWillReceiveProps and you should use getDerivedStateToProps as of react 16.3. Refer here for more details.
I am having a bit of an issue rendering components before the state is set to the data from a returned asynchronous API request. I have a fetch() method that fires off, returns data from an API, and then sets the state to this data. Here is that block of code that handles this:
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi.USD.rate);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
You will notice the console.log('API CALL', currentPrice.bpi.USD.rate) that I use to check if the API data is being returned, and it absolutely is. currentPrice.bpi.USD.rate returns an integer (2345.55 for example) right in the console as expected.
Great, so then I assumed that
this.setState = ({ currentPrice: currentPrice.bpi.USD.rate }) should set the state without an issue, since this data was received back successfully.
So I now render the components like so:
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
With this, I was expecting to be able to access this data in my PriceOverview.js component like so: this.props.data
I have used console.log() to check this.props.data inside my PriceOverview.js component, and I am getting 'null' back as that is the default I set intially. The issue I am having is that the components render before the API fetch has ran it's course and updated the state with the returned data. So when App.js renders the PriceOverview.js component, it only passes currentPrice: null to it, because the asynchronous fetch() has not returned the data prior to rendering.
My confusion lies with this.setState. I have read that React will call render any time this.setState is called. So in my mind, once the fetch() request comes back, it calls this.setState and changes the state to the returned data. This in turn should cause a re-render and the new state data should be available. I would be lying if I didn't say I was confused here. I was assuming that once the fetch() returned, it would update the state with the requested data, and then that would trigger a re-render.
There has to be something obvious that I am missing here, but my inexperience leaves me alone.. cold.. in the dark throws of despair. I don't have an issue working with 'hard coded' data, as I can pass that around just fine without worry of when it returns. For example, if I set the state in App.js to this.state = { currentPrice: [254.55] }, then I can access it in PriceOverview.js via this.props.data with zero issue. It's the async API request that is getting me here, and I am afraid it has gotten the best of me tonight.
Here App.js in full:
import React, { Component } from 'react';
import './components/css/App.css';
import NavigationBar from './components/NavigationBar';
import PriceOverview from './components/PriceOverview';
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
Here is PriceOverview.js in full:
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
currentPrice: this.props.data
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Thank you in advance to any help, it's much appreciated.
this.setState ({
currentPrice: currentPrice.bpi.USD.rate
})
Do not put an = in this.setState
Ok First thing, when you're writting code on React the components that hold state are the class base components so ... What I see here is that you're creating two class base components so when you pass down props from your app class component to your PriceOverview wich is another class base component you're essentially doing nothing... Because when your constructor on your PriceOverview get call you're creating a new state on that Component and the previous state ( that's is the one you want to pass down) is being overwritten and that's why you're seem null when you want to display it. So it should work if you just change your PriveOverview component to a function base component ( or a dumb component). So this way when you pass down the state via props, you're displaying the correct state inside of your div. This is how would look like.
import React from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
const PriceOverview = (data) => {
return (
<div className="overviewBar">
<div className="currentPrice panel">
//Im calling data here because that's the name you gave it as ref
//No need to use 'this.props' you only use that to pass down props
{data != null ? <div className="price">
{data}</div> : <div className="price">Loading...</div>
}
</div>
</div>
)
}
}
export default PriceOverview;
Whenever you're writing new components start always with function base components if you component is just returning markup in it and you need to pass some data go to his parent component update it (making the api calls there or setting the state there) and pass down the props you want to render via ref. Read the React docs as much as you can, hope this explanation was useful (my apologies in advance if you don't understand quite well 'cause of my grammar I've to work on that)
The thing is constructor of any JS class is called only once. It is the render method that is called whenever you call this.setState.
So basically you are setting currentPrice to null for once and all in constructor and then accessing it using state so it will always be null.
Better approch would be using props.
You can do something like this in your PriceOverview.js.
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.props.data!= null ? <div className="price">{this.props.data}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Or you can use react lifecycle method componentWillReceiveProps to update the state of PriceOverview.js
componentWillReceiveProps(nextProps) {
this.setState({
currentPrice:nextProps.data
});
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice }</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
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);
}
I have two components to represent a list of articles and a filtering form. Every time any form field is changed, I need to send a HTTP request including the selected filters.
I have the following code for the SearchForm:
import React from 'react';
import { reduxForm, Field } from 'redux-form';
const SearchForm = ({ onFormChange }) => (
<form>
<Field component='select' name='status' onChange={onFormChange}>
<option>All</option>
<option value='published'>Published</option>
<option value='draft'>Draft</option>
</Field>
<Field
component='input'
type='text'
placeholder='Containing'
onChange={onFormChange}
/>
</form>
);
export default reduxForm({ form: 'myCustomForm' })(SearchForm);
And the following for the PostsList:
import React, { Component } from 'react';
import SearchForm from './SearchForm';
import { dispatch } from 'redux';
class PostsList extends Component {
constructor(props) {
super();
this.onFormChange = this.onFormChange.bind(this);
}
onFormChange() {
// Here I need to make the HTTP Call.
console.info(this.props.myCustomForm.values);
}
componentWillMount() {
this.props.actions.fetchArticles();
}
render() {
return (
<div>
<SearchForm onFormChange={this.onFormChange} />
<ul>
{ this.props.articles.map((article) => (<li>{article.title}</li>)) }
</ul>
</div>
);
}
}
const mapStateToProps = (state) => ({
myCustomForm: state.form.myCustomForm
});
const mapDispatchToProps = (dispatch) => ({
actions: {
fetchArticles: dispatch({ type: 'FETCH_ARTICLES' })
}
});
export default connect(mapStateToProps, mapDispatchToProps)(PostsList);
Though there is nothing going wrong with the rendering itself, something very awkful is happending with the myCustomForm.values prop when I change the form.
When I do that for the first time, the console.log(this.props.myCustomForm.values) call returns undefined, and the next calls return the previous value.
For example:
I load the page and select the draft option. undefined is printed.
I select published. { status: 'draft' } is printed.
I select draft again... { status: 'published' } is printed.
I inspected the redux store and the componend props. Both change according to the form interaction. But my function is returning the previous, not the new value sent by onChange.
This is clearly a problem with my code, most probably with the way I'm passing the function from parent to child component.
What am I doing wrong?
There is nothing wrong with your function. What I think is happening is that first time you select the option your callback is fired and is console logging current state for myCustomForm.values which haven't been yet changed by redux-form. So when the select changes:
your callback is fired...
...then redux-form is updating the state.
So. when your callback is making console.log it's printing not yet updated store.
do this, and you will see it's true:
onFormChange(e) {
// Here I need to make the HTTP Call.
console.info(e.currentTarget.value);
}
EDIT
My first question would be, do you really need to store this value in redux and use redux-form? It's a simple case, and you get current value in a way I showed you above.
However, if that's not the case, the callback is not required here, you just need to detect in your connected component (PostsList) that values have been changed in a form. You can achieve it with componentWillReceiveProps hook.
class PostsList extends Component {
constructor(props) {
super(props); // you should pass props to parent constructor
this.onFormChange = this.onFormChange.bind(this);
}
componentWillReceiveProps(nextProps) {
if(this.props.myCustomForm.values !== nextProps.myCustomForm.values) {
// do your ajax here....
}
}
componentWillMount(nextProps) {
this.props.actions.fetchArticles();
}
render() {
return (
<div>
<SearchForm />
<ul>
{ this.props.articles.map((article) => (<li>{article.title}</li>)) }
</ul>
</div>
);
}
}
I have the following code:
import { Redirect } from 'react-router-dom';
import QPContent from '../QPContent';
class AnswerContainer extends Component {
constructor(props) {
super(props);
this.state = {
redirect: false
};
}
render() {
const { redirect } = this.state;
if (redirect) {
return <Redirect push to={'/question/'+this.props.qID}/>;
}
return (
<div className="qp-answer-container">
....
</div>
);
}
}
const mapDispatchToProps = (dispatch, ownprops) => ({
....
});
const mapStateToProps = (state) => ({
....
});
export default connect(mapStateToProps, mapDispatchToProps)(AnswerContainer);`
I have an array of AnswerContainer components in a parent component. I am currently in the route /question/qId. I am trying to refresh the page on setting redirect: true. So when I use setState() and change the state of only one component in the list to redirect: true, the parent component doesn't re-render. Instead, the child component disappears. Why is this happening and how to trigger re-render of the whole page on <Redirect/> ?
Note: I'm calling setState() inside an action dispatch
If you really want to re-render the whole page, just use window.location.reload().
The idea of <Redirect /> component is to update the current url, not to re-render components. Of course they usually do re-render since most commonly you display something else when the url changes. But the job of react is to re-render as little as possible.