Creating a web page for our ski lodge, we are renting it out to people. I have a Google spreadsheet with pricing, dates and wether it is available for rent that specific date.
My previous page was written in AngularJS with TabletopJS fetching the data from the spreadsheet, but I thought it would be a good experience writing it in React and Redux without TabletopJS.
How would I go about fetching data from the JSON that the Google spreadsheet spits out? I have looked at the Redux tutorial of async actions, but I find it hard to implement with my own code.
Do you guys have any tips on how I should do this?
EDIT: Here is my angularjs code:
tabletop-config.js
'use strict';
angular
.module('myApp')
.config(tableTopConfig);
function tableTopConfig (TabletopProvider) {
TabletopProvider.setTabletopOptions({
key: 'https://docs.google.com/spreadsheets/d/1GJHfKQy24BFKNkYVjCfFtar1OCd4vJ8_TvFRheMKH90/pubhtml',
simpleSheet: true
});
}
bookingController.js
(function () {
'use strict';
angular
.module('myApp')
.controller('bookingController', bookingController);
function bookingController($scope, Tabletop) {
var vm = this;
Tabletop.then(function(ttdata) {
vm.data = ttdata[0];
});
}
})();
You'll need* redux-thunk or something similar to handle asynchronous actions. The fetchTable function below has to be updated with whatever your API is to get the spreadsheet data.
(*NOTE: Technically you can do without redux-thunk, but it will only be more complicated.)
// actions/spreadsheet.js
// --- Action-creators ---
function requestSpreadsheet() {
return {
type: 'SPREADSHEET_REQUEST'
}
}
function receiveSpreadsheet(data) {
return {
type: 'SPREADSHEET_RECEIVED',
payload: {
data: data
}
}
}
function receiveSpreadsheetError(error) {
return {
type: 'SPREADSHEET_FAIL',
payload: {
error: error
}
}
}
// --- API ---
function fetchTable(tableInfo) {
// Code related to API here. Should just return a promise.
// Someting like...
return fetch('spreadsheet url here')
}
// --- Thunks ---
function getSpreadsheetData(tableInfo) {
return function (dispatch, getState) {
// Tell reducers that you are about to make a request.
dispatch(requestSpreadsheet())
// Make the request, then tell reducers about
// whether it succeeded or not.
return fetchTable(tableInfo).then(
data => dispatch(receiveSpreadsheet(data)),
error => dispatch(receiveSpreadsheetError(error))
)
}
}
Then you'll need to have a reducer or reducers that listen for those actions and update the application state accordingly. You should think about how you want the application state to be shaped. I'm assuming nothing else about your application.
// reducers/spreadsheet.js
const initialState = {
spreadsheetData: {}
loading: false,
errorMsg: ''
}
function spreadsheet(state = {}, action = {}) {
switch (action.type) {
case 'SPREADSHEET_REQUEST':
return {
...state,
loading: true
}
case 'SPREADSHEET_RECEIVED':
return {
...state,
loading: false,
spreadsheetData: action.payload.data,
errorMsg: ''
}
case 'SPREADSHEET_FAIL':
return {
loading: false,
errorMsg: action.payload.error
}
}
}
Then you have your view logic in React that treats the entire view as a function taking in the app state and returning the HTML you would like to display. You need to npm install --save react-redux to allow your React components to "listen" for changes in your redux app state, so they will re-render accordingly.
// containers/Spreadsheet.js
import { connect } from 'react-redux'
import SpreadsheetComponent from '../components/Spreadsheet'
// This function tells your SpreadsheetComponent about the
// parts of the app state that it should "listen" to. The
// component will receive them as normal react props.
// These fields can be named whatever is most convenient.
function mapStateToProps(appState) {
return {
spreadsheetData: state.spreadsheetData,
loading: state.loading,
errorMsg: state.errorMsg
}
}
//
export default connect(mapStateToProps)(SpreadsheetComponent)
// components/Spreadsheet.js
import React from 'react';
function Spreadsheet(props) {
return (
<h1>Here is the spreadsheet!</h1>
<ul>
{props.spreadsheetData.map(row => {
return (
<li>
<RowStuff row={row} />
</li>
)
})}
</ul>
)
}
And how do you wire all this stuff together?
// configureStore.js
import React from 'react'
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux'
import { render } from 'react-dom'
import thunk from 'redux-thunk';
import App from './components/App'
import rootReducer from './reducers/index';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Related
I have a react main component that dispatches redux action on componentDidMount, the action will fetch API data.
The problem is: when I start my application my componentDidMount and main component are executed twice. So, it makes 2 API calls for each time application loads. API has a limit for the total number of calls I make, I don't want to reach my limit.
I have already tried fixing the issue by removing constructor, using componentWillMount problem is not solved.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../redux/actions/fetchActions';
import TableHeader from './tableHeader';
class Main extends Component {
componentDidMount() {
console.log("mounted");
// this.props.dispatch(actions.fetchall("market_cap"));
}
render() {
console.log("rendered");
// console.log(this.props.cdata);
// console.log(this.props.cdata.data.data_available);
return <div className="">
<TableHeader {...this.props} />
</div>
}
}
export default Main;
///actions
import axios from 'axios';
export function fetchall(sort) {
return function (dispatch) {
axios.get(`https://cors-anywhere.herokuapp.com/https:-----------`)
.then(function (response) {
dispatch({
type: 'FETCH_DATA',
payload: response.data
})
})
.catch(function (error) {
console.log(error);
})
}
}
//reducer
let initialState = {
coins: [],
data_available: false,
};
export default function (state = initialState, action) {
switch (action.type) {
case 'FETCH_DATA':
return {
...state,
coins: action.payload,
data_available: true
}
default: return state;
}
}
//rootreducer
import { combineReducers } from 'redux';
import DataReducer from './dataReducer';
export default combineReducers({
data: DataReducer
});
////index
import {createStore, applyMiddleware} from 'redux';
import MapStateToProps from './components/mapStateToProps';
import rootReducer from './redux/reducers/rootReducer';
import {Provider} from 'react-redux';
import thunk from 'redux-thunk';
//const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer, applyMiddleware(...middleware));
ReactDOM.render(<Provider store={store}><MapStateToProps/></Provider>, document.getElementById("root"));
console image is posted for reference "rendered" is logged inside main component
"runned1" is logged inside main-subcomponent
"mounted" logged inside componentDidMount
"
I believe you can work around this by providing some additional logic in your componentDidmount. You should also make use of your component state.
Write something like this:
constructor(props){
super(props)
this.state = {
mounted: false
}
}
componentDidMount(){
if(!this.state.mounted){
this.props.dispatchmyAction()
this.setState({
mounted: true
})
}
}
This essentially says, if your component has already mounted once, then you will not make your action creator request.
If you watch your console.log carefully you can notice that your HMR Hot Module Reloading -plugin, re-mounts your component and this is the main reason behind this occurrence.
What this plugin does, is that it watches for your bundles code changes and on every time you save re-renders your component. There has been a lot of discussion as well that this plugin does not work all cases as expected.
Here is some material you might consider to go trough if you wish to use HMR.
Writing about HMR -
https://codeburst.io/react-hot-loader-considered-harmful-321fe3b6ca74
User guide for HMR -
https://medium.com/#rajaraodv/webpacks-hmr-react-hot-loader-the-missing-manual-232336dc0d96
The problem is solved when I removed webpack from the project. But can anyone answer how can I solve this while still using the webpack.
I am implementing asynchronous action creators using react-redux and redux-thunk. However, I am getting the following error message: Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
I know that actions are supposed to be plain objects, and that middleware like thunk is supposed to take care of the cases when they are not. I have read several tutorials and looked at every SO question I could find on this, but I still can't figure out where I'm going wrong. Am I setting up thunk incorrectly, or am I using action creators in a bad way? Might this be an error with webpack or something?
Below I've included the code snippets I believe are relevant. Please let me know if additional info is needed.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Route } from 'react-router';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
import Layout from './components/Layout.js';
import OrderPage from './containers/order-page';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
ReactDOM.render(
<Provider store={store}>
<App>
<Route exact path="/" component={Layout}/>
</App>
</Provider>,
document.querySelector('.app'));
reducers/order.js
import { FETCH_ERR, FETCH_SUCCESS, START_FETCH } from '../actions/types';
const initialState = {
fetching: false
};
export default (state=initialState, action)=>{
switch (action.type) {
case START_FETCH:
return {
fetching: true
}
case FETCH_ERR:
return {
err: action.payload.err,
fetching: false
}
case FETCH_SUCCESS:
return {
price: action.payload,
fetching: false
}
default:
return state
}
}
actions/price-fetch.js
import axios from 'axios'
const FETCH_ERR = 'FETCH_ERR'
const FETCH_SUCCESS = 'FETCH_SUCCESS'
const START_FETCH = 'START_FETCH'
const fetchSucc = (data)=>{
return{
type:FETCH_SUCCESS,
payload:data
}
}
const fetchFail = (message)=>{
return{
type:FETCH_ERR,
payload:message
}
}
const startFetch = () =>{
return{
type: START_FETCH,
payload:null
}
}
const fetchPrices = () =>{
return async (dispatch) =>{
try {
dispatch(startFetch())
let data = await axios.get('mybackendurl')
dispatch(fetchSucc(data))
} catch (error) {
dispatch(fetchFail({err:'failed to get shit'}))
}
}
}
export {
FETCH_ERR,
FETCH_SUCCESS,
fetchPrices,
START_FETCH
}
Relevant pieces of containers/order.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPrices } from '../actions/price-fetch';
class Order extends Component {
...
render() {
this.props.fetchPrices();
return ...
}
const mapDispatchToProps = dispatch => {
return {
fetchPrice: () => {
dispatch(fetchPrices())
}
}
}
function mapStateToProps(state){
return {
prices: state.order
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Order);
Thanks in advance for any help!
In case anyone comes across the same issue. The problem was not in the code shown above, or how I was dispatching actions. I had a duplicate definition of the redux-store in a different file, which overwrote the definition with the middleware.
In my case, I had the action declaration like below, due to which it was throwing such error.
export const withdrawMoney = (amount) => {
return (dispath) => {
dispath({
type: "withdraw",
payload: amount
})
}};
What I did was just changed my action definition to be an object type
export const depositMoney = (amount) => ({
type: "deposit",
payload: amount
});
And it jsut worked fine!
If anyone is here grasping at straws when using ImmutableJS + Typescript, turns out that you HAVE to define the "initialState" for the middleware to actually apply.
export const store = createStore(
combineReducers({APIReducer}),
{},
applyMiddleware(thunk.withExtraArgument(api))
);
I suspect it may be because you have an async (dispatch) function. That would cause it to return a Promise, which may be even confusing thunk.
In normal scenarios, the function itself would return another function, which thunk would inject the dispatch and call again and you would call dispatch inside the function:
arg => dispatch => dispatch({ type: arg });
When you add async, it basically becomes the same as this:
arg => dispatch => Promise.resolve(dispatch({ type: arg }));
You may have to ditch async/await inside of there and just use axios as a normal Promise, or add something extra to ensure it returns a nothing instead of a Promise.
const fetchPrices = () =>{`
return async (dispatch) =>{`
try {
dispatch(startFetch())
let data = await axios.get('mybackendurl')
dispatch(fetchSucc(data))
} catch (error) {
dispatch(fetchFail({err:'failed to get shit'}))
}
}
}
is returning a promise so when you do
const mapDispatchToProps = dispatch => {
return {
fetchPrice: () => {
dispatch(fetchPrices())
}
}
}
dispatch(fetchPrices()) is getting a promise not a plain object
the way i do these things is leave the heavy weight to my action; call async, when resolved dispatch data to store and in your component listen for and handle data(prices list) change.
const mapDispatchToProps = dispatch => {
return {
fetchPrice
}
}
you can thus show "loading please wait" while price list is empty and promise is not resolved/rejected
I am using a boilerplate called trufflebox's react-auth where
getWeb is called on loading the page (Link to code)
which creates the web3 object (Link to Code)
and stores web3 in the Redux store (Link to code)
Problem: When I retrieve the web3 object from the Redux store, it is undefined, most likely because web3 has not been created yet in Step 2 described above.
What should be the correct way to retrieve web3 from the Redux store only after it has been set?
layouts/test/Test.js
import React, { Component } from 'react';
import store from '../../store';
class Test extends Component {
componentWillMount() {
this.setState({
web3: store.getState().results.payload.web3Instance
})
this.instantiateContract()
}
instantiateContract() {
console.log(this.state.web3) // UNDEFINED!!
}
render() {
return (
<h1>Test</h1>
)
}
}
export default Test
Everything works if I retrieve web3 again without going to the Redux store:
import React, { Component } from 'react';
import getWeb3 from '../../util/web3/getWeb3';
class Test extends Component {
componentWillMount() {
getWeb3
.then(results => {
this.setState({
web3: results.payload.web3Instance
})
this.instantiateContract()
})
}
instantiateContract() {
console.log(this.state.web3)
}
render() {
return (
<h1>Test</h1>
)
}
}
export default Test
Resolve the promise just after creating the store
src/store.js
import getWeb3 from './util/web3/getWeb3';
import { createStore } from 'redux';
//... prepare middlewares and other stuffs , then : create store
const store = createStore(/*....configure it as you want..*/);
// Just after creating store, here the engineering:
getWeb3.then(results => {
// Assuming you have suitable reducer
// Assuming the reducer put the payload in state and accessible via "getState().results.payload.web3Instance"
store.dispatch({ type: 'SAVE_WEB3', payload: results.payload.web3Instance });
});
export default store;
In you ./index.js (where you are rendering the whole app) consider to use Provider component as wrapper to pass store behind the seen and have a singleton store.
src/index.js
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<Test />
</Provider>
)
Now, in your component, connect HOC will do everything , see comments below :
src/.../Test.js
import { connect } from 'react-redux';
class Test extends Component {
// No need any lifecyle method , "connect" will do everything :)
render() {
console.log(this.props.web3)
return (
<h1>Test</h1>
)
}
}
// Retrieve from Redux STATE and refresh PROPS of component
function mapStateToProps(state) {
return {
web3: state.results.payload.web3Instance // since you are using "getState().results.payload.web3Instance"
}
}
export default connect(mapStateToProps)(Test); // the awesome "connect" will refresh props
Maybe try calling instantiateContract during the componentWillReceiveProps phase. Check out the following...
componentWillMount() {
this.setState({
web3: store.getState().results.payload.web3Instance
});
}
instantiateContract() {
console.log(this.state.web3); // hopefully not undefined
}
componentWillReceiveProps(nextProps) {
if(nextProps.whatever) {
this.instantiateContract();
}
}
render() {
return (
<h1>Test</h1>
)
}
where nextProps.whatever is what you are mapping from redux (not totally sure what this is given your details). Ideally this is getting fed back into your component and when the value either populates or changes, you then call your function
Also, I see a lot of state management here opposed to what I would expect to see done via props. if componentWillReceiveProps(nextProps) is not a good hook given your application architecture, componentDidUpdate(prevProps, prevState) could be a viable alternative.
I am trying to get familiar with the flow of the react-boilerplate.
Till now I love how neat clean and easy to understand are things, I although feel that I miss a piece of the puzzle. Would be nice if someone with more experience could help me with that.
The problem I am facing at the moment goes as follows.
I am triggering an action within componentWillMount() of a specific component.
The action is being created in actions.js, its a simple get request made with axios.
The data are being processed in a promise middleware library redux-promise.
The promise is now being passed into the reducer of the specific component, where the whole state and the data that I need are being returned.
Trying to catch this state at the component is where I fail. I am trying to mapStateToProps but cannot find the data that I need there instead a Map {} is being received.
How do I Map this object with my props ?
I am sure I miss something important.
Here is my repo.
https://github.com/paschalidi/blog-react-redux
And here is my code so you can have a brief look.
index.js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { fetchPosts } from './actions'
import selectPostsIndex from './selectors'
export class PostsIndex extends React.Component { // eslint-disable-line react/prefer-stateless-function
componentWillMount() {
this.props.fetchPosts();
}
render() {
return (
<div>
<h3>Posts</h3>
<ul className="list-group">
A list would render here.
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log(state.posts)
//return { posts: state } //****I dont get why the redux state is not being given here.
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchPosts }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsIndex);
actions.js
import axios from 'axios'
import { FETCH_POSTS } from './constants';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=dsklhfksdhfjkdshfkjdshkj';
export function fetchPosts() {
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
store.js
import promise from 'redux-promise';
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
promise
];
reducer.js
import { fromJS } from 'immutable';
import {
FETCH_POSTS
} from './constants';
const initialState = fromJS({ all:[], post: null });
function postsIndexReducer(state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return { ...state, all: action.payload.data };
default:
return state;
}
}
export default postsIndexReducer;
Also the action is being registered in reducers.js
import PostsReducer from 'containers/PostsIndex/reducer'
export default function createReducer(asyncReducers) {
return combineReducers({
route: routeReducer,
language: languageProviderReducer,
posts: PostsReducer,
form: reduxFormReducer,
...asyncReducers,
});
}
Note I didn't test your code, but it looks like your reducer puts the fetched data in the field all of your global states posts field, but your mapStateToProps doesn't pick that up. Note that mapStateToProps should slice the part of the global state that the given component is interested in.
After a successful fetch the state you receive in mapStateToProps should look something like this:
{
posts: {
all: // whatever fetch returned
post: null
}
}
So your mapStateToProps could look something like this (note that this method receives the global state as an argument, not just for the specific reducer):
function mapStateToProps(state) {
// in component this.props.posts is { all: /* fetch result */, post: null }
return { posts: state.posts }
}
Also try to debug these methods, it becomes clearer once you see the flow of the data!
This GitHub issue covers this exact problem: https://github.com/reactjs/react-redux/issues/60.
I had to manually extract the values from the Map in mapStateToProps function:
const mapStateToProps = (state) => {
return {
posts: state.get('posts'),
};
}
Thanks to this StackOverflow post.
I've spent about 1 week reading up on redux before plunging into anything sizeable. After completing most of the tutorials I've done I realised, ok I understand redux but how the hell do I make a complex system :P
I started going about by creating my systems actions:
function requestLogin(creds) {
return {
type: LOGIN_REQUEST,
isFetching: true,
isAuthenticated: false,
creds
}
}
function receiveLogin(user) {
return {
type: LOGIN_SUCCESS,
isFetching: false,
isAuthenticated: true,
id_token: user.id_token
}
}
function loginError(message) {
return {
type: LOGIN_FAILURE,
isFetching: false,
isAuthenticated: false,
message
}
}
But how can I with each router used (using react-router) check to see if the user has a session after storing the users logged in state in the redux state?
I wanted to create something that gets executed with each view. Just simply write a function that exec()'s in each view?
Yes, you create a function that executes whenever you go to a route that requires a login.
import LoginActions from '../loginActions';
const requireLogin = (nextState, replace) => {
store.dispatch(LoginActions.CheckLogin());
const {login} = store.getState();
if (!login.isAuthenticated)
replace('/login');
};
Call it in your router:
<Router component={Root}>
<Route path="/dashboard" onEnter={requireLogin} component={dashboard}/>
</Router>
You can implement auth filter for paths requiring the user to be authenticated using Higher Order Components.
Create wrapping component
import React from 'react';
import { connect } from 'react-redux';
export default function(ComposedComponent) {
class AuthFilter extends React.Component {
// triggered when component is about to added
componentWillMount() {
if (!this.props.userAuthenticated) {
console.log("navigate to login");
this.context.router.push('/login');
}
}
// before component updated
componentWillUpdate(nextProps) {
if (!nextProps.userAuthenticated) {
console.log("navigate to login");
this.context.router.push('/login');
}
}
render() {
return <ComposedComponent {...this.props} />
}
}
AuthFilter.contextTypes = {
router: React.PropTypes.func.isRequired
}
function mapStateToProps(state) {
return { userAuthenticated: state.authenticated };
}
return connect(mapStateToProps)(AuthFilter);
}
And then add wrapping to your route component as:
Route path="/asset" component={AuthFilter(AssetRoute)}/>