I have the following code, but when the web page is displayed, this.props.items is not passed to Item. When I try to output this.props.items in console.log, nothing is displayed.
However, when the page is automatically refreshed by saving it in the code editor instead of reloading the page, this.props.items is properly passed.
Why is this happening?
I would appreciate it if you could tell me more about it.
import React, { Component } from 'react';
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { getItems } from '../../actions/items'
class Container extends Component {
static propTypes = {
items: PropTypes.array.isRequired,
getItems: PropTypes.func.isRequired,
}
componentWillMount() {
this.props.getItems();
}
render() {
return (
<Item timetable={this.props.items} />
);
}
}
const mapStateToProps = (state) => ({
items: state.items.items,
});
export default connect(mapStateToProps, { getItems })(Container);
export const getItems = () => (dispatch, getState) => {
axios.get(`${url}/items/`, tokenConfig(getState))
.then(res => {
dispatch({
type: GET_ITEMS,
payload: res.data
})
}).catch(err => console.log(err));
}
Most probably the issue would be that your backend API response time is more than what you had expected. It's always a good practice to check whether the data that you fetch from your backend API is actually present, in the sense not null/not empty. The most common counter to this is to use a Loader component and wait until the whole data is fetched from server.
class Container extends Component {
static propTypes = {
items: PropTypes.array.isRequired,
getItems: PropTypes.func.isRequired,
}
componentWillMount() {
this.props.getItems();
}
render() {
if (!this.props?.items) return (<p>Loading...</p>); // --> Use a guard clause
return (
<Item timetable={this.props.items} />
);
}
}
Please go through these references to understand more clearly the points I have elaborated:
How to handle AJAX requests - Official React Docs
Why use guard clauses?
When you save a file in your code editor, React refreshes the app but retains as much of the state as possible, so that you can keep working without needing to redo a lot of steps to get where you're working on. But when you refresh the page, the whole app is restarted from zero and all state is lost.
You can avoid this behavior and save the state you want to persist by using localStorage or sessionStorage. You can do this in the context of React and Redux using Redux Persist.
Related
I run into a problem that is litterally blowing my mind.
I'm developing my web application using React and Redux, my application use a system of notification implemented with Firebase.
Every notification is structured as below:
var notification = {
from_user:{
name: 'John',
surname: 'Doe'
},
payload:{
message:'Lorem ipsum ...'
}
seen:false,
timestamp:1569883567
}
After fetched, notification is send to notificationReducer with:
dispatch({type:'FETCH_NOTIFICATION_OK',payload:notification})
And so far everything is ok.
My notificationReducer is structured as below:
const INITIAL_STATE = {
loading:false,
notification:{}
}
const notificationReducer = (state=INITIAL_STATE,action)=>{
switch(action.type){
case 'FETCHING_NOTIFICATION':
return {...state,loading:true}
case 'FETCH_NOTIFICATION_OK':
return {...state,loading:false,notification:action.payload} // I THINK PROBLEM IS HERE
default:
return state
}
}
export default notificationReducer;
The problem is that, when I pass state props to my presentational component, notification object is empty
My presentational component is reported below
import React from 'react';
import {getNotification} from '../actions/actioncreators';
import {connect} from 'react-redux';
class NotificationDetail extends React.Component {
componentWillMount(){
this.props.fetch_notification('9028aff78d78x7hfk');
console.log(this.props.notification) // IT PRINTS: {}
}
render(){
return(
<div>
'TODO'
</div>
)
}
}
const mapStateToProps = state =>{
return {
is_loading:state.notificationReducer.loading,
notification:state.notificationReducer.notification
}
}
const mapDispatchToProps = dispatch =>{
return {
fetch_notification: (id_notification)=>dispatch(getNotification(id_notification))
}
}
export default connect(mapStateToProps,mapDispatchToProps)(NotificationDetail)
Any suggestion ?
EDIT: In my reducer I tried to print the new state. I succesfully got this:
But, Anyway In my presentational component I got an empty object
I think the dispatch call hasn't fired yet. Try executing the below
componentDidMount() {
this.props.fetch_notification();
}
render() {
console.log(this.props.notification); // It should print an output here if your xhr/ajax/axios call is correct
}
Also, using componentWillMount is UNSAFE (according to the ReactJS current documentation). Avoid using this lifecycle in the future.
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>
)
}
}
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);
So I just switched to using stateless functional components in React with Redux and I was curious about component lifecycle. Initially I had this :
// actions.js
export function fetchUser() {
return {
type: 'FETCH_USER_FULFILLED',
payload: {
name: 'username',
career: 'Programmer'
}
}
}
Then in the component I used a componentDidMount to fetch the data like so :
// component.js
...
componentDidMount() {
this.props.fetchUser()
}
...
After switching to stateless functional components I now have a container with :
// statelessComponentContainer.js
...
const mapStateToProps = state => {
return {
user: fetchUser().payload
}
}
...
As you can see, currently I am not fetching any data asynchronously. So my question is will this approach cause problems when I start fetching data asynchronously? And also is there a better approach?
I checked out this blog, where they say If your components need lifecycle methods, use ES6 classes.
Any assistance will be appreciated.
Firstly, don't do what you are trying to to do in mapStateToProps. Redux follows a unidirectional data flow pattern, where by component dispatch action, which update state, which changes component. You should not expect your action to return the data, but rather expect the store to update with new data.
Following this approach, especially once you are fetching the data asynchronously, means you will have to cater for a state where your data has not loaded yet. There are plenty of questions and tutorials out there for that (even in another answer in this question), so I won't worry to put an example in here for you.
Secondly, wanting to fetch data asynchronously when a component mounts is a common use case. Wanting to write nice functional component is a common desire. Luckily, I have a library that allows you to do both: react-redux-lifecycle.
Now you can write:
import { onComponentDidMount } from 'react-redux-lifecycle'
import { fetchUser } from './actions'
const User = ({ user }) => {
return // ...
}
cont mapStateToProps = (state) => ({
user = state.user
})
export default connect(mapStateToProps)(onComponentDidMount(fetchUser)(User))
I have made a few assumptions about your component names and store structure, but I hope it is enough to get the idea across. I'm happy to clarify anything for you.
Disclaimer: I am the author of react-redux-lifecycle library.
Don't render any view if there is no data yet. Here is how you do this.
Approach of solving your problem is to return a promise from this.props.fetchUser(). You need to dispatch your action using react-thunk (See examples and information how to setup. It is easy!).
Your fetchUser action should look like this:
export function fetchUser() {
return (dispatch, getState) => {
return new Promise(resolve => {
resolve(dispatch({
type: 'FETCH_USER_FULFILLED',
payload: {
name: 'username',
career: 'Programmer'
}
}))
});
};
}
Then in your Component add to lifecycle method componentWillMount() following code:
componentDidMount() {
this.props.fetchUser()
.then(() => {
this.setState({ isLoading: false });
})
}
Of course your class constructor should have initial state isLoading set to true.
constructor(props) {
super(props);
// ...
this.state({
isLoading: true
})
}
Finally in your render() method add a condition. If your request is not yet completed and we don't have data, print 'data is still loading...' otherwise show <UserProfile /> Component.
render() {
const { isLoading } = this.state;
return (
<div>{ !isLoading ? <UserProfile /> : 'data is still loading...' }</div>
)
}