Reputation: 5286
I'm building a React/Redux project that makes a couple calls to an S3 bucket to grab images, and then render them into the app.
I'm finding that for some reason, I cannot iterate through the array that I set in my state tree as a result of those calls to render them onto the app. I believe that I may be dealing with an array-like object or something within the Promises I created caused this mutation to occur.
First, I have this file in a folder titled utils:
const bucketName = 'foo';
const roleARN = 'arn:aws:s3:::foo';
const identityPoolId = 'us-west-2:anActualIdHere';
AWS.config.update({
region: 'us-west-2',
credentials: new AWS.CognitoIdentityCredentials({
IdentityPoolId: identityPoolId
})
});
const bucket = new AWS.S3({
params: {
Bucket: bucketName,
}
});
const textDecoder = new TextDecoder('utf8');
export function listObjects (callback) {
return bucket.listObjects((error, data) => {
if (error) {
console.error('error: ', error);
return;
}
callback(data.Contents);
});
}
export function getSingleObject (key) {
let getObject = new Promise((resolve, reject) => {
bucket.getObject({
Bucket: bucketName,
Key: key
}, (error, data) => {
if (error) {
console.error('error: ', error);
}
resolve(data.Body.toString('base64'));
});
})
return getObject.then((result) => {
return result;
})
}
What happens here is that listObjects
will return an array of all items in a specific S3 bucket.
Then, the getSingleObject
function grabs a single object's contents based on a key provided in that list of all items, grabbing its Uint8Array and converting it to a base-64 string.
These two functions are called in a thunk action:
import { listObjects, getSingleObject } from '../utils/index.js';
export function loadPhotos () {
return function (dispatch) {
listObjects((results) => {
let photos = [];
let mapPhotosToArray = new Promise((resolve, reject) => {
results.forEach((singlePhoto) => {
let getSinglePhoto = new Promise((resolve, reject) => {
resolve(getSingleObject(singlePhoto.Key));
});
getSinglePhoto.then((result) => {
photos.push(result);
});
});
resolve(photos);
})
mapPhotosToArray.then((result) => {
dispatch(savePhotos(result));
});
});
}
}
function savePhotos (photos) {
return {
type: 'GET_PHOTOS',
photos
}
}
loadPhotos
is the thunk action exposed to my Redux containers. It returns a function that first calls the listObjects
function in the utils file, passing it a callback that creates an array called photos
.
Then, it creates a new Promise that loops through the results array returned by the listObjects
utility function. In each iteration of this results array, I instantiate another new Promise that calls the getSingleObject
utility.
Within that iteration, I push the results of getSingleObject
into the photos
array created within the callback passed into listObjects
.
The last thing I do in the loadPhotos
thunk action is call the outer Promise and then dispatch the result to the savePhotos
action, which finally dispatches the payload object to the store for my reducer to catch.
This is how my reducer looks:
const defaultState = {
photos: []
}
const photos = (state = defaultState, action) => {
switch (action.type) {
case 'GET_PHOTOS':
return Object.assign({}, state, {
photos: action.photos
})
default:
return state;
}
};
export default photos;
Here is how I've set up the entry point to the app:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, compose, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import photos from './reducers/';
import App from './components/app';
import styles from './styles/styles.css';
const createStoreWithMiddleware = compose(applyMiddleware(thunk))(createStore);
const store = createStoreWithMiddleware(photos, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('root')
)
This is the App component rendered:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import AllPhotos from '../containers/AllPhotos';
import Navbar from './Navbar';
import '../styles/styles.css';
export default class App extends Component {
constructor (props) {
super (props);
}
render () {
return (
<div className="app">
<Navbar />
<AllPhotos />
</div>
)
}
}
Here is the AllPhotos container it renders:
import { connect } from 'react-redux';
import AllPhotos from '../components/AllPhotos';
import {
loadPhotos
} from '../actions/index.js';
const mapDispatchToProps = (dispatch) => {
return {
loadPhotos: () => {
dispatch(loadPhotos());
}
}
}
const mapStateToProps = (state) => {
return {
photos: state.photos
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AllPhotos);
And finally, this is the AllPhotos component:
import React, { Component } from 'react';
import _ from 'lodash';
export default class AllPhotos extends Component {
constructor(props) {
super(props);
}
componentWillMount () {
this.props.loadPhotos();
}
render () {
return (
<div>
<h1>Test</h1>
{ this._renderImages() }
</div>
)
}
_renderImages () {
_.map(this.props.photos, (image) => {
return (
<img
src={ 'data:image/png;base64,' + image }
width={ 480 }
/>
)
})
}
}
This is what happens when I attempt to log this.props.photos
within _renderImages
:
The first log occurs before the photos array is populated and loaded into my state.
However, if I were to log the length of this.props.photos
in the same area that I log just the array itself, this is what I see:
When I call Array.isArray on this.props.photos
in that same line, this is what I get:
I have also attempted to convert this into an array using Array.from
, but have not been successful.
Going a bit deeper, I attempted to find the length of the photos
array from the action payload in my reducer, and still received 0
as the output. I also tried this within the savePhotos
action, and found the same result.
As a result, I believe I may have not written my Promises correctly. Could someone help point me in the right direction?
Upvotes: 0
Views: 503
Reputation: 4310
You can try rewriting listObjects
and getSingleObject
to return Promises and utilising Promise.all.
Then you can write loadPhotos
export function loadPhotos () {
return function (dispatch) {
listObjects().then((results) =>
Promise.all(results.map((singlePhoto) => getSingleObject(singlePhoto.Key)))
.then((result) => dispatch(savePhotos(result)))
)
}
}
Upvotes: 2