I am trying to build an app based on https://github.com/orbitdb/orbit-db-control-center/ which works as intended but when I ported it to my application there are some issues.
Within a useEffect I am calling dispatch which is not changing the application's state. The dispatches are within async functions which are being called and behave as intended but the dispatches aren't updating the UI. I am using parcel to help hot-reload during dev. On page load the dispatches aren't being called but when the page is updated (not-refreshed) using parcel hot-reload the dispatches work as intended.
Systems.js
const [appState, dispatch] = useStateValue()
React.useEffect(() => {
dispatch({ type: actions.PROGRAMS.SET_PROGRAMS_LOADING, loading: true })
initIPFS().then(async (ipfs) => {
dispatch({ type: actions.SYSTEMS.SET_IPFS, ipfsStatus: 'Started' })
initOrbitDB(ipfs).then(async () => {
dispatch({ type: actions.SYSTEMS.SET_ORBITDB, orbitdbStatus: 'Started' })
const programs = await getAllDatabases(pid)
dispatch({ type: actions.PROGRAMS.SET_PROGRAMS, programs: programs.reverse() })
dispatch({ type: actions.PROGRAMS.SET_PROGRAMS_LOADING, loading: false })
})
})
}, [dispatch])
App.js
import React from 'react'
import {Outlet} from 'react-router-dom'
import { actions, loadingState,
StateProvider
} from './state'
import {Systems} from './components/Systems'
import {Header} from './components/Header'
import './index.css'
export function DBView () {
const initialState = {
user: null,
loginDialogOpen: false,
createDBDialogOpen: false,
addDBDialogOpen: false,
programs: [],
program: false,
db: null,
entries: [],
orbitdbStatus: 'Starting',
ipfsStatus: 'Starting',
loading: {
programs: false
}
}
const reducer = (state, action) => {
console.log(action)
switch (action.type) {
case actions.SYSTEMS.SET_ORBITDB:
return {
...state,
orbitdbStatus: action.orbitdbStatus
}
case actions.SYSTEMS.SET_IPFS:
return {
...state,
ipfsStatus: action.ipfsStatus
}
case actions.PROGRAMS.SET_PROGRAM:
return {
...state,
program: action.program
}
case actions.PROGRAMS.SET_PROGRAM_LOADING:
return {
...state,
program: loadingState
}
case actions.PROGRAMS.SET_PROGRAMS:
return {
...state,
programs: action.programs
}
case actions.DB.SET_DB:
return {
...state,
db: action.db,
entries: action.entries,
}
case actions.DB.OPEN_CREATEDB_DIALOG:
return {
...state,
createDBDialogOpen: true
}
case actions.DB.CLOSE_CREATEDB_DIALOG:
return {
...state,
createDBDialogOpen: false
}
case actions.DB.OPEN_ADDDB_DIALOG:
return {
...state,
addDBDialogOpen: true
}
case actions.DB.CLOSE_ADDDB_DIALOG:
return {
...state,
addDBDialogOpen: false
}
case actions.PROGRAMS.SET_PROGRAMS_LOADING:
return {
...state,
loading: { ...state.loading, programs: action.loading }
}
default:
return state
}
}
return (
<StateProvider initialState={initialState} reducer={reducer}>
<Header />
<Systems />
<Outlet />
</StateProvider>
)
}
state/index.js
import React, { createContext, useReducer, useContext } from 'react'
export const StateContext = createContext()
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
)
export const useStateValue = () => useContext(StateContext)
export const actions = {
DB: {
OPEN_CREATEDB_DIALOG: 'OPEN_CREATEDB_DIALOG',
CLOSE_CREATEDB_DIALOG: 'CLOSE_CREATEDB_DIALOG',
OPEN_ADDDB_DIALOG: 'OPEN_ADDDB_DIALOG',
CLOSE_ADDDB_DIALOG: 'CLOSE_ADDDB_DIALOG',
SET_DB: 'SET_DB'
},
SYSTEMS: {
SET_IPFS: 'SET_IPFS',
SET_ORBITDB: 'SET_ORBITDB'
},
PROGRAMS: {
SET_PROGRAMS: 'SET_PROGRAMS',
SET_PROGRAMS_LOADING: 'SET_PROGRAMS_LOADING',
SET_PROGRAM: 'SET_PROGRAM',
SET_PROGRAM_LOADING: 'SET_PROGRAM_LOADING'
}
}
export const loadingState = 'loading'
Related
I'm trying to check if my store is onboarded or not. for that, I'm making an API call through the redux to check it in the BE and if it's true I'll redirect it to the dashboard. I'm able to get the data successfully from BE, and on success checkIsStoreOnboardedSuccess() is called but in the reducer, the state is not updated with the CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS state in the reducer.
action.js
import * as actionTypes from './index';
import API from '../../api';
export const clearCheckIsStoreOnboarded = () => {
return {
type: actionTypes.CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING,
};
};
export const checkIsStoreOnboarded = (payload) => {
return (dispatch) => {
dispatch(checkIsStoreOnboardedInitiate());
API.getAccountSettings(payload)
.then((response) => {
checkIsStoreOnboardedSuccess(response.data);
})
.catch((err) => {
checkIsStoreOnboardedFailure(err);
});
};
};
const checkIsStoreOnboardedInitiate = () => {
return {
type: actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START,
};
};
const checkIsStoreOnboardedSuccess = (data) => {
return {
type: actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS,
data: data,
};
};
const checkIsStoreOnboardedFailure = (err) => {
return {
type: actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL,
data: err,
};
};
reducer.js
import * as actionTypes from '../actions';
const initialState = {
isLoading: true,
isError: false,
isDone: false,
data: [],
error: null,
};
const clearCheckIsStoreOnboarded = () => {
return initialState;
};
const checkIsStoreOnboardedStart = (state) => {
return { ...state, isLoading: true, error: null, isError: false };
};
const checkIsStoreOnboardedSuccess = (state, action) => {
return { ...state, data: action.data, isDone: true, isLoading: false };
};
const checkIsStoreOnboardedFailure = (state, action) => {
return { ...state, error: action.data, isLoading: false, isError: true };
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING:
return clearCheckIsStoreOnboarded();
case actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START:
return checkIsStoreOnboardedStart(state);
case actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS:
return checkIsStoreOnboardedSuccess(state, action);
case actionTypes.CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL:
return checkIsStoreOnboardedFailure(state, action);
default:
return state;
}
};
export default reducer;
actionTypes.js
export const CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING = 'CLEAR_CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING';
export const CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START = 'CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_START';
export const CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS = 'CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_SUCCESS';
export const CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL = 'CHECK_IS_STORE_ONBOARDED_FOR_ONBOARDING_FAIL';
onboard.js
import React, { useState, useEffect } from 'react';
import { withCookies } from 'react-cookie';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import Crew from './Crew';
import Service from './Services';
import Address from './Address';
import { useStyles } from './css/index.css';
import Header from './header';
import Stepper from './stepper';
import { getStoreID } from '../../../utils';
import {
clearCheckIsStoreOnboarded,
checkIsStoreOnboarded,
} from '../../../store/actions/check-is-store-onboarded-for-onboarding'
import Loader from '../../../components/CircularProgressLoader';
const OnboardScreen = ({
cookies,
clearCheckIsStoreOnboarded,
checkIsStoreOnboarded,
checkIsStoreOnboardedData,
}) => {
const [step, setStep] = useState(0);
// eslint-disable-next-line no-unused-vars
const [width, isDesktop] = useWindowWitdh();
const classes = useStyles(isDesktop);
const store_id = getStoreID(cookies);
useEffect(() => {
checkIsStoreOnboarded({
store_id,
});
}, []);
useEffect(() => () => clearCheckIsStoreOnboarded(), []);
if(checkIsStoreOnboarded.isDone){
<Redirect to='/dashboard'>
}
const updateStep = () => {
const updatedStep = step + 1;
setStep(updatedStep);
};
const onboardingScreenToRender = () => {
switch (step) {
case 0:
return (
<Crew />
);
case 1:
return (
<Service />
);
case 2:
return <Address />;
}
};
return (
<div className={classes.container}>
<Header isDesktop={isDesktop} />
<div className={classes.contentOfContainer}>
<div className={classes.titleHeader}>
Onboarding
</div>
<Stepper stepNumber={step} setStepNumber={setStep} />
{checkIsStoreOnboardedData.isLoading && <Loader />}
</div>
</div>
// <OnboardLoader />
);
};
const mapStateToProps = (state, ownProps) => {
return {
...ownProps,
checkIsStoreOnboardedData: state.checkIsStoreOnboardedForOnboardingReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
checkIsStoreOnboarded: (payload) => dispatch(checkIsStoreOnboarded(payload)),
clearCheckIsStoreOnboarded: () => dispatch(clearCheckIsStoreOnboarded()),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(withCookies(OnboardScreen));
You need to dispatch your actions:
export const checkIsStoreOnboarded = (payload) => {
return (dispatch) => {
dispatch(checkIsStoreOnboardedInitiate());
API.getAccountSettings(payload)
.then((response) => {
// here
dispatch(checkIsStoreOnboardedSuccess(response.data));
})
.catch((err) => {
// and here
dispatch(checkIsStoreOnboardedFailure(err)(;
});
};
};
That said: you are writing a very outdated style of Redux here - in modern Redux, all of that would probably be possible with 1/4 of the code. If you are just learning Redux, you are probably following a very outdated tutorial. Modern Redux does not require you to write action type strings or action creators and your reducers can contain mutable logic. Also, it does not use connect unless you are working with legacy class components (which you don't seem to be doing).
I really recommend you to read the official Redux tutorial at https://redux.js.org/tutorials/essentials/part-1-overview-concepts
So when I check Redux dev-tools i see that I've received my data and they are a part of the state, but when I try to use conditional rendering it wont render the page and gives error TypeError: Cannot read property 'Global Quote' of undefined !
If I just use this.props.data.TSLA it works fine and the page renders...
When I use this.props.data.TSLA["Global Quote"]["01. symbol"] page won't render! (the keys are strings in the JSON so I need to use square brackets).
I am also using Redux-Thunk !
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { START_FETCH_DATA } from './redux/dataReducer';
class Fetcher extends Component {
componentDidMount() {
this.props.START_FETCH_DATA()
}
render() {
const { data, dataLoading } = this.props;
return (
<li className="tesla-container">
{ this.props.dataLoading ?
(<div className="ticker"> Loading! </div>)
:
(<div className="ticker">{
this.props.data.TSLA["Global Quote"]["01. symbol"] }</div>) }
</li>
)
}
const mapStateToProps = (state) => {
return {
data: state.data,
dataLoading: state.dataLoading
}
}
const mapDispatchToProps = (dispatch) => {
return {
START_FETCH_DATA: bindActionCreators(START_FETCH_DATA, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Fetcher)
and here is the reducer + actions...
export const dataReducer = (state = {dataLoading: true}, action) => {
switch(action.type) {
case "START_FETCH_DATA":
return {...state, dataLoading: true}
case "FINISH_FETCH_DATA":
return {...state, dataLoading: false, data: action.payload}
default:
return state;
}};
export const START_FETCH_DATA = () => {
return (dispatch) => {
Promise.all(
[
fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=TSLA&apikey=LOL`).then(data => data.json()),
fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=AMZN&apikey=LOL`).then(data => data.json())
]
)
.then(([TSLA, AMZN]) => {
dispatch({ type: "FINISH_FETCH_DATA", payload: {TSLA, AMZN} })
})
}};
DEVTOOLS SCREENSHOT
https://imgur.com/a/2Tcrdpe
For starters, you should use the data provided by redux in render():
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { START_FETCH_DATA, dataReducer } from './redux/dataReducer';
class Fetcher extends Component {
componentDidMount() {
this.props.START_FETCH_DATA()
}
render() {
return (
<li className="tesla-container">
{ this.props.dataLoading ?
(<div className="ticker"> Loading! </div>)
:
(<div className="ticker">{
this.props.data.TSLA["Global Quote"]["01. symbol"] }</div>) }
</li>
)
}
const mapStateToProps = (state) => {
return {
data: state.data,
dataLoading: state.dataLoading
}
}
const mapDispatchToProps = (dispatch) => {
return {
START_FETCH_DATA: bindActionCreators(START_FETCH_DATA, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Fetcher)
I fixed my issue.
Fixed code below:
`const mapStateToProps = (state) => {
return {
dataLoading: state.dataReducer.dataLoading,
data: state.dataReducer.data
}
}`
instead of
`const mapStateToProps = (state) => {
return {
dataLoading: state.dataLoading,
data: state.data
}
}`
I feel little confused, the problem is defineAvailableTouch action and state update connected to it.
Here is my code:
Actions/index.js
import {
ANIMATE_HELLO,
HANDLE_SCROLL,
IS_TOUCH_DEVICE,
SET_ABOUT_TOP,
SET_CONTACT_TOP,
SET_PORTFOLIO_TOP
} from "../Constants/ActionTypes";
export const animateHello = hello => ({
type: ANIMATE_HELLO,
payload: hello
});
export const handleScroll = scrollDelta => ({
type: HANDLE_SCROLL,
payload: scrollDelta
});
export const defineTouchAvailable = isTouchDevice => ({
type: IS_TOUCH_DEVICE,
payload: isTouchDevice
});
export const setAboutTop = aboutTop => ({
type: SET_ABOUT_TOP,
payload: aboutTop
});
export const setContactTop = contactTop => ({
type: SET_CONTACT_TOP,
payload: contactTop
});
export const setPortfolioTop = portfolioTop => ({
type: SET_PORTFOLIO_TOP,
payload: portfolioTop
});
Reducers/index.js
import {
IS_TOUCH_DEVICE,
} from "../Constants/ActionTypes";
import { initialState } from "../Constants/InitialState/InitialState";
export const rootReducer = (state = initialState, action) => {
switch(action.type) {
case ANIMATE_HELLO:
return {
...state,
hello: action.payload
};
case HANDLE_SCROLL:
return {
...state,
scrollState: action.payload
};
case IS_TOUCH_DEVICE:
console.log(action.payload); //!!!!!! THIS PRINTS EXPECTED VALUE !!!!!!!!!!
return {
...state,
isTouchDevice: action.payload
};
case SET_ABOUT_TOP:
return {
...state,
aboutTop: action.payload
};
case SET_CONTACT_TOP:
return {
...state,
contactTop: action.payload
};
case SET_PORTFOLIO_TOP:
return {
...state,
portfolioTop: action.payload
};
default:
return state
}
};
InitialState.js
export const initialState = {
scrollState: 0,
hello: 'H',
aboutTop: 0,
portfolioTop: 0,
contactTop: 0,
isTouchDevice: true
};
App.js
import React, { Component } from 'react';
import { connect } from "react-redux";
import About from "./Containers/About";
import Contact from "./Containers/Contact";
import Page from "./Containers/Page";
import Projects from "./Containers/Projects";
import {
defineTouchAvailable,
handleScroll
} from "./Actions";
window.onbeforeunload = () => {
handleScroll(0);
document.documentElement.scrollTop = 0;
};
const mapStateToProps = state => {
return {
isTouchDevice: state.isTouchDevice
}
};
const dispatchStateToProps = dispatch => {
return {
defineTouchAvailable: isTouchDevice =>
dispatch(defineTouchAvailable(isTouchDevice)),
handleScroll: scrollState => dispatch(handleScroll(scrollState))
}
};
class App extends Component {
componentDidMount() {
try {
document.createEvent('touchevent');
this.props.defineTouchAvailable(true);
} catch(e) {
this.props.defineTouchAvailable(false);
}
console.log(this.props.isTouchDevice); //!!!!!!!!!!!!!!! THIS ALWAYS PRINTS VALUE FROM initialState !!!!!!!!!!!!!!
if(this.props.isTouchDevice) {
document.documentElement.scroll(0, 1);
}
document.addEventListener('scroll', () => {
if (document.documentElement.scrollTop === 0) {
this.props.handleScroll(0);
}
});
}
render() {
return (
<div>
<Page/>
<Projects/>
<About/>
<Contact/>
</div>
);
}
}
export default connect(mapStateToProps, dispatchStateToProps)(App);
I really can't figure out whats wrong here.
As I commented
reducer console.log prints correct value that is expected to be assigned to my state (isTouchDevice field), but
after assigning it in dispatch action nothing changes - it is always value from initialState.
Can someone please explain it to me? Do I change my redux state uncorrectly? Then why other actions work as they're expected to?
The updated value of isTouchDevice will be available in componentDidUpdate, render or componentWillReceiveProps, not in componentDidMount.
componentDidMount will only be called one time when your component is mounted.
Note: componentWillReceiveProps is deprecated, better to not use it.
I am new to redux and I am having a hard time understanding how to connect the payload of my API call to my state.
Right now my action.js file looks like this:
import ApiService from '../../services/ApiService';
import { reset } from 'redux-form';
//actions
export const getStock = () => {
return {
type: 'GET_STOCK'
}
}
export const getStockPending = () => {
return {
type: 'GET_STOCK_PENDING'
}
}
export const getStockFulfilled = (stock) => {
return {
type: 'GET_STOCK_FULFILLED',
payload: stock
}
}
export const getStockRejected = () => {
return {
type: 'GET_STOCK_REJECTED'
}
}
// async function calls
export function fetchStocksWithRedux() {
const action_type = "GET_STOCK";
const stock = 'AAPL';
return (dispatch) => {
dispatch({type: `${action_type}_PENDING`});
return ApiService.get(`/search?query=${stock}`)
.then(([response, json]) =>{
if(response.status === 200){
dispatch(getStockFulfilled(json))
}
else{
dispatch(getStockRejected())
}
})
}
}
and my reducer.js file looks like this:
const initialState = {
inProgress: false,
stock: {},
stocks: ['NKE', 'AMZN', 'AAPL'],
error: {}
}
export default (state = initialState, action) => {
switch(action.type) {
case 'GET_STOCK_PENDING':
return {
...state,
inProgress: true,
error: false
}
case 'GET_STOCK_FULFILLED':
return {
...state,
stock: action.payload,
inProgress: false
}
case 'GET_STOCK_REJECTED':
return {
...state,
inProgress: false,
error: action.error
}
default:
return state;
}
}
When I go to call my method fetchStocksWithRedux in my component, the network tab in my dev tools shows a 200 status and the response I'm expecting, but the reducer dispatches the 'GET_STOCK_REJECTED' action, but the error hash is empty. What do you think is going wrong?
Here is my component, for reference:
import React, { Component } from 'react';
import { fetchStocksWithRedux } from '../../redux/modules/Stock/actions';
import { connect } from 'react-redux';
class Dashboard extends Component {
componentDidMount() {
this.props.fetchStocksWithRedux()
}
render() {
return (
<div className="uk-position-center">
</div>
)
}
}
export default connect(
state => ({
stocks: state.stocks,
stock: state.stock
})
, { fetchStocksWithRedux }
)(Dashboard);
Thanks. Any advice or guidance would be greatly appreciated!
So I'm learning redux currently and I'm making an app that displays a list of articles. But I can't figured out why my data from my back end isn't showing up. I'm not sure where my error is that preventing my data from my backend from showing up? I know it not the setup of redux because I did simpler app to see if that was the problem and it wasn't so it has to do more with the action, reducers , and component. I would like to go farther eventually when there is more data in the database where it provides a link so it goes to another page that shows all the information about that article.
data from my node backend
[{"_id":"58c71df9f7e4e47f1fe17eeb","article":"words words","author":"Jason","date":"1/2/2014","title":"my article","__v":0}]
fashionActions.js
import axios from "axios";
export function fetchFashionArticle() {
return function(dispatch) {
axios.get("http://localhost:3000/api/fashion")
.then((response) => {
dispatch({type: "FETCH_FASHIONARTICLES_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_FASHIONARTICLES_REJECTED", payload: err})
})
}
}
export function addFashionArticle(_id, title,date, author, article) {
return {
type: "ADD_FASHIONARTICLE",
payload: {
_id,
title,
date,
author,
article,
},
}
}
export function updateFashionArticle(_id, title,date, author, article) {
return {
type: "UPDATE_FASHIONARTICLE",
payload: {
_id,
title,
date,
author,
article,
},
}
}
export function deleteFashionArticle(id) {
return {type: 'DELETE_FASHIONARTICLE', payload: id}
}
FashionArticle.js
import React from "react";
import { connect } from "react-redux";
import {fetchFashionArticle} from "../actions/fashionActions";
#connect((store) => {
return {
fashionarticles:store.fashionarticles.fashionarticles,
};
})
export default class FashionArticle extends React.component {
fetchFashionArticle() {
this.props.dispatch(fetchFashionArticle())
}
render() {
const { fashionarticles } = this.props;
if(!fashionarticles.length) {
return <button onClick={this.fetchFashionArticles.bind(this)}>Load articles</button>
}
const mappedArticles = fashionarticles.map(fashionarticle => <li>{fashionarticle}</li>)
return(
<div>
<h1>Fashion Article</h1>
<h2>{fashionarticles.title}</h2>
</div>
)
}
}
fashionArticleReducers.js
export default function reducer(state={
fashionarticles: [],
fetching: false,
fetched: false,
error: null,
}, action) {
switch (action.type) {
case "FETCH_FASHIONARTICLES": {
return {...state, fetching: true}
}
case "FETCH_FASHIONARTICLES_REJECTED": {
return {...state, fetching: false, error: action.payload}
}
case "FETCH_FASHIONARTICLES_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
fashionarticles: action.payload,
}
}
case "ADD_FASHIONARTICLE": {
return {
...state,
fashionarticles: [...state.fashionarticles, action.payload],
}
}
case "UPDATE_FASHIONARTICLE": {
const { _id, title,date,author,article } = action.payload
const newFashionArticles = [...state.fashionarticles]
const fashionarticleToUpdate = newFashionArticles.findIndex(fashionarticle => fashionarticle.id === id)
newFashionArticles[fashionarticleToUpdate] = action.payload;
return {
...state,
fashionarticles: newFashionArticles,
}
}
case "DELETE_FASHIONARTICLE": {
return {
...state,
fashionarticles: state.fashionarticles.filter(fashionarticle => fashionarticle.id !== action.payload),
}
}
}
return state
}
index.js
import { combineReducers } from 'redux';
import user from './testReducers'
import fashionarticles from './fashionArticleReducers';
export default combineReducers({
user,
fashionarticles,
})
You're sending the payload with the axios response as type FETCH_FASHIONARTICLES_DONE but your reducer is listening for FETCH_FASHIONARTICLES_FULFILLED