Types.js File:
const ADD_TOKENS = "ADD_TOKENS";
export { ADD_TOKENS};
Actions.js File:
import { ADD_TOKENS } from "./types";
function addTokens() {
return {
type: ADD_TOKENS,
};
}
const actionCreators = {
addTokens,
};
export { actionCreators };
Reducers.js File
import { ADD_TOKENS } from "./types";
import getTokens from "../../api/getTokens";
const initialState = {
access_token: null,
refresh_token: null,
expiration_time: null,
};
function applyAddTokens(state) {
console.log("Function being hit"); //yes
return async (dispatch) => {
console.log("Function being hit"); //no
const token = await getTokens();
return {
...state,
access_token: dispath(token.access_token),
refresh_token: dispath(token.refresh_token),
expiration_time: dispath(token.expiration_time),
};
};
}
function reducer(state = initialState, action) {
switch (action.type) {
case ADD_TOKENS:
return applyAddTokens(state);
default:
return state;
}
}
export default reducer;
index.js File
import React, { Component } from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { actionCreators as actions } from "./actions";
class Login extends Component {
render() {
const { addTokens } = this.props;
console.log("Props in Login/index.js", this.props);
return (
<View>
<TouchableOpacity onPress={addTokens}>
<Text>Login</Text>
</TouchableOpacity>
</View>
);
}
}
function mapStateToProps(state) {
const { access_token, refresh_token, expiration_time } = state;
return {
access_token,
refresh_token,
expiration_time,
};
}
function mapDispatchToProps(dispatch) {
return {
addTokens: bindActionCreators(actions.addTokens, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
store.js File
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducer from "../components/Login/reducers";
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
Why is this not working? Why is applyAddTokens function's thunk not producing anything? Let me know if anyone needs any additional details. getTokens() returns an object that contains access_token, refresh_token, and expiration_time. This worked before when I tried it without async (just regular data without any request).
Your code flow is like below:
In your JSX, upon onPress, you trigger the action addTokens.
This action returns an object of type ADD_TOKENS
Redux will directly execute the reducer (the ADD_TOKENS switch case). Here you are calling an async function applyAddTokens.
Redux will execute it IMMEDIATELY (without waiting for promise result) and returns the output of applyAddTokens function which is a pending promise
That's it.
Solution
First, put all async code in your actions. Keep reducers light and use it only to update state.
To use thunk, your action must return a function (not an object).
See this example as well
Related
I am new to Next.js, So I follow some tutorials for Redux integration in Next.js. All is working fine but whenever I switch between pages, each time API make a call, and Redux lost its stored value.
The basic function is like this. Whenever a user loads a website an API call will fetch category data from the server and save that data in reducer[categoryReducer], then the user can navigate to any page and category data will fetched from the reducer. But in my case, it hits again and again
Full Code:
// Action Call
import * as Constants from '../../constant/constant';
import * as t from '../types';
import axios from 'axios';
export const loadCategoryApi = (type) => dispatch => {
axios.post(Constants.getCategories,type)
.then(function (response) {
console.log(response);
if(response && response.data && response.data.status==="200"){
dispatch({
type: t.LOAD_CATEGORY,
value: type
});
}
else if(response && response.data && response.data.status==="404"){
alert('Someting went wrong');
}
})
}
// Reducer File
import * as t from '../types';
const initialState = {
doc:null
}
const CategoryReducer = (state = initialState, action) =>{
console.log('reducer action', action.type);
switch (action.type){
case t.LOAD_CATEGORY:
console.log('slots actions',action);
return({...state, doc:action.value})
default:
return state;
}
}
export default CategoryReducer;
// Store file
import { createStore, applyMiddleware, compose } from "redux"
import thunk from "redux-thunk"
import { createWrapper } from "next-redux-wrapper"
import rootReducer from "./reducers/rootReducer"
const middleware = [thunk]
const makeStore = () => createStore(rootReducer, compose(applyMiddleware(...middleware)))
export const wrapper = createWrapper(makeStore);
// rootReducer
import { combineReducers } from "redux"
import CategoryReducer from "./CategoryReducer";
const rootReducer = combineReducers({
CategoryReducer: CategoryReducer
})
export default rootReducer;
// _app.js
import React from "react"
import { wrapper } from "../redux/store"
import Layout from '../components/Layout';
import '../styles/globals.css'
const MyApp = ({ Component, pageProps }) =>(
<Layout>
<Component {...pageProps} />
</Layout>
);
export default wrapper.withRedux(MyApp);
// Uses
import React, { useState, useEffect } from 'react';
import {connect} from "react-redux";
import {loadCategoryApi} from "../redux/actions/CategoryAction";
function navbar(props){
const { loadCategory, loadCategoryApi } = props;
useEffect(() => {
if(loadCategory===null){
console.log('navbar loading funciton');
loadCategoryFunation();
}
}, []);
const loadCategoryFunation = () =>{
var json = {
type : 'main'
};
loadCategoryApi(json);
}
}
const mapStateToProps = state => {
return { loadCategory: state.CategoryReducer.doc }
}
const mapDispatchToProps = {
loadCategoryApi
}
export default connect(mapStateToProps, mapDispatchToProps)(Navbar)
What I am doing wrong?
You have to create main reducer to handle the hydration. I explained this hydration process here.
In the file that you created the store, write main reducer
import reducers from "./reducers/reducers";
const reducer = (state, action) => {
// hydration is a process of filling an object with some data
// this is called when server side request happens
if (action.type === HYDRATE) {
const nextState = {
...state,
...action.payload,
};
return nextState;
} else {
// whenever we deal with static rendering or client side rendering, this will be the case
// reducers is the combinedReducers
return reducers(state, action);
}
};
then pass this reducer to the store
I have just gone through some redux tutorial and started to implement
i have one API call that has to be happen from redux as soon as page loads.. its possible with ComponentDidMount ,but i need to know how redux helps in achieving this.
For easiness i had shared the code in
https://codesandbox.io/s/quirky-sunset-s95gu?fontsize=14
My index.js look like
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { createStore,applyMiddleware } from "redux";
import allReducer from "./reducers";
import { Provider } from "react-redux";
import thunk from 'redux-thunk';
import "./styles.css";
let store = createStore(
allReducer,
applyMiddleware(thunk)
);
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
My Action creater look like(action/index.js)
import UserApi from "../UserApi";
export function loadUserSuccess(user) {
return { type: "LOAD_USER_SUCCESS", user };
}
export function loadUsers() {
return function(dispatch) {
return UserApi.getAllUsers()
.then(user => {
console.log("midhun");
dispatch(loadUserSuccess(user));
})
.catch(error => {
throw error;
});
};
}
and its subsequent api caliing function look like
class UserApi {
static getAllUsers() {
return fetch("https://jsonplaceholder.typicode.com/users")
.then(response => {
console.log("response", response);
return response.json();
})
.catch(error => {
return error;
});
}
}
export default UserApi;
My Reducer look like
import initialState from "./InitialState";
export default function IsLoggedReducer(state = initialState.user, action) {
console.log(state, action);
switch (action.type) {
case "LOAD_USER_SUCCESS":
return state;
default:
return state;
}
}
and my App.js look like
import React from "react";
import { connect } from "react-redux";
import * as userActions from "./action/index";
import UserList from "./UserList";
class App extends React.Component {
render() {
return (
<div>
<h1>MYpage</h1>
<UserList user={this.props.user} />
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return {
user: state.user
};
}
export default connect(mapStateToProps)(App);
I had put couple of console in action creator and its subsequent function,but its not triggering.
Any help will be much much apreciated and will be highly help for beginners
You guys can check the complete set of code
https://codesandbox.io/s/quirky-sunset-s95gu?fontsize=14
In addition to the info lavor gaved, since you use combineReducers, you need to access to state by using your reducer key.
App.js
import React from "react";
import { connect } from "react-redux";
import {loadUsers} from "./action/index";
import UserList from "./UserList";
class App extends React.Component {
componentDidMount() {
this.props.loadUsers();
}
render() {
return (
<div>
<h1>MYpage</h1>
<UserList users={this.props.users} />
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return {
users: state.IsLoggedReducer
};
}
export default connect(mapStateToProps, {loadUsers})(App);
I also made some corrections in the reducer file, we need to return the new state with the given payload.
import initialState from "./InitialState";
export default function IsLoggedReducer(state = initialState.user, action) {
console.log("ap", action.payload);
switch (action.type) {
case "LOAD_USER_SUCCESS":
return [...action.payload]
default:
return state;
}
}
And action file:
import UserApi from "../UserApi";
export function loadUserSuccess(users) {
return { type: "LOAD_USER_SUCCESS", payload: users };
}
export function loadUsers() {
return function(dispatch) {
return UserApi.getAllUsers()
.then(users => {
dispatch(loadUserSuccess(users));
})
.catch(error => {
throw error;
});
};
}
You can check this codesandbox for the working app.
https://codesandbox.io/s/cranky-colden-v6r6w
You are not dispatching your action, try to do it in componentDidMount (you need to map dispatch to props first):
App.js
componentDidMount() {
this.props.loadUsers();
}
// ...
function mapStateToProps(state, ownProps) {
return {
user: state.user
};
}
function mapDispatchToProps(dispatch) {
return {
loadUsers: () => dispatch(userActions.loadUsers())
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
I am trying to save a value to a redux store upon a user entering text for a search.
Edited to include my full import list
import React, { Component } from 'react';
import axios from 'axios';
import API from './API';
import { connect } from "react-redux";
import searchSave from '../actions/search'
import {bindActionCreators} from 'redux'
const format = require('string-format')
class Search extends Component {
...
searchForText = (text) => {
console.log("user stopped typing");
console.log(text);
this.props.searchSave(text);
}
...
}
const mapStateToProps = (state) => {
return {
oidc: state.oidc,
search: state.searchtext,
};
};
function mapDispatchToProps(dispatch) {
let actions = bindActionCreators({ searchSave });
return { ...actions, dispatch };
}
export default connect(mapStateToProps, mapDispatchToProps)(Search)
My reducer looks like this and is in my combineReducers function elsewhere:
import Actions from '../actions/search.js'
export default (state = "", action) => {
switch (action.type) {
case Actions.SEARCH:
return {searchtext: action.payload}
}
return state;
};
I am getting the error, "TypeError: Dispatch is not a function" - but theoretically dispatch should be bound by bindActionCreators?
I have searched for this exact error, but none of it seems related to my specific conditions.
I am assuming I am doing something wrong in my mapStateToProps or my reducer?
I am pretty new to redux and still find it somewhat confusing.
You need to pass the dispatch function as the second parameter to bindActionCreators:
function mapDispatchToProps(dispatch) {
return bindActionCreators({ searchSave }, dispatch);
}
You can see the docs for this here: https://react-redux.js.org/using-react-redux/connect-mapdispatch
If your intention was to also include dispatch in the returned props (which seems like an antipattern to me, but I'm not an expert), your original code works with the addition of the second parameter to bindActionCreators:
function mapDispatchToProps(dispatch) {
const actions = bindActionCreators({ searchSave }, dispatch);
return { ...actions, dispatch };
}
Looks like you're missing the import for connect, and some stuff in mapDispatchToProps. I also don't believe you need bindActionCreators in this scenario:
import searchSave from '../actions/search'
//Missing import connect
import { connect } from 'react-redux'
class Search extends Component {
...
searchForText = (text) => {
console.log("user stopped typing");
console.log(text);
this.props.searchSave(text);
}
...
}
const mapStateToProps = (state) => {
return {
oidc: state.oidc,
search: state.searchtext,
};
};
function mapDispatchToProps(dispatch) {
searchSave: (inputs) => dispatch(searchSave(inputs)
}
export default connect(mapStateToProps, mapDispatchToProps)(Search)
Here is my code:
The action creator
export function fetchHead() {
const url = HEAD_URL;
const request = axios.get(url);
return {
type: FETCH_HEAD,
payload: request
};
}
The Reducer
import { FETCH_HEAD } from '../actions';
import _ from 'lodash';
export default function(state = {}, action) {
switch (action.type) {
case FETCH_HEAD:
return _.mapKeys(action.payload.data.articles, 'id');
default:
return state;
}
}
Reducer keys, promise
import { combineReducers } from 'redux';
import HeadReducer from './head_reducer';
const rootReducer = combineReducers({
heads: HeadReducer
});
export default rootReducer;
Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchHead } from '../actions';
class HeadNews extends Component {
componentDidMount() {
this.props.fetchHead();
console.log(this.props.heads);
}
render() {
return <div>Hello</div>;
}
}
function mapStateToProps(state) {
return { heads: state.heads };
}
export default connect(mapStateToProps, { fetchHead })(HeadNews);
You are passing a deffered object to the reducer and not the data returned from the ajax request.
You should use .then:
axios.get(url)
.then(function (response) {
return {
type: FETCH_HEAD,
payload: response
}
})
.catch(function (error) {
console.log(error);
});
EDIT
I don't know if you are using redux-thunk middleware but in order to dispatch actions that returns a function instead of a plain object like an action should be, you need to use redux-thunk.
Just but console.log under rendering funcnction :
componentDidMount() {
this.props.fetchHead();
}
render() {
console.log(this.props.heads);
return <div>Hello</div>;
}
I try to deal with ajax data in my learning react,redux project and I have no idea how to dispatch an action and set the state inside a component
here is my component
import React, {PropTypes, Component} from 'react';
import Upload from './Upload';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import * as profileActions from '../../../actions/profileActions';
class Profile extends Component {
static propTypes = {
//getProfile: PropTypes.func.isRequired,
//profile: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this.state = {
profile:{
username: '',
password: ''
}
}
this.onUpdate = this.onUpdate.bind(this)
}
onUpdate(event) {
alert()
}
componentWillMount() {
}
componentDidMount() {
}
render() {
const {profile} = this.props;
return (
<div>
</div>
);
}
}
function mapStateToProps(state) {
console.log(state)
return {
profile: state.default.profile,
};
}
function mapDispatchToProps(dispatch, ownProps) {
return {
actions: bindActionCreators(profileActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
I create the store as it follows
import { createStore, combineReducers, applyMiddleware } from 'redux'
import createLogger from 'redux-logger'
import thunk from 'redux-thunk'
import { routerReducer, routerMiddleware, push } from 'react-router-redux'
import reducers from '../reducers'
import { browserHistory } from 'react-router';
const middleware = [ thunk ];
if (process.env.NODE_ENV !== 'production') {
middleware.push(createLogger());
}
middleware.push(routerMiddleware(browserHistory));
// Add the reducer to your store on the `routing` key
const store = createStore(
combineReducers({
reducers,
routing: routerReducer
}),
applyMiddleware(...middleware),
)
export default store;
reducer
export const RESOLVED_GET_PROFILE = 'RESOLVED_GET_PROFILE'
const profileReducer = (state = {}, action) => {
switch (action.type) {
case 'RESOLVED_GET_PROFILE':
return action.data;
default:
return state;
}
};
export default profileReducer;
actions
import * as types from './actionTypes';
import Api from '../middleware/Api';
export function getProfile() {
return dispatch => {
dispatch(setLoadingProfileState()); // Show a loading spinner
Api.getAll('profile').then(profile => {
dispatch(doneFetchingProfile(profile));
}).catch(error => {
throw(error);
});
/*Api.fetch(`profile`, (response) => {
console.log(response)
dispatch(doneFetchingBook()); // Hide loading spinner
if(response.status == 200){
dispatch(setProfile(response.json)); // Use a normal function to set the received state
}else {
dispatch(error)
}
}) */
}
}
function setProfile(data) {
return {type: types.SET_PROFILE, data: data}
//return { type: types.SET_PROFILE, data: data };
}
function setLoadingProfileState() {
return {type: types.SHOW_SPINNER}
}
function doneFetchingProfile(data) {
console.log(data)
return {
type: types.HIDE_SPINNER,
profile: data
}
}
function error() {
return {type: types.SHOW_ERROR}
}
but I have no idea how would I dispatch action and update the state after getProfile action
You need to only dispatch your event RESOLVED_GET_PROFILE right after dispatching doneFetchingProfile, or simply listen RESOLVED_GET_PROFILE and hide spinner on reducing it.
Api.getAll('profile').then(profile => {
dispatch(doneFetchingProfile(profile));
dispatch(resoloveGetProfile(profile));
})
Actually you r doing everything right - so I didn't understand what is your question is, so if you meant something else - let me know, I`ll try to describe you.
About dispatch(resoloveGetProfile(profile));
There you dispatch action, which will update your state, simple as you do with some static state, I saw that you already have setProfile action, so you can change that line, to call your existed function.
dispatch(setProfile(profile))
Than you need to reduce your state in this action
case 'SET_PROFILE' : (action, state) => {...state, profile: action.data}
Than your state will change and your components will update. Note that your 'get profile method' better to call from componentDidMount to avoid freezing at rendering because of performing web request.