Hi so I'm doing a project with spotify api and I'm trying to get new releases. The data is being passed down to the reducer however when I call the fetch action in my App component and try to console.log the new releases (this.props.newAlbums) it's empty. Fetching user data still works, but fetching new releases doesn't.
This is my fetch action in actions/index.js.
export const fetchUserData = (accessToken) => (dispatch) =>{
fetch('https://api.spotify.com/v1/me', {
headers: {'Authorization': 'Bearer ' + accessToken}})
.then(response => response.json())
.then(data =>
dispatch({
type:"FETCH_USER_DATA",
payload: data
})
);
};
export const fetchNewAlbums = (accessToken) => dispatch =>{
fetch('https://api.spotify.com/v1/browse/new-releases?limit=5',{
headers:{'Authorization': 'Bearer ' + accessToken}})
.then(res => res.json())
.then(albums =>
dispatch({
type:"FETCH_NEW_ALBUMS",
payload:albums.albums.items
})
);
}
This is the reducer for new releases (newAlbumReducer.js)
const initialState={
albums:null
}
const newAlbumReducer = (state=initialState,action)=>{
switch(action.type){
case "FETCH_NEW_ALBUMS":
return{
...state,
albums:action.payload
};
default:
return state;
}
}
export default newAlbumReducer;
My store
import {createStore, applyMiddleware} from "redux";
import rootReducer from "../reducers/rootReducer";
import thunk from "redux-thunk";
import logger from "redux-logger";
const initialState = {};
const store = createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
export default store;
And my App.js
import React, {Component} from 'react';
import "../css/style.css";
import queryString from 'query-string';
import {connect} from "react-redux";
import PropTypes from "prop-types";
import {fetchUserData,fetchNewAlbums} from "../actions/index.js";
import SignInButton from "../components/SignInButton";
import SearchBar from "../components/SearchBar";
import MusicCards from "../components/Cards/MusicCards";
class App extends Component {
constructor(props){
super(props);
this.state = {
isShowAlbumsClicked:false
};
this.handleSignIn = this.handleSignIn.bind(this);
this.handleShowAlbumsClick = this.handleShowAlbumsClick.bind(this);
}
componentDidMount(){
let parsed = queryString.parse(window.location.search);
let accessToken = parsed.access_token;
if(!accessToken){
return;
}
this.setState({
accessToken:accessToken
})
this.props.fetchUserData(accessToken);
this.props.fetchNewAlbums(accessToken);
console.log(this.props.newAlbums);
}
handleSignIn(e){
e.preventDefault();
window.location=window.location.href.includes('localhost') ? 'http://localhost:8888/login':'https://something.herokuapp.com/login'
}
handleShowAlbumsClick(e){
this.setState({
isShowAlbumsClicked:!this.state.isShowAlbumsClicked
})
}
render(){
return (
<div className="app">
{this.props.userData ?
<div>
<h1 style={{'fontSize': '40px', 'marginTop':'1%'}}>
Welcome {this.props.userData.display_name} to Find Your Music
</h1>
<SearchBar/>
{this.state.isShowAlbumsClicked ?
<div>
<MusicCards/>
</div> : <h2 onClick={this.handleShowAlbumsClick}>Show</h2>
}
</div> : <SignInButton handleSignIn={this.handleSignIn}/>
}
</div>
);
}
}
App.propTypes={
fetchUserData:PropTypes.func.isRequired,
fetchNewAlbums:PropTypes.func.isRequired,
userData:PropTypes.object,
newAlbums:PropTypes.object
}
const mapStateToProps = (state) =>{
return{
userData: state.userData.data,
newAlbums: state.newAlbum.albums
};
};
const mapDispatchToProps = {
fetchUserData,
fetchNewAlbums
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
So it looks like you already have redux-thunk installed but you are not using it.
I see you passed thunk to apply middleware in create store, that's good.
Try something like this in your action creator.
export const fetchNewAlbulms = (accessToken) => async dispatch => {
const response = await fetch('https://api.spotify.com/v1/browse/new-releases?limit=5',{headers:{'Authorization': 'Bearer ' + accessToken}})
dispatch({
type:"FETCH_NEW_ALBUMS",
payload:albums.albums.items
})
}
or this (same thing)
export const fetchNewAlbulms = (accessToken) => {
return async dispatch => {
const response = await fetch('https://api.spotify.com/v1/browse/new-releases?limit=5',{headers:{'Authorization': 'Bearer ' + accessToken}})
dispatch({
type:"FETCH_NEW_ALBUMS",
payload:albums.albums.items
})
}
}
Your reducer should not need any updating.
This will wait to dispatch until it gets a response.
Hope this helps.
Related
I am currently trying to access my data using the Spotify API. This works very well. Thats the function I am using to get my Data. I assume the other stuff is not important. I can post that, if you need that.
export const getSpotifyUser = (access_token:string) =>{
setAuthorizationHeader(access_token)
axios.get('https://api.spotify.com/v1/me').then((res) => {
console.log(res.data)
})
}
I have set up a redux store and trying to put the credentials into the store, by dispatching the right type (SET_USER).
export const getSpotifyUser = (access_token:string) => (dispatch: any) => {
console.log("function is not called") // Function is not even called why ?
setAuthorizationHeader(access_token)
axios.get('https://api.spotify.com/v1/me').then((res) => {
console.log(res.data)
dispatch ({
type: SET_USER,
payload: res.data
})
}
but as soon as I use dispatch, the function is no longer called.
I really do not see my mistake. Is that a typescript error ?. ( I am using react typescript)
store.js
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './rootReducer'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
)
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
export default store
rootReducer.ts
import { combineReducers } from 'redux'
import userReducer from './User/userReducer'
const rootReducer = combineReducers({
user: userReducer,
})
export default rootReducer
userReducer.ts
import { AnyAction } from 'redux'
import { SET_USER } from './userTypes'
interface Credentials {
username: string
email: string
profilepicture: string
id: number
}
interface InitialState {
authenticated: boolean
loadding: boolean
credentials?: Credentials
}
const initialState: InitialState = {
authenticated: false,
loadding: false,
credentials: {} as Credentials,
}
const reducer = (state = initialState, action: AnyAction) => {
switch (action.type) {
case SET_USER: {
return {
...state,
loading: false,
credentials: action.payload,
}
}
default:
return state
}
}
export default reducer
Login.tsx ( I am making the login here. It is working fine, if am not using dispatch
import { IonButton } from '#ionic/react'
import React, { useEffect } from 'react'
import {
getAuthorizeHref,
getHashParams,
removeHashParamsFromUrl,
getSpotifyUser,
} from '../../Helpers/login'
const Login: React.FC = () => {
// const user = useSelector((state: RootState) => state.user.credentials)
useEffect(() => {
const hashParams = getHashParams()
const access_token = hashParams.access_token
// const expires_in = hashParams.expires_in
removeHashParamsFromUrl()
getSpotifyUser(access_token)
}, [])
return (
<IonButton onClick={() => window.open(getAuthorizeHref(), '_self')}>
)}
export default Login
since you're using typescript with react, I believe you have added the getSpotifyUser function to your interface, now if you want to access that i think you should call it like this
props.getSpotifyUser(access_token)
and finally add it to your connect as a dispatch function that's wrapping your component
your login component should be like this one
import { IonButton } from '#ionic/react'
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import {
getAuthorizeHref,
getHashParams,
removeHashParamsFromUrl,
getSpotifyUser,
} from '../../Helpers/login'
interface ILogin {
getAuthorizeHref: () => any;
getHashParams: () => any;
removeHashParamsFromUrl: () => any;
getSpotifyUser: (access_token) => any;
}
const Login: React.FC = (props: ILogin) => {
// const user = useSelector((state: RootState) => state.user.credentials)
useEffect(() => {
const hashParams = props.getHashParams()
const access_token = hashParams.access_token
// const expires_in = hashParams.expires_in
props.removeHashParamsFromUrl()
props.getSpotifyUser(access_token)
}, [])
return (
<IonButton onClick={() => window.open(props.getAuthorizeHref(), '_self')}>
)}
export default connect(null, {getAuthorizeHref, getHashParams, removeHashParamsFromUrl, getSpotifyUser})(Login)
Basicly Shamim has given the right answer.Any function that uses that dispatch is a redux action, and you have to follow the docs specifically to call that function. You have to use connect to dispatch actions. As an alternative you can use the dispatchHook. If am wrong please please correct me !!!!
Thats the right code I just had to correct Login.tsx
import { IonApp, IonButton } from '#ionic/react'
import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import {
getAuthorizeHref,
getHashParams,
removeHashParamsFromUrl,
getSpotifyUser,
} from '../../Helpers/login'
const style = {
Logo: {
display: 'flex',
justifyContent: 'space-evenly',
color: 'white',
position: 'relative',
top: '70%',
} as const,
}
const Login: React.FC = (props: any) => {
// const user = useSelector((state: RootState) => state.user.credentials)
useEffect(() => {
const hashParams = getHashParams()
const access_token = hashParams.access_token
// const expires_in = hashParams.expires_in
removeHashParamsFromUrl()
console.log('halloeuseeffect')
props.getSpotifyUser(access_token)
console.log('halloeuseeffect')
}, [])
return (
<IonApp>
<IonButton onClick={() => window.open(getAuthorizeHref(), '_self')}>
knsnan
</IonApp>
)
}
export default connect(null, {
getSpotifyUser,
})(Login)
I am working on project where I am stuck in this problem. The thing is, I am calling an axios API and after its success I want to update my redux state i.e. in the .then() chain of axios. How can I achieve that? As what I have tried by applying what I know is -> I have created a react-redux dispatch in my component. I know how to do this in normal onClick but in then method I don't know how to trigger that.
I have tried doing this:
let submitForm = (e) => {
e.preventDefault();
// Axios request
const url = 'http://localhost:5000/api/v1/users/login'
axios({
//Api details
})
.then(res => {
// Store API data in LocalStorage
})
.then(() => {
LogIN(); // Here I want to change redux state //
history.push('/dashboard')
})
}
--Component
function Signin({LogIN}) {
return (
)
}
const mapDispatchToProps = dispatch => {
return {
LogIN: () => dispatch(login_action())
}
}
export default connect(null , mapDispatchToProps)(Signin)
After doing this, I see same state with no difference
Here is redux:
const login_action = () => {
return {
type : 'LOG-IN'
}
}
const loginLogOutReducer = (state = false, action) => {
switch (action.type) {
case 'LOG_IN':
return !state
default:
return state
}
}
const AllReducers = combineReducers({
isLoggedIn : loginLogOutReducer
})
You can use redux-thunk and function component in react hook
App.js
import {Provider} from 'react-redux'
import store from './store'
<Provider store={store()}>
<AppComponent />
</Provider>
store.js
import {applyMiddleware, compose, createStore} from 'redux'
import thunk from 'redux-thunk'
import {initialState, rootReducer} from './reducers'
const store = () => {
return createStore(rootReducer, initialState, compose(applyMiddleware(thunk)))
}
export default store
reducer.js
import {actionTypes} from './actionTypes'
const initialState = {}
const rootReducer = (state = initialState, action) => {
if (action.type === actionTypes.STH) {
return {
...state,
sth: action.payload,
}
}
}
export {initialState, rootReducer}
actionTypes.js
export const actionTypes = {
STH: 'STH'
}
Component
...
const onChange = => {
dispatch(actionFunc()).then(res => {
// DO Something
})
...
action.js
const actionFunc = () => {
return (dispatch, getState) => {
return axios({
//Api details
}).then(res => res).catch(err => err)
}
}
For some reason, even though the reducer runs and console.log shows that the correct data was passed to it, the redux store was not updated.
Relevant files:
App.jsx
import {Provider} from 'react-redux';
import store from './store';
const Stack = createStackNavigator();
export default class App extends Component {
render() {
return (
<Provider store={store()}>
Store.js
import {createStore, applyMiddleware} from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
const store = (initialState = {}) =>{
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
)
}
export default store;
Register.tsx
...
<Pressable
style={styles.button}
onPress={() => this.props.submitRegistration(this.state)}
>
...
const mapDispatchToProps = (dispatch: any) => {
return {
submitRegistration: (data: any) => {
dispatch(UserActions.submitRegister(data))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Register);
UserActions
import { signUp } from '../../services/backend';
import { setUser } from '../../actions';
export function submitRegister(data: any) {
return async (dispatch: any) => {
const response = await signUp(data);
const responseData = await response.json();
if(responseData.token) {
console.log('here', responseData);
dispatch(setUser(responseData.user));
}
};
}
Action creator
export const setUser = (user: any) => ({
type: 'SET_USER',
user
});
User Reducer
import { SET_USER } from "../actions/actionTypes"
const initialState = {
user: {}
}
const User = (state = initialState, action: any) => {
switch(action.type) {
case SET_USER:
console.log('here action', action.user);
return { user: action.user}
default:
return state
}
}
export default User;
I would really appreciate any help possible. Seems like I misconfigured in someway because even when I set initial state :
const initialState = {
user: {firstName: "John"}
}
it's not reflected in the redux store.
In your action creator:
export const setUser = (user: any) => (
return {
type: 'SET_USER',
user
});
Still getting used to Redux, first off. I have a component that should simply load data for display when the component loads. I have redux setup with the store:
//store.js
import { createStore, applyMiddleware, compose } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import root from './reducers';
const middleware = [thunk, logger];
const initState = {};
const store = createStore(
root,
initState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
and all the reducers that I'll need in a full on combine reducers file:
//{projectFolder}/reducers/index.js
import { combineReducers } from 'redux';
import authReducer from './authReducer';
import errorsReducer from './errorReducer';
import suggestionReducer from './suggestionReducer';
import insiderReducer from './insiderReducer';
import connectionReducer from './connectionReducer';
import outsiderReducer from './outsiderReducer';
import contactReducer from './contactReducer';
import metaReducer from './metaReducer';
export default combineReducers({
auth: authReducer,
errors: errorsReducer,
suggestions: suggestionReducer,
insider: insiderReducer,
connection: connectionReducer,
outsider: outsiderReducer,
contact: contactReducer,
meta: metaReducer
});
The one that I'm interested in is the metaReducer which is the called by an action, or so it should be.
//metaReducer.js
import {GET_INSIDER_META_INFORMATION, GET_OUTSIDER_META_INFORMATION } from '../actions/types';
const initState = {
insider: {},
outsider: {}
};
export default (state = initState, { type, payload }) => {
switch (type) {
case GET_INSIDER_META_INFORMATION:
return{
...state,
insider: payload
}
case GET_OUTSIDER_META_INFORMATION:
return {
...state,
outsider: payload
}
default:
return state;
}
};
The meta reducer is just to house the information coming from the back-end and is each case of the reducer is called from the actions/meta.js file which looks like this:
//{projectfolder}/actions/meta.js
import {
GET_INSIDER_META_INFORMATION,
GET_OUTSIDER_META_INFORMATION,
POPULATE_ERRORS
} from "./types";
import Axios from "axios";
export const getMetaInsider = (dispatch) => {
return Axios.get("meta/insiders")
.then(res =>
dispatch({ type: GET_INSIDER_META_INFORMATION, payload: res.data })
)
.catch(err =>
dispatch({ type: POPULATE_ERRORS, payload: err.response.data })
);
};
export const getMetaOutsider = (dispatch) => {
return Axios.get("meta/outsiders")
.then(res => {
dispatch({ type: GET_OUTSIDER_META_INFORMATION, payload: res.data });
})
.catch(err =>
dispatch({ type: POPULATE_ERRORS, payload: err.response.data })
);
};
and My component that calls all of this is setup as below:
//{projectfolder}/components/home.js
import React, {Component} from 'react';
import {Card, CardTitle, CardSubtitle, CardBody} from 'reactstrap';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {getMetaInsider, getMetaOutsider} from '../actions/meta';
class Home extends Component{
constructor(props){
super(props);
this.state = {
insider:{},
outsider: {}
}
}
componentDidMount() {
console.log(this.props);
this.props.getMetaInsider();
this.props.getMetaOutsider();
}
render(){
let {insiders, outsiders} = this.state;
return(
<React.Fragment>
{*/ omitted as it's not really an issue right now, data is more important than layout /*}
</React.Fragment>
)
}
}
const mapState = state => {
console.log(state);
return {
insider: state.meta.insider,
outsider: state.meta.outsider
}
};
Home.propTypes = {
getMetaInsider: PropTypes.func.isRequired,
getMetaOutsider: PropTypes.func.isRequired,
insider: PropTypes.object.isRequired,
outsider: PropTypes.object.isRequired
};
export default connect(mapState, {getMetaInsider, getMetaOutsider})(Home);
So when the component loads, I get a horribly weird issue where it looks like jquery is being called, and it's imported in my App.js file for bootstrap. However, the main error is this:
"TypeError: dispatch is not a function
at http://localhost:3000/static/js/bundle.js:73524:22"
Which maps up to the .catch block of the getMetaInsider function.
You have to do something like this:
export const getMetaOutsider = () => {
return (dispatch) => {
Axios.get("meta/outsiders")
.then(res => {
dispatch({ type: GET_OUTSIDER_META_INFORMATION, payload: res.data });
})
.catch(err =>
dispatch({ type: POPULATE_ERRORS, payload: err.response.data })
);
}
};
Try this, It should work. Feedbacks are welcome.
redux-thunk handles functions passed as the argument to dispatch instead of objects.
This issue likely stems from a misconfiguration of redux-thunk or a misunderstanding of how to write a thunk. I've tried a lot of different ways, but from what I can tell, this should work. However, I'm still getting a console message that says its firing a redux action of undefined.
Here is my store configuration
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import App from './components/App';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('rootElement')
);
Here is my action:
import axios from 'axios';
export const GET_ABOUT_CONTENT_REQUEST = 'GET_ABOUT_CONTENT_REQUEST';
export const GET_ABOUT_CONTENT_FAILED = 'GET_ABOUT_CONTENT_FAILED';
export const GET_ABOUT_CONTENT_OK = 'GET_ABOUT_CONTENT_OK';
export const fetchAboutContent = () => {
const url = `http://localhost:3000/about`;
return (dispatch, getState) => {
if (getState.isInitialized === true){
console.log("desktop init should not be called when already desktop is init")
return Promise.resolve();
}
if (getState.about.isLoading) {
console.log('is loading');
return Promise.resolve();
}
dispatch({ type: GET_ABOUT_CONTENT_REQUEST });
axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));
}
}
Here is me firing the action in my component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../../actions/about';
import getAboutContent from '../../reducers';
class AboutMe extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.getAboutContent();
}
render() {
return <div>{ this.props.content }</div>
}
}
const mapStateToProps = (state) => ({
content: {} || getAboutContent(state)
})
const mapDispatchToProps = (dispatch) =>
bindActionCreators({ getAboutContent }, dispatch)
export default connect(
mapStateToProps, mapDispatchToProps
)(AboutMe);
I've tried quite a few configurations for mapDispatchToProps, i.e. connect(..., { fetchData: getAboutContent })..., and more. Any help is greatly appreciated.
Edit:
Here is the git repo, if that is helpful to anybody: https://github.com/sambigelow44/portfolio-page
Check your reducer name,you export fetchAboutContent, but import getAboutContent.
Code in action file is seems to be incorrect.
getState is a function.
const state = getState();
Change below code.
import axios from 'axios';
export const GET_ABOUT_CONTENT_REQUEST = 'GET_ABOUT_CONTENT_REQUEST';
export const GET_ABOUT_CONTENT_FAILED = 'GET_ABOUT_CONTENT_FAILED';
export const GET_ABOUT_CONTENT_OK = 'GET_ABOUT_CONTENT_OK';
export const fetchAboutContent = () => {
const url = `http://localhost:3000/about`;
return (dispatch, getState) => {
if (getState().isInitialized === true){
console.log("desktop init should not be called when already desktop is init")
return Promise.resolve();
}
if (getState().about.isLoading) {
console.log('is loading');
return Promise.resolve();
}
dispatch({ type: GET_ABOUT_CONTENT_REQUEST });
axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));
}
}
Also you need to return promise from axios call, just add return statement.
return axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));