I'm trying to use createAsyncThunk for some API calls but I can't seem to get it to work. My normal actions are working, so I must be connecting my component to redux correctly, but there's something different about createAsyncThunk I'm missing. Calling this.props.checkSession() from below does nothing. None of the console.logs inside checkSession are printed an fetch() never hits the server.
AppScreen
import React from 'react';
import { View, Text, ActivityIndicator } from 'react-native';
import { connect } from 'react-redux';
import { checkSession } from './actions';
import { setToken } from './reducer';
class AppScreen extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("Mounted")
this.props.checkSession();
console.log("Moving on")
if (!this.props.loading && !this.props.auth_token) {
this.props.navigation.navigate('Auth')
}
}
render() {
if (this.props.loading) {
return (
<View style={{ flex: 1 }}>
<ActivityIndicator />
</View>
)
} else {
return (
<View>
<Text>You're in! {this.props.auth_token}</Text>
</View>
)
}
}
}
function mapStateToProps(state) {
return {
user: state.app.user,
auth_token: state.app.auth_token,
loading: state.app.loading,
error: state.app.error
};
}
const mapDispatchToProps = dispatch => {
return {
checkSession: () => dispatch(checkSession),
setToken: token => dispatch(setToken(token))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AppScreen);
Actions
import { createAsyncThunk } from "#reduxjs/toolkit";
import { API_URL, ENDPOINTS } from "./../constants";
export const checkSession = createAsyncThunk("checkSession", (thunkAPI) => {
console.log("Running")
let body = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({auth_token: thunkAPI.getState().app.auth_token})
}
console.log("Checking session.")
return fetch(`${API_URL}${ENDPOINTS.CHECK_SESSION}`, body)
.then(response => {
console.log(`API hit: ${response.ok}`)
if (!response.ok) throw Error(response.statusText);
return response.json();
})
.then(json => json);
});
Reducer
import { createSlice } from "#reduxjs/toolkit";
import { checkSession } from "./actions"
const appSlice = createSlice({
name: "app",
initialState: {
loading: true,
auth_token: "",
error: "",
user: {}
},
reducers: {
setToken: (state, action) => {
state.auth_token = action.payload;
state.loading = false;
},
},
extraReducers: {
[checkSession.pending]: state => {
state.loading = true;
},
[checkSession.rejected]: (state, action) => {
state.loading = false;
state.error = action.error.message;
},
[checkSession.fulfilled]: (state, action) => {
state.loading = false;
state.user = action.payload.user;
state.auth_token = action.payload.auth_token;
}
}
});
export const { setToken } = appSlice.actions;
export const appReducer = appSlice.reducer;
Store
import { appReducer } from "./App/reducer";
import { authReducer } from "./Auth/reducer";
import { configureStore, getDefaultMiddleware } from "#reduxjs/toolkit";
const middleware = [
...getDefaultMiddleware(),
]
const store = configureStore({
reducer: {
app: appReducer,
auth: authReducer
},
middleware,
});
export default store;
You're using checkSession wrong. It should be dispatch(checkSession()).
That said, you should also be using the "object shorthand" form of mapDispatch, like this:
const mapDispatch = {checkSession, setToken};
Related
I am new in redux and redux-toolkit. I am trying to get data from api.
I get error and can not receive data from api. I am using redux-toolkit library.
This is my App.js:
function App() {
const companies = useSelector(state => state.companyList);
console.log(companies)
return (
<div className="App">
<header className="App-header">
{companies.map(company => {
return(
<h1>{company.name}</h1>
)
})}
<h1>hello</h1>
</header>
</div>
);
}
export default App;
This is createSlice.js
const getCompanies = axios.get(
"https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8"
).then((response) => {
console.log(response.data)
return response.data;
}).catch((ex) => {
console.log(ex)
})
export const companySlice = createSlice({
name: "companyList",
initialState: {value: getCompanies},
reducers: {
addCompnay: (state, action) => {},
},
});
export default companySlice.reducer;
Here is store.js
import { configureStore } from "#reduxjs/toolkit";
import companyReducer from "./features/companySlice/compnayList";
export const store = configureStore({
reducer:{
companyList: companyReducer,
}
})
In the browser, I receive this error:
enter image description here
You are making a lot of mistakes here so be sure to check out some tutorials and docs: https://redux-toolkit.js.org/tutorials/quick-start
You need to use createAsyncThunk and handle the response in extraReducers: https://redux-toolkit.js.org/rtk-query/usage/migrating-to-rtk-query#implementation-using-createslice--createasyncthunk
In companySlice:
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
export const getCompanies = createAsyncThunk(
"companyList/getCompanies",
async () => {
try {
const response = await axios.get(
"https://mocki.io/v1/d4867d8b-b5d5-4a48-a4ab-79131b5809b8"
);
return response.data;
} catch (error) {
console.error(error);
}
});
const companySlice = createSlice({
name: "companyList",
initialState: {
company: {},
isLoading: false,
hasError: false
},
extraReducers: (builder) => {
builder
.addCase(getCompanies.pending, (state, action) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(getCompanies.fulfilled, (state, action) => {
state.company = action.payload;
state.isLoading = false;
state.hasError = false
})
.addCase(getCompanies.rejected, (state, action) => {
state.hasError = true
state.isLoading = false;
})
}
});
// Selectors
export const selectCompanies = state => state.companyList.company;
export const selectLoadingState = state => state.companyList.isLoading;
export const selectErrorState = state => state.companyList.hasError;
export default companySlice.reducer;
Then you import selectCompanies wherever you want to use it and access it with useSelector.
In App.js:
import { useSelector } from "react-redux";
import { selectCompanies } from "WHEREEVER selectCompanies IS EXPORTED FROM";
function App() {
// Company list state
const companies = useSelector(selectCompanies);
// Rest of the code
.....
.....
.....
.....
}
export default App;
React native app
Store(states and backend) is built with axios and redux through redux-axios middleware which requires suffixes _SUCCESS and _FAIL for the Request.
Trying to make API call with redux axios middleware. However, data is not passing to the component. Reducer is executing only Default case for some reason.
action:
import { Actions } from "../../../constants/actions";
export const getNewsBloomberg = () => {
return {
type: Actions.GET_NEWS_BLOOMBERG,
payload: {
client: "newsClient",
request: {
url: "top-headlines?sources=bloomberg",
},
},
};
};
Reducer:
import { Actions } from "../../../constants/actions";
const initialState = {
data: [],
latestUpdate: null,
loading: null,
error: false,
};
export const bloomberg = (state = initialState, action) => {
switch (action.type) {
case Actions.GET_NEWS_BLOOMBERG:
return { ...state, latestUpdate: null, loading: true, error: false };
case Actions.GET_NEWS_BLOOMBERG_SUCCESS:
const data_string = JSON.stringify(action.payload.data);
const data_parsed = JSON.parse(data_string);
const data = data_parsed.articles;
return {
...state,
latestUpdate: new Date(),
loading: false,
data: list,
};
case Actions.GET_NEWS_BLOOMBERG_FAIL:
return {
...state,
latestUpdate: null,
loading: false,
error: "No results found.",
};
default:
return { ...state };
}
};
index.js in Store:
import axios from "axios";
import { multiClientMiddleware } from "redux-axios-middleware";
import storage from "redux-persist/lib/storage";
import { createStore, applyMiddleware } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import reducers from "./reducers";
//import { API_KEY } from "react-native-dotenv";
const persistConfig = {
key: "root",
storage,
whitelist: ["favorites"],
};
const clients = {
stockClient: {
client: axios.create({
baseURL: "https://sandbox.iexapis.com",
responseType: "json",
params: {
token: "Tpk_27c",
},
}),
},
newsClient: {
client: axios.create({
baseURL: "https://newsapi.org/v2",
responseType: "json",
params: {
apiKey: "c7c",
},
}),
},
};
const persistedReducer = persistReducer(persistConfig, reducers);
const store = createStore(
persistedReducer,
// applyMiddleware(client),
applyMiddleware(multiClientMiddleware(clients))
);
const persistor = persistStore(store);
export default { store, persistor };
Reducers are combined and Provider is wrapped to the application in App.js
The component:
import React, { Component } from "react";
import { FlatList, RefreshControl } from "react-native";
import { Content, Text, View } from "native-base";
import NewsItem from "./NewsItem";
import { connect } from "react-redux";
import { getNewsBloomberg } from "../../store/actions/news";
class NewsBloomberg extends Component {
onRefresh = () => this.props.getNewsBloomberg; //merge it
refreshControl = (loading) => (
<RefreshControl
onRefresh={this.onRefresh}
refreshing={loading}
/>
);
render() {
const { data, latestUpdate, loading } = this.props.bloomberg;
return (
<View refreshControl={this.refreshControl(loading)} style={{ flex: 1 }}>
{console.log(loading)}
<FlatList
data={data}
keyExtractor={(key) => key.source.id}
renderItem={({ item, index }) => (
<NewsItem onPress={() => console.log("Pressed")} data={data} />
)}
/>
</View>
);
}
}
const mapStateToProps = (state) => {
console.log(state.bloomberg);
return { bloomberg: state.bloomberg };
};
const mapDispatchToProps = {
getNewsBloomberg,
};
export default connect(mapStateToProps, mapDispatchToProps)(NewsBloomberg);
**I noticed that reducer throws the DEFAULT case only **
Does it mean that action is not dispatching or what?
You're not calling the action creator getNewsBloomberg inside onRefresh.
onRefresh = () => this.props.getNewsBloomberg();
Your mapDispatchToProps is wrong, what mapDispatchToProps does is it gives you dispatch as a first argument by using the higher order component "connect" and by using that you can dispatch your actions in react components,
now what you are doing is you are simply calling actions and not dispatching it,
const mapDispatchToProps = (dispatch) => {
getNewsBloomberg:()=>{dispatch(getNewsBloomberg())},
};
here i am taking dispatch as first argument and invoking the action inside dispatch
I have a component that should navigate when a user is authenticated:
componentDidUpdate(prevProps, prevState) {
if (this.props.authenticated) {
this.props.navigation.navigate('Main')
}
}
When I dispatch authLogin it should cause a rerender, which handles the navigation:
export const authLogin = (username, password) => {
return dispatch => {
dispatch(authStart());
axios.post(`http://10.0.2.2:8000/api/v1/rest-auth/login/`, {
username: username,
password: password
})
.then(response => {
var token = response.data.key;
try {
AsyncStorage.setItem('token', token);
} catch (err) {
console.log(err)
}
dispatch(authSuccess(token));
})
.catch(err => {
dispatch(authFail());
console.log(err);
})
}
}
Here is my reducer:
export default function (state = initialState, action) {
switch (action.type) {
case "AUTH_START": {
return {
...state,
authenticating: true,
}
}
case "AUTH_SUCCESS": {
return {
...state,
authenticating: false,
authenticated: true,
token: action.token,
}
}
case "AUTH_FAIL": {
return {
...state,
authenticating: false,
authenticated: false,
}
}
case "AUTH_LOGOUT": {
return {
...state,
authenticating: false,
authenticated: false,
token: null,
}
}
default:
return state
}
}
and action creators:
export const authStart = () => ({type: "AUTH_START"})
export const authSuccess = token => ({type: "AUTH_SUCCESS", token})
export const authFail = () => ({type: "AUTH_FAIL"})
My console is logging that Redux actions are dispatched and that the state is changing, but no rerendering is happening. Here's the whole component:
import React, { Component } from 'react';
import { View, StyleSheet } from 'react-native';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import LoginForm from '../components/LoginForm';
import { authLogin } from '../actions/authActions';
export class LoginScreen extends Component {
handlePress = async (username, password) => {
await this.props.authLogin(username, password);
}
componentDidUpdate(prevProps, prevState) {
if (this.props.authenticated) {
this.props.navigation.navigate('Main')
}
}
render() {
return (
<View style={styles.loginForm}>
<LoginForm handlePress={this.handlePress} {...this.props} />
</View>
);
}
}
const mapState = state => {
return {
authenticated: state.auth.authenticated
}
};
const mapDispatch = dispatch => {
return bindActionCreators({
authLogin,
}, dispatch)
};
export default connect(mapState, mapDispatch)(LoginScreen);
LoginScreen.propTypes = {
authLogin: PropTypes.func.isRequired,
authenticated: PropTypes.bool.isRequired,
};
const styles = StyleSheet.create({
loginForm: {
justifyContent: 'center',
alignItems: 'center',
flex: 1
}
});
and here's my store:
import { combineReducers } from 'redux';
import { createStore, applyMiddleware } from 'redux';
import { logger } from 'redux-logger';
import thunk from 'redux-thunk';
import auth from './auth'
const reducer = combineReducers({auth})
const enhancer = applyMiddleware(thunk, logger)
const store = createStore(reducer, enhancer)
export default store
The store is connected in the Provider in App.js.
I added another reducer, which fixed it instantly. Apparently redux didn't like that combineReducers() only had one argument.
i.e. change
const reducer = combineReducers({auth})
to
const reducer = combineReducers({auth, otherReducer})
Why not put the authentication check and navigation statement inside the handlePress().
handlePress = async (username, password) => {
await this.props.authLogin(username, password);
if (this.props.authenticated) {
this.props.navigation.navigate('Main')
}
}
After the authLogin() dispatches the action and state is updated, you can check the authentication status and navigate the user.
Hope this helps!
I'm having a problem here, I have been doing redux for about 2 weeks now.
I'm trying to create a loader so I am trying to get the isFetching state. Using thunk, i'm doing an ajax fetch, and dispatching the loading state.
The dispatch was called, because I can see in my console.
before component will mount, its suppose to call FETCH_PROFILE, and isFetching set to true, but when i console.log(this.props.profile.isFetching), it's returning false.
Same for FETCH_PROFILE_SUCCESS, it doesn't update this.props.profile. (Or because It's not rerendering...so I can't see it)
I've been working on this simple thing for hours and I have no idea why it doesn't update...... I am sure I made some mistake somewhere but no idea what.
export const FETCH_PROFILE = 'FETCH_PROFILE';
export const FETCH_PROFILE_SUCCESS = 'FETCH_PROFILE_SUCCESS';
export const FETCH_PROFILE_FAIL = 'FETCH_PROFILE_FAIL';
export function getUserProfile() {
return (dispatch) => {
dispatch(getUserProfileStart());
const config2 = {
method: 'GET',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
credentials: 'include',
body: ``,
};
fetch('http://api.example.com/profile/',config2)
.then((resp) => resp.json())
.then(function(data) {
dispatch(getUserProfileSuccess(data));
return 0;
}).catch(function(error){
return dispatch({type: FETCH_PROFILE_FAIL});
})
}
}
function getUserProfileSuccess(data) {
return {
type: FETCH_PROFILE_SUCCESS,
isFetching: false,
payload: data
}
}
function getUserProfileStart() {
return {
type: FETCH_PROFILE,
isFetching: true
}
}
my reducer
import {
FETCH_PROFILE,
FETCH_PROFILE_SUCCESS,
FETCH_PROFILE_FAIL
} from '../actions/profile';
export default function userProfile(state={isFetching: false}, action) {
switch(action.type) {
case FETCH_PROFILE:
return {...state, isFetching: true}
case FETCH_PROFILE_SUCCESS:
return {...state, isFetching: false, data: action.payload}
case FETCH_PROFILE_FAIL:
return { ...state, isFetching: false };
default:
return state
}
}
My Component.
import React from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import {connect } from 'react-redux';
import * as profileActions from '../../actions/profile';
import {bindActionCreators} from 'redux';
class ProfilePage extends React.Component {
constructor(props) {
super(props);
this.getUserProfile = this.getUserProfile.bind(this);
}
componentWillMount() {
this.props.actions.getUserProfile();
}
render() {
console.log('Checking isFetching State', this.props.profile.isFetching);
return (
<div>
some text here.
</div>
);
}
}
function mapStateToProps(state) {
console.log('Mapping Stat', state.profile);
return {
profile: state.userProfile
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(profileActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(s)(ProfilePage));
my combine reducers...
import userProfile from './profile';
//some other imports...
export default combineReducers({
userProfile,
//other reducers that works...
});
Try this:
At index while creating store
export const store = createStore(
combineReducer,
applyMiddleware(thunk) // choose you middleware....
//initial state as per need
);
At reducer:
import {
FETCH_PROFILE,
FETCH_PROFILE_SUCCESS,
FETCH_PROFILE_FAIL
} from '../actions/profile';
export default function userProfile(state= {
initialized: true,
init:false,
success:false,
fail:false,
}, action) {
switch(action.type) {
case FETCH_PROFILE:{
const message = action.message;
return Object.assign({}, state, {
init:true,
success:false,
fail:false,
data:message,
})
}
}
case FETCH_PROFILE_SUCCESS:{
const data = action.data;
return Object.assign({}, state, {
init:false,
success:true,
fail:false,
data:data,
})
}
case FETCH_PROFILE_FAIL:{
const err = action.err;
return Object.assign({}, state, {
init:false,
success:false,
fail:true,
data:err,
})
}
default:
return state
}
}
At Component:
import React from 'react';
//use can use this if required.
//import withStyles from 'isomorphic-style-loader/lib/withStyles';
import {connect } from 'react-redux';
import { profileActions} from '../../actions/profile';
//import {bindActionCreators} from 'redux';
class ProfilePage extends React.Component {
constructor(props) {
super(props);
this.state{
success:false;
}
}
componentWillMount() {
this.props.getUserProfile();
}
componentWillReciveProps(nextprop){
if(nextprop.success===true){
this.setState({success==true});
}
}
render() {
return (
{(this.state.success)?<div>this.props.profile.data.yourdata</div>:<div>Loading....</div>}
);
}
}
function mapStateToProps(state) {
return {
profile: state.userProfile
};
}
export default connect(mapStateToProps,{profileActions
})(ProfilePage);
At action:
export const FETCH_PROFILE = 'FETCH_PROFILE';
export const FETCH_PROFILE_SUCCESS = 'FETCH_PROFILE_SUCCESS';
export const FETCH_PROFILE_FAIL = 'FETCH_PROFILE_FAIL';
export function getUserProfile() {
return (dispatch) => {
dispatch(getUserProfileStart(const message:"fetch start"));
const config2 = {
method: 'GET',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
credentials: 'include',
body: ``,
};
fetch('http://api.example.com/profile/',config2)
.then((resp) => resp.json())
.then(function(data) {
dispatch(getUserProfileSuccess(data));
return 0;
}).catch(function(error){
return dispatch(getUserProfileError(error));
})
}
}
function getUserProfileSuccess(data) {
return {
type: FETCH_PROFILE_SUCCESS,
data
}
}
function getUserProfileStart(message) {
return {
type: FETCH_PROFILE,
message
}
}
function getUserProfileError(err) {
return {
type: FETCH_PROFILE,
err
}
}
I'm making my first react-native app and I cant seem to bind my actions to props. In the component this.props.actions is an empty, and LoginActions is also an empty object in the mapDispatchToProps function. This leads me to believe its an issue in the action or the connect binding. Can anyone see where I'm going wrong?
Component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
View,
StyleSheet,
} from 'react-native';
import {
google,
facebook,
twitter,
} from 'react-native-simple-auth';
import LoginConstants from '../../constants/Login.constants';
import * as LoginActions from '../../actions/Login.actions';
import LoginForm from '../../components/LoginForm';
class Login extends Component {
constructor(props) {
super(props);
alert(JSON.stringify(this.props.actions))
this.loginActions = {
google,
facebook,
twitter,
};
this.loginAction = this.loginAction.bind(this);
}
loginAction(platform) {
alert(JSON.stringify(this.props.actions))
// this.loginActions[platform](LoginConstants[platform])
// .then((info) => {
// alert(info);
// // info.user - user details from the provider
// // info.credentials - tokens from the provider
// }).catch((error) => {
// throw Error(`Error ${error.code}: ${error.description}`);
// });
}
render() {
return (
<LoginForm actions={this.loginActions} loginAction={this.loginAction} />
);
}
}
Login.propTypes = {
actions: PropTypes.object.isRequired,
user: PropTypes.object
};
const styles = StyleSheet.create({
});
const mapStateToProps = (state) => {
return {
user: state.user
};
};
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(LoginActions, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Actions:
import LoginConstants from '../constants/Login.constants';
export function getUser(userId) {
return {
type: LoginConstants.actions.getUser,
payload: new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
userId: '123ddgs',
});
}, 2000);
});
};
};
export function saveUser(user) {
return {
type: LoginConstants.actions.saveUser,
payload: new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
userId: '123ddgs',
});
}, 2000);
})
};
};
Reducer:
import LoginConstants from '../constants/Login.constants';
const loginReducers = (state = {
user: {},
prevStates: []
}, action) => {
switch (action.type) {
case LoginConstants.actions.getUser:
state = {
...state,
user: action.payload,
prevStates: [...state.prevStates, action.payload]
};
break;
case LoginConstants.actions.saveUser:
state = {
...state,
user: action.payload,
prevStates: [...state.prevStates, action.payload]
};
break;
}
return state;
};
export default loginReducers;
Store:
import {
createStore,
combineReducers,
applyMiddleware,
} from 'redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise-middleware';
import { createLogger } from 'redux-logger';
import loginReducers from './src/reducers/Login.reducers';
import beerReducers from './src/reducers/Beer.reducers';
export default createStore(
combineReducers({
loginReducers,
beerReducers,
}),
{},
applyMiddleware(createLogger(), thunk, promise())
);
JSON.stringify strips functions from its output and therefore, the actions and dispatcher were not visible in the alert output.