I'm trying to create my own pagination (without using a package), but I can't get it to work.
I'm wondering if it has something to do with how I'm copying my arrays, but I'm not really sure.
class InsightSearchResults extends Component {
state = {
start: 0,
end: 2,
insightsArrayOriginal: [],
copiedArr: []
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.insightList[0]) {
this.setState({
insightsArrayOriginal: nextProps.insightList[0].insights,
copiedArr: nextProps.insightList[0].insights.splice(this.state.start, this.state.end)
})
}
}
clickNext = () => {
let copied = [...this.state.insightsArrayOriginal];
this.setState({
start: this.state.start + 2,
end: this.state.end + 2
}, () => {
this.setState({
copiedArr: copied.splice(this.state.start, this.state.end)
})
})
}
clickPrev = () => {
this.setState({
start: this.state.start - 2 < 0 ? 0 : this.state.start - 2,
end: this.state.end - 2
})
}
render() {
const { copiedArr } = this.state;
return (
<div style={{padding: "1.5rem"}}>
{copiedArr ? copiedArr.map(insight => (
<div>
<Grid className="insight_result_block">
<Col className="insight_results_col2" span="10">
<div>
<h4>Hello</h4>
<p>{insight.insightDesc}</p>
</div>
</Col>
</Grid>
<hr className="bottom_hr_insight" />
</div>
)) : <p>loading...</p> }
<button onClick={this.clickPrev}>Prev</button>
<button onClick={this.clickNext}>Next</button>
</div>
)
}
}
I haven't really worked on the "prev" part yet. I'm just trying to get the "next" to work for now...
There are two problems:
UNSAFE_componentWillReceiveProps is not called on initial render. From the docs:
React doesn’t call UNSAFE_componentWillReceiveProps() with initial
props during mounting. It only calls this method if some of
component’s props may update. Calling this.setState() generally
doesn’t trigger UNSAFE_componentWillReceiveProps().
splice mutates the original array, use slice instead. See this question.
So you can move the content of UNSAFE_componentWillReceiveProps to componentDidMount and componentDidUpdate
componentDidMount() {
this.updateState();
}
componentDidUpdate() {
// check if a change in props has caused the rerender
// or you will get infinite rerenders if one state update causes the next one
if (
this.props.insightList[0] &&
this.props.insightList[0].insights !== this.state.insightsArrayOriginal
) {
this.updateState();
}
}
These functions don't receive a parameter: replace nextProps parameter with this.props; and change all splice occurrences with slice.
updateState() {
if (this.props.insightList[0]) {
this.setState({
insightsArrayOriginal: this.props.insightList[0].insights,
copiedArr: this.props.insightList[0].insights.slice( . // <-here
this.state.start,
this.state.end
)
});
}
}
clickNext = () => {
let copied = [...this.state.insightsArrayOriginal];
this.setState({ start: this.state.start + 2, end: this.state.end + 2 },
() => {
this.setState({
copiedArr: copied.slice(this.state.start, this.state.end) // <- and here
});
}
);
};
Also, based on this code sample alone, you could entirely remove insightsArrayOriginal from your state and use it from props, but this may change if you plan to expand the functionality.
Related
I'm sure my issues is around useCallback and the setter method I have here setSpeakers returned from useState but I'm not understanding exactly why my example, as listed here and in codesandbox is not incrementing the state correctly. The old state keeps being returned, not the new incremented state. When I pass a function into setSpeakers instead of just a new state, my example works (you can see the commented out code marked THIS CODE WORKS below the non-working code.
I know others have written articles on this and I've read those articles but still don't get it.
sandbox: https://codesandbox.io/s/elated-lichterman-7rejs?file=/pages/index.js
import React, { useState, memo, useCallback } from "react";
//const Speaker = React.memo(({ imageSrc, counter, setCounter }) => {
const Speaker = ({ speaker, speakerClick }) => {
console.log(speaker.id);
return (
<div>
<span
onClick={() => {
speakerClick(speaker.id);
}}
src={`speakerimages/Speaker-${speaker.id}.jpg`}
width={100}
>
{speaker.id} {speaker.name}
</span>
<span className="fa fa-star "> {speaker.clickCount}</span>
</div>
);
};
function SpeakerList({ speakers, setSpeakers }) {
return (
<div>
{speakers.map((speaker) => {
return (
<Speaker
speaker={speaker}
speakerClick={useCallback((id) => {
// THIS CODE FAILS BECAUSE OF STALE STATE BUT I DON'T GET WHY
const speakersNew = speakers.map((speaker) => {
return speaker.id === id
? { ...speaker, clickCount: speaker.clickCount + 1 }
: speaker;
});
setSpeakers(speakersNew);
// THIS CODE WORKS
// setSpeakers(function(speakers) {
// const speakersNew = speakers.map((speaker) => {
// return speaker.id === id
// ? { ...speaker, clickCount: speaker.clickCount + 1 }
// : speaker;
// });
// return speakersNew;
// });
}, [])}
key={speaker.id}
/>
);
})}
</div>
);
}
//
const App = () => {
const speakersArray = [
{ id: 1124, name: "aaa", clickCount: 0 },
{ id: 1530, name: "bbb", clickCount: 0 },
{ id: 10803, name: "ccc", clickCount: 0 }
];
const [speakers, setSpeakers] = useState(speakersArray);
return (
<div>
<h1>Speaker List</h1>
<SpeakerList speakers={speakers} setSpeakers={setSpeakers}></SpeakerList>
</div>
);
};
export default App;
That's because set state works asynchronously. In the first code that isn't running you set the state and try to immediately have it show up which wont work since there is delay while the state is being set. The second one works because you're passing a callback to set state which basically means set the state and do this while you're at it. while the first one means set the state and then do this afterwards. That's not what you want. Its a loose analogy but look into how and why set state is aync you'll have a better idea. Cheers.
Sending a react-redux action to an API to return posts when the user activates drag refresh or accesses the component for the first time.
This normally does not cause a problem as the FlatList would take directly from props and therefore not trigger a recursive loop. However, I need to format the data to fit the application and as a result, I pass the data through a series of methods before it hits the FlatList. This process is clashing with componentDidUpdate thus throwing the application into a loop of requests. Code below:
componentDidUpdate(prevProps) {
if (prevProps.posts !== this.props.posts){
this.storePosts() //the problem is that when storeposts is called it is updating the props which then is calling store posts which is updating the props and so on and so on....
}
}
storePosts() { //the problem with this is that it changes the state
this.props.fetchPosts() //this line is causing a continous request to rails
let itemData = this.props.posts
if (itemData !== this.state.currentlyDisplayed) {
this.setState({ currentlyDisplayed: this.props.posts, items: itemData }, () => {
this.setState({isLoading: false})
});
}
}
formatItems = () => {
const itemData = this.props.posts
const newItems = [itemData]
const numberOfFullRows = Math.floor(itemData.length / 3);
let numberOfElementsLastRow = itemData.length - (numberOfFullRows * 3);
while (numberOfElementsLastRow !== 3 && numberOfElementsLastRow !== 0) {
newItems.push({ key: `blank-${numberOfElementsLastRow}`, empty: true });
numberOfElementsLastRow++;
}
return this.setState({ newItems: newItems})
// console.log(newItems.length, numberOfElementsLastRow)
};
renderItem = ({ item, type }) => {
const { items } = this.state;
if (item.empty === true) {
return <View style={[styles.item, styles.itemInvisible]} />;
} else {
return (
<TouchableOpacity style={styles.item} onPressIn={() => this.setState({ itemSelected: item.id })} onPress={() => this.props.navigation.navigate('View Post', {post: item})} key={item.id}>
<Image source={{ uri: item.image }} style={{ flex: 1, width: '100%', height: undefined }} />
</TouchableOpacity>
);
}
};
onRefresh = () => {
this.setState({refreshing: true})
this.storePosts()
this.setState({refreshing: false, currentlyDisplayed: this.props.posts})
};
render() {
const { error: { vaultErrorMessage }} = this.props
const { posts } = this.props
<SafeAreaView>
<FlatList
data={this.state.currentlyDisplayed}
renderItem={this.renderItem}
numColumns={3}
keyExtractor={(item, index) => index.toString()}
refreshControl={<RefreshControl refreshing={this.state.refreshing} onRefresh={() => this.onRefresh()} />}
extraData={this.state.refresh}
/>
</SafeAreaView>
);
}
}
}
If anyone has any ideas to go about this better or solve the problem that would be great! I think I have been looking at the code for too long so I'm pretty dulled to thinking about how to solve....
I suggest splitting the logic for updating the posts and storing the posts into two separate methods to avoid the infinite state update.
For example:
componentDidUpdate(prevProps) {
if (shouldUpdate) { // some other condition
this.updatePosts();
}
if (prevProps.posts !== this.props.posts){
this.storePosts();
}
}
updatePosts() {
this.props.fetchPosts();
}
storePosts() {
let itemData = this.props.posts;
if (itemData !== this.state.currentlyDisplayed) {
this.setState({ currentlyDisplayed: this.props.posts, items: itemData }, () => {
this.setState({isLoading: false})
});
}
}
You should also look into a better way to check if the posts have actually changed since the array may have changed but the content may have stayed the same. (Note that [1, 2, 3] === [1, 2, 3] evaluates to false). fast-deep-equal is a good library for this, or you can come up with a custom solution.
If you have to use this approach, use static getDerivedStateFromProps instead of componentDidUpdate.
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
This method exists for rare use cases where the state depends on changes in props over time. For example, it might be handy for implementing a <Transition> component that compares its previous and next children to decide which of them to animate in and out.
You can try it;
extraData={this.state.currentlyDisplayed}
I have this application that has a deprecated lifecycle method:
componentWillReceiveProps(nextProps) {
if (this.state.displayErrors) {
this._validate(nextProps);
}
}
Currently, I have used the UNSAFE_ flag:
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.state.displayErrors) {
this._validate(nextProps);
}
}
I have left it like this because when I attempted to refactor it to:
componentDidUpdate(prevProps, prevState) {
if (this.state.displayErrors) {
this._validate(prevProps, prevState);
}
}
It created another bug that gave me this error:
Invariant Violation: Maximum update depth exceeded. This can happen
when a component repeatedly calls setState inside componentWillUpdate
or componentDidUpdate. React limits the number of nested updates to
prevent infinite loops.
It starts to happen when a user clicks on the PAY NOW button that kicks off the _handlePayButtonPress which also checks for validation of credit card information like so:
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.state.displayErrors) {
this._validate(nextProps);
}
}
_validate = props => {
const { cardExpireDate, cardNumber, csv, nameOnCard } = props;
const validationErrors = {
date: cardExpireDate.trim() ? "" : "Is Required",
cardNumber: cardNumber.trim() ? "" : "Is Required",
csv: csv.trim() ? "" : "Is Required",
name: nameOnCard.trim() ? "" : "Is Required"
};
if (validationErrors.csv === "" && csv.trim().length < 3) {
validationErrors.csv = "Must be 3 or 4 digits";
}
const fullErrors = {
...validationErrors,
...this.props.validationErrors
};
const isValid = Object.keys(fullErrors).reduce((acc, curr) => {
if (fullErrors[curr]) {
return false;
}
return acc;
}, true);
if (isValid) {
this.setState({ validationErrors: {} });
//register
} else {
this.setState({ validationErrors, displayErrors: true });
}
return isValid;
};
_handlePayButtonPress = () => {
const isValid = this._validate(this.props);
if (isValid) {
console.log("Good to go!");
}
if (isValid) {
this.setState({ processingPayment: true });
this.props
.submitEventRegistration()
.then(() => {
this.setState({ processingPayment: false });
//eslint-disable-next-line
this.props.navigation.navigate("PaymentConfirmation");
})
.catch(({ title, message }) => {
Alert.alert(
title,
message,
[
{
text: "OK",
onPress: () => {
this.setState({ processingPayment: false });
}
}
],
{
cancelable: false
}
);
});
} else {
alert("Please correct the errors before continuing.");
}
};
Unfortunately, I do not have enough experience with Hooks and I have failed at refactoring that deprecated lifecycle method to one that would not create trouble like it was doing with the above error. Any suggestions at a better CDU or any other ideas?
You need another check so you don't get in an infinite loop (every time you call setState you will rerender -> component did update -> update again ...)
You could do something like this:
componentDidUpdate(prevProps, prevState) {
if (this.state.displayErrors && prevProps !== this.props) {
this._validate(prevProps, prevState);
}
}
Also I think that you need to call your validate with new props and state:
this._validate(this.props, this.state);
Hope this helps.
componentDidUpdate shouldn't replace componentWillRecieveProps for this reason. The replacement React gave us was getDerivedStateFromProps which you can read about here https://medium.com/#baphemot/understanding-react-react-16-3-component-life-cycle-23129bc7a705. However, getDerivedStateFromProps is a static function so you'll have to replace all the setState lines in _validate and return an object instead.
This is how you work with prevState and hooks.
Working sample Codesandbox.io
import React, { useState, useEffect } from "react";
import "./styles.css";
const ZeroToTen = ({ value }) => {
const [myValue, setMyValue] = useState(0);
const [isValid, setIsValid] = useState(true);
const validate = value => {
var result = value >= 0 && value <= 10;
setIsValid(result);
return result;
};
useEffect(() => {
setMyValue(prevState => (validate(value) ? value : prevState));
}, [value]);
return (
<>
<span>{myValue}</span>
<p>
{isValid
? `${value} Is Valid`
: `${value} is Invalid, last good value is ${myValue}`}
</p>
</>
);
};
export default function App() {
const [value, setValue] = useState(0);
return (
<div className="App">
<button value={value} onClick={e => setValue(prevState => prevState - 1)}>
Decrement
</button>
<button value={value} onClick={e => setValue(prevState => prevState + 1)}>
Increment
</button>
<p>Current Value: {value}</p>
<ZeroToTen value={value} />
</div>
);
}
We have two components, one to increase/decrease a number and the other one to hold a number between 0 and 10.
The first component is using prevState to increment the value like this:
onClick={e => setValue(prevState => prevState - 1)}
It can increment/decrement as much as you want.
The second component is receiving its input from the first component, but it will validate the value every time it is updated and will allow values between 0 and 10.
useEffect(() => {
setMyValue(prevState => (validate(value) ? value : prevState));
}, [value]);
In this case I'm using two hooks to trigger the validation every time 'value' is updated.
If you are not familiar with hooks yet, this may be confusing, but the main idea is that with hooks you need to focus on a single property/state to validate changes.
Newbie React question here on show hide functionality.
I have a state of 'show' that I set to false:
this.state = {
show: false,
};
Then I use the following function to toggle
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
And my display is
{this.state.show && <xxxxxx> }
This all works fine. However I want to apply the function it to multiple cases (similar to accordion, without the closing of other children. So I change my constructor to
this.state = {
show: [false,false,false,false,false,false]
};
and this to recognise there are 6 different 'shows'.
{this.state.show[0] && <xxxxxx> }
{this.state.show[1] && <xxxxxx> } etc
But where I get stuck is how to account for them in my toggleDiv function. How do I insert the square bracket reference to the index of show (if this is my problem)?
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
Thanks for looking.
First of all I'd suggest you not to rely on current state in setState function, but to use the callback option to be 100% sure that you are addressing to the newest state:
this.setState((prevState) => ({ show: !prevState.show }));
How to deal with multiple elements?
You'll have to pass the index of currently clicked element.
{yourElements.map((elem, i) => <YourElem onClick={this.toggleDiv(i)} />)}
and then inside your toggleDiv function:
toggleDiv = (i) => () => {
this.setState((prevState) => {
const r = [...prevState.show]; // create a copy to avoid state mutation
r[i] = !prevState.show[i];
return {
show: r,
}
}
}
Use an array instead of a single value. In your toggle div function make a copy of the state array make necessary changes and push the entire array back up to state at the end.
This is some simplified code showing the workflow I described above
export default class myClass extends React.Component{
constructor(props){
super(props);
this.state = { show: new Array(2).fill(false) };
}
//you need a index or id to use this method
toggleDiv = (index) => {
var clone = Object.assign( {}, this.state.show ); //ES6 Clones Object
switch(clone[index]){
case false:
clone[index] = true
break;
case true:
clone[index] = false
break;
}
this.setState({ show: clone });
}
render(){
return(
<div>
{ this.state.show[0] && <div> First Div </div> }
{ this.state.show[1] && <div> Second Div </div> }
{ this.state.show[2] && <div> Third Div </div> }
</div>
)
}
}
I am having difficulties with creating an initial state for a component which relies on an API call.
In every way I tried to implement it, it get's into a loop of giving me the error that this.setState() is called during rendering, which is an anti patern,...
Question:
How is it possible to implement an initial state for a parent component depending on a child component's state? Only firing if there is nothing in the reduxstate
I could show code but it's quite extensive and divided between multiple files.
EDIT 1: As requested - simplified code
Parent component (Filter)
class Filter extends Component {
constructor(props) {
super(props);
this.state = {
active: '',
period: this.props.filter.period,
periodDisplay: null,
departments: this.props.filter.departments,
departmentsDisplay: null
};
this.updateFilterDepartments = this.updateFilterDepartments.bind(this);
this.updateFilterPeriod = this.updateFilterPeriod.bind(this);
this.initDepartments = this.initDepartments.bind(this);
}
componentDidMount() {
// Getting initial period
if (!this.state.period && this.props.initialFrom && this.props.initialTo) {
this.didSelectPeriod(this.props.initialFrom, this.props.initialTo);
}
else if (this.state.period) {
let initPeriod = this.state.period.match(/.{1,2}/g);
this.didSelectPeriod(
initPeriod[2] + initPeriod[3] + '-' + initPeriod[1] + '-' + initPeriod[0],
initPeriod[6] + initPeriod[7] + '-' + initPeriod[5] + '-' + initPeriod[4]
);
}
}
componentWillUnmount(){
this.setState({active: ''});
}
updateFilterPeriod() {
this.props.updateFilterPeriod(this.state.period);
}
didSelectPeriod(rawFrom, rawTo) {
if (rawFrom && rawTo) {
let From = moment(rawFrom),
To = moment(rawTo);
if (From.diff(To, 'days') === 0) {
this.setState ({
periodDisplay: From.format('D MMM YYYY'),
period: From.format('DDMMYYYY') + To.format('DDMMYYYY')
}, () => this.updateFilterPeriod());
}
else {
this.setState ({
periodDisplay: From.format('D MMM YYYY') + ' - ' + To.format('D MMM YYYY'),
period: From.format('DDMMYYYY') + To.format('DDMMYYYY')
}, () => this.updateFilterPeriod());
}
}
else {
this.setState({
periodDisplay: null,
period: null
}, () => this.updateFilterPeriod());
}
}
updateFilterDepartments() {
this.props.updateFilterDepartments(this.state.departments);
}
didSelectDepartments(hashes) {
if (hashes.length === 0) {
this.setState({
departments: null,
departmentsDisplay: null
}, () => this.updateFilterDepartments());
}
else {
this.setState({
departments: hashes,
departmentsDisplay: hashes.length + ' department(s) selected'
}, () => this.updateFilterDepartments());
}
}
render() {
return (
<div className='FILTER_wrapper' >
<div className={classnames('FILTER_section', {active: this.state.active == 'datepicker'})} onClick={() => this.setState({active: 'datepicker'})}>
<i className="fa fa-calendar FILTER_icon FILTER_icon--datepicker"></i><div className="FILTER_input FILTER_input--datepicker">{this.state.periodDisplay || <div className="placeholder">Select a period</div>}</div>
<div className={classnames('FILTER_dropdown FILTER_dropdown--datepicker', {active: this.state.active == 'datepicker'})}>
<DatePicker className='' didSelect={(rawFrom, rawTo) => this.didSelectPeriod(rawFrom, rawTo)} />
</div>
</div>
<div className={classnames('FILTER_section', {active: this.state.active == 'departmentpicker'})} onClick={() => this.setState({active: 'departmentpicker'})}>
<i className="fa fa-tags FILTER_icon FILTER_icon--departmentpicker"></i><div className="FILTER_input FILTER_input--departmentpicker">{this.state.departments ? this.state.departmentsDisplay : <div className="placeholder">Select department(s)</div>}</div>
<div className={classnames('FILTER_dropdown FILTER_dropdown--departmentpicker', {active: this.state.active == 'departmentpicker'})}>
<DepartmentPicker didSelect={(hashes, names) => this.didSelectDepartments(hashes)} />
</div>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
departments: state.departments,
filter: state.filter
};
}
Child component (DepartmentFilter)
class DepartmentPicker extends Component {
callCallback() {
this.props.didSelect(this.state.selectedHash, this.state.selectedName);
}
handlePredefined(type) {
let newStateHash = [];
let newStateName = [];
switch(type) {
case 'all':
this.props.departments.items.map(department => {
newStateHash.push(department.hash);
newStateName.push(department.name);
});
this.setState({
selectedHash: newStateHash,
selectedName: newStateName
}, () => this.callCallback());
break;
case 'none':
this.setState({
selectedHash: newStateHash,
selectedName: newStateName
}, () => this.callCallback());
break;
}
}
handleDepartment(hash, name) {
if (this.state.selectedHash.length > 0 && this.state.selectedHash.indexOf(hash) > -1) {
let newStateHash = this.state.selectedHash;
let newStateName = this.state.selectedName;
let index = newStateHash.indexOf(hash);
newStateHash.splice(index, 1);
this.setState({
selectedHash: newStateHash,
selectedName: newStateName
}, () => this.callCallback());
}
else {
this.setState({
selectedHash: [...this.state.selectedHash, hash],
selectedName: [...this.state.selectedName, name]
}, () => this.callCallback());
}
}
renderDepartments(department) {
let inSelection = this.state.selectedHash.length > 0 && this.state.selectedHash.indexOf(department.hash) > -1 ? true : false;
return(
<li className={classnames('DEPARTMENTPICKER_department', {active: inSelection})} key={department.hash} onClick={() => this.handleDepartment(department.hash, department.name)}>
<i className={classnames('DEPARTMENTPICKER_icon', 'fa', {'fa-check': inSelection, 'fa-times': !inSelection})} style={{color: department.theme}}></i>
{department.name}
</li>
);
}
render() {
// Notice that here it waits for the end of the api call
if (this.props.departments.isFetching) {return <Loader type='spinner' />;}
return (
<div className='DEPARTMENTPICKER_wrapper'>
<ul className='DEPARTMENTPICKER_predefined'>
<li onClick={() => this.handlePredefined('all')}>All</li>
<li onClick={() => this.handlePredefined('none')}>None</li>
</ul>
<ul className="DEPARTMENTPICKER_departments">
{this.props.departments.items.map(this.renderDepartments)}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
return {
departments: state.departments
};
}
export default connect(mapStateToProps)(DepartmentPicker);
EDIT 2
Because some people are asking about the flow the application is following I decided to include it here:
Parent component renders + API call (triggered by loggin in)
Child component renders (API call still in progress)
API call finishes -> need to create initial state for child component
componentWillMount will fire only one time so setting state over there is not a good idea. But if you want to set state for one time then you can use componentWillMount. otherwise there are three options:
According to the sequence of fire:
1)componentWillReceiveProps, Which will be invoked when props are changed
2)shouldComponentUpdate, this lifecycle function will give permission to render or not by returning true or false.
3)ComponentWillUpdate, this will be fired just before render is called. you can compare new props with old props and setState.
I've had a quick look. Seems you're updating the state in componentDidMount (via didSelectPeriod) in the parent. That's guaranteed unnecessary render. You should be moving that code to
constructor if it is a one time init thing
componentWillReceiveProps to if you want it to be executed when a relevant prop changes.
Now, this way you may or not get rid of the error mesage but there are more apparent issues with your code:
You're trying to render a redux container in a redux container. Well, this is technically doable but very wrong from an architectural perspective and probably the root of your problem. Try to imagine how simple your code would be if you just passed departments to your child components instead of using a connected component. Please refer to smart / dumb components in Redux manual.
componentWillUnmount(){
this.setState({active: ''});
}
This is not needed as a component's state is deleted as it is unmounted.
didSelect={(rawFrom, rawTo) => this.didSelectPeriod(rawFrom, rawTo)} Don't create a function every time you render. Use: didSelect={this.didSelectPeriod} instead. Don't forget to bind in the constructor: this.didSelectPeriod = this.didSelectPeriod.bind(this);
Using redux middleware (thunk, promise) would greatly simplify your API calls.
Try to put more context to your components. Is it a layout, a container or a dumb component? Most of the the time these three are all you need and layout > container > component is your usual render order.