I am fetching an api with axios and the action is being fired after the "persist/REHYDRATE" action resulting in the following
"redux-persist/autoRehydrate: 1 actions were fired before rehydration completed...."
If I delete a tweet one by one and then refresh my browser, it does not store the state. Can't seem to crack this..
Client.js
import React from 'react';
import { render } from 'react-dom';
import { Provider } from "react-redux"
import { compose, applyMiddleware, createStore } from 'redux';
import logger from "redux-logger"
import thunk from "redux-thunk"
import promise from "redux-promise-middleware"
import {persistStore, autoRehydrate} from 'redux-persist'
import tweetApp from "./reducers"
import Layout from "./components/Layout"
import { REHYDRATE } from 'redux-persist/constants'
import createActionBuffer from 'redux-action-buffer'
//const middleware = applyMiddleware(promise(), thunk, logger())
let enhancer = compose(
autoRehydrate({ log: true }),
applyMiddleware(
promise(), thunk, logger(), createActionBuffer(REHYDRATE)
)
)
const store = createStore(
tweetApp,
enhancer
);
const persistConfig = {
whitelist : ["tweets"]
};
persistStore(store, persistConfig);
render(
<Provider store={store}>
<Layout />
</Provider>,
document.getElementById('app')
);
tweetsReducer.js
import {REHYDRATE} from 'redux-persist/constants'
export default function reducer(state={
tweets: [],
fetching: false,
fetched: false,
error: null,
}, action) {
switch (action.type) {
case "persist/REHYDRATE": {
const incoming = action.payload.tweets; // Carts is the name of the reducer
if (incoming) return {...state, ...incoming}
}
case "FETCH_TWEETS": {
return {...state, fetching: true}
}
case "FETCH_TWEETS_REJECTED": {
return {...state, fetching: false, error: action.payload}
}
case "FETCH_TWEETS_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
tweets: action.payload,
}
}
case "ADD_TWEET": {
return {
...state,
tweets: [...state.tweets, action.payload],
}
}
case "UPDATE_TWEET": {
const { id, text } = action.payload
const newTweets = [...state.tweets]
const tweetToUpdate = newTweets.findIndex(tweet => tweet.id === id)
newTweets[tweetToUpdate] = action.payload;
return {
...state,
tweets: newTweets,
}
}
case "DELETE_TWEET": {
return {
tweets: [
...state.tweets.slice(0, action.payload),
...state.tweets.slice(action.payload + 1)
],
}
}
}
return state
}
tweetsActions.js
import axios from "axios";
export function fetchTweets() {
return function(dispatch) {
axios.get("http://rest.learncode.academy/api/test123/tweets")
.then((response) => {
dispatch({type: "FETCH_TWEETS_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_TWEETS_REJECTED", payload: err})
})
}
}
export function addTweet(id, text) {
return {
type: 'ADD_TWEET',
payload: {
id,
text,
},
}
}
export function updateTweet(id, text) {
return {
type: 'UPDATE_TWEET',
payload: {
id,
text,
},
}
}
export function deleteTweet(id) {
return { type: 'DELETE_TWEET', payload: id}
}
layouts.js
import React from "react"
import { connect } from "react-redux"
import { fetchUser } from "../actions/userActions"
import { fetchTweets } from "../actions/tweetsActions"
import { deleteTweet } from "../actions/tweetsActions"
#connect((store) => {
return {
user: store.user.user,
userFetched: store.user.fetched,
tweets: store.tweets.tweets,
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
this.props.dispatch(fetchTweets())
}
fetchTweets() {
//this.props.dispatch(fetchTweets())
}
deleteTweet(idx, e) {
this.props.dispatch(deleteTweet(idx))
}
render() {
const { user, tweets, i } = this.props;
//console.log(this.props)
const mappedTweets = tweets.map((tweet, i) => <li key={i}>{tweet.text}<button onClick={this.deleteTweet.bind(this, i)}>delete</button></li>)
return <div>
<h1>{user.name}</h1>
<ul>{mappedTweets}</ul>
</div>
}
}
UPDATE AND SAME ISSUE:
I tried replacing "componentWILLMount() with componentDidMount()" and the issue still occurs. See logged output:
Move your fetchTweets and fetchUser calls to componentDidMount, otherwise all your code is executed synchronously: from the store being created to your Layout being instantiated and rendered.
componentWillMount is called before render, while componentDidMount is called after the component has been rendered for the first time.
Related
I am studying redux-saga and I want to fetch data from :
https://jsonplaceholder.typicode.com/posts
and in my redux folder I have the fallowing:
(it can be checked in this github repository
https://github.com/jotasenator/redux-saga-fetching-example/tree/main/src)
\src\redux\api.js
import axios from 'axios'
export const loadPostApi = async () => {
await axios.get(`https://jsonplaceholder.typicode.com/posts`)
}
the get request to the address in question
src\redux\app.actions.js
export const loadPostStart = () => ({
type: 'LOAD_POST_START',
})
export const loadPostSuccess = (posts) => ({
type: 'LOAD_POST_SUCCESS',
payload: posts,
})
export const loadPostFail = (error) => ({
type: 'LOAD_POST_FAIL',
payload: error,
})
those are the actions functions
src\redux\app.reducer.js
const INITIAL_STATE = {
loading: false,
posts: [],
errors: null,
}
export const appReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'LOAD_POST_START':
return {
...state,
loading: true,
}
case 'LOAD_POST_SUCCESS':
return {
...state,
posts: action.payload,
loading: false,
}
case 'LOAD_POST_FAIL':
return {
...state,
errors: action.payload,
loading: false,
}
default:
return state;
}
}
the reducer of the fetching, updating state,
src\redux\counterReducer.js
import { types } from "./types";
const initialState = {
value: 0
}
export const counterReducer = (state = initialState, action) => {
switch (action.type) {
case types.adicionar:
return {
...state,
value: state.value + 1
}
case types.resetear:
return {
...state,
value: 0
}
case types.restar:
return {
...state,
value: state.value - 1
}
default:
return state
}
}
this is the reducer of the counter app, with different approach, types are isolated in another file
src\redux\rootReducer.js
import { combineReducers } from 'redux'
import { counterReducer } from './counterReducer'
import { appReducer } from './app.reducer'
export const rootReducer = combineReducers({
counterReducer,
appReducer
})
the rootReducer for gathering the reducers
src\redux\sagas.js
import { put, takeLatest, call } from 'redux-saga/effects'
import { loadPostApi } from './api'
import { loadPostFail, loadPostSuccess } from './app.actions'
export function* onLoadPostStartAsync() {
try {
const response = yield call(loadPostApi)
yield put(loadPostSuccess(response.data))
} catch (error) {
yield put(loadPostFail(error))
}
}
export function* onLoadPost() {
yield takeLatest('LOAD_POST_START', onLoadPostStartAsync)
}
export default function* rootSaga() {
yield ([
onLoadPost(),
])
}
saga onLoadPostStartAsync called by saga onLoadPost inside rootSaga
src\redux\store.js
import { applyMiddleware, compose, createStore } from "redux";
import createSagaMiddleware from 'redux-saga'
import { rootReducer } from "./rootReducer";
import rootSaga from "./sagas";
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware))
export const store = createStore(rootReducer, enhancer)
sagaMiddleware.run(rootSaga)
this is the store with the redux_devtool_extension, the reducers, and running rootSaga
src\redux\types.js
export const types = {
adicionar: 'ADICIONAR',
resetear: 'RESETEAR',
restar: 'RESTAR'
}
those are the types of the counterApp reducer
src\Counter.js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
export const Counter = () => {
const dispatch = useDispatch()
const { value } = useSelector(state => state.counterReducer)
const handleAdicionar = () => {
dispatch({ type: 'ADICIONAR' })
}
const handleResetear = () => {
(value !== 0) && dispatch({ type: 'RESETEAR' })
}
const handleRestar = () => {
dispatch({ type: 'RESTAR' })
}
console.log(value)
return (
<div>
<button onClick={handleAdicionar}>Adicionar</button>
{' '}
<button onClick={handleResetear}>Resetear</button>
{' '}
<button onClick={handleRestar}>Restar</button>
<hr />
</div>
)
}
this is the Counter component, it works ok
src\Fetching.js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { loadPostStart } from './redux/app.actions'
export const Fetching = () => {
const dispatch = useDispatch()
const fetchPost = () => {
dispatch(loadPostStart())
}
const state = useSelector(state => state.appReducer)
console.log(state)
return (
<>
<h1>Fetching from https://jsonplaceholder.typicode.com</h1>
<button onClick={fetchPost}>Fetching</button>
{
!state.loading && state.posts.map((post) => (
<li key={post.id}><h2>{post.title}</h2></li>
))
}
</>
)
}
the Fetching component click on the button calls fetchPost function who dispatch loadPostStart() function which is the same of dispatching {type: 'LOAD_POST_START'}, but nothing happens here when clicking, not fetch nothing from here https://jsonplaceholder.typicode.com/posts
src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { store } from './redux/store';
import { Provider } from "react-redux";
import { Unificator } from './Unificator';
ReactDOM.render(
<Provider store={store}>
<Unificator />
</Provider>,
document.getElementById('root')
);
component Unificator has Counter and Fetching component
src\Unificator.js
import React from 'react'
import { Counter } from './Counter'
import { Fetching } from './Fetching'
export const Unificator = () => {
return (
<div>
<Counter />
<Fetching />
</div>
)
}
as you can see is about of two reducers, one is the famous counter, and the another one is the fetching issue, do not know what is happening that is not fetching the data
obviously, i am doing something wrong here...don t see where
Axio returns promise, You need to capture that and return. Please try replacing below code.
export const loadPostApi = async () => {
await axios.get(`https://jsonplaceholder.typicode.com/posts`)
.then((response) => {
console.log('Response', response);
return response;
})
.catch((error) => {
console.log('error', error);
})
}
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!
Currently I am trying to pass user data through my react app with Redux. I have created a user API with a django backend that is definately working, as I am able to go the url and see all the json that comes out of it. However, when I try to pass it into a component I keep getting undefined. Here is my code:
userActions.js:
import Axios from "axios";
export function getUser() {
const id = this.params.match.id
return dispatch => {
dispatch(fetchUserBegin());
return Axios.get(`/api/user/${id}`)
.then((res) => {
this.setState({
user: res.data,
})
})
}
}
export const FETCH_USER_BEGIN = 'FETCH_USER_BEGIN';
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS';
export const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE';
export const fetchUserBegin = () => ({
type: FETCH_USER_BEGIN
});
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: { user }
});
export const fetchUserFailure = error => ({
type: FETCH_USER_FAILURE,
payload: { error }
});
userReducer.js
import { FETCH_USER_BEGIN, FETCH_USER_SUCCESS, FETCH_USER_FAILURE } from '../actions/actionTypes'
const initialState = {
user: {},
loading: false,
error: null
};
export default function productReducer(state = initialState, action) {
switch(action.type) {
case FETCH_USER_BEGIN:
// Mark the state as "loading" so we can show a spinner or something
// Also, reset any errors. We're starting fresh.
return {
...state,
loading: true,
error: null
};
case FETCH_USER_SUCCESS:
// All done: set loading "false".
// Also, replace the items with the ones from the server
return {
...state,
loading: false,
user: action.user
};
case FETCH_USER_FAILURE:
// The request failed, but it did stop, so set loading to "false".
// Save the error, and we can display it somewhere
// Since it failed, we don't have items to display anymore, so set it empty.
// This is up to you and your app though: maybe you want to keep the items
// around! Do whatever seems right.
return {
...state,
loading: false,
error: action.payload.error,
user: {}
};
default:
// ALWAYS have a default case in a reducer
return state;
}
}
And the display component:
UserInformation.js:
import React from "react";
import { connect } from "react-redux";
import { getUser } from "../store/actions/userActions";
class UserDetailView extends React.Component {
componentDidMount() {
this.props.dispatch(getUser());
}
render() {
const { user } = this.props;
console.log(user)
return (
<ul>
{user.map(user =>
<li key={user.id}>{user.username}</li>
)}
</ul>
);
}
}
const mapStateToProps = state => ({
user: state.user,
});
export default connect(mapStateToProps)(UserDetailView);
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { createStore, compose, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import reducer from './store/reducers/auth';
const composeEnhances = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducer, composeEnhances(
applyMiddleware(thunk)
))
const app = (
<Provider store={store}>
<App />
</Provider>
)
ReactDOM.render(app, document.getElementById('root'));
registerServiceWorker();
Anyone got any ideas why this isn't working?
You're not supposed to setState() in that action creator:
this.setState({
user: res.data,
})
you should dispatch an action instead
Try this:
export function getUser() {
const id = this.params.match.id
return dispatch => {
dispatch(fetchUserBegin());
return Axios.get(`/api/user/${id}`)
.then( res => {
dispatch(fetchUserSuccess(res.data);
})
}
}
You should pass the mapDispatchToProps function to the connect() method as the second argument, like this:
import React from "react";
import { connect } from "react-redux";
import { getUser } from "../store/actions/userActions";
class UserDetailView extends React.Component {
componentDidMount() {
this.props.getUser() //fixed
}
render() {
const { user } = this.props;
console.log(user)
return (
<ul>
{user.map(user =>
<li key={user.id}>{user.username}</li>
)}
</ul>
);
}
}
const mapStateToProps = state => ({
user: state.user,
});
const mapDispatchToProps = dispatch => ({ //added
getUser: dispatch(getUser())
})
export default connect(mapStateToProps,mapDispatchToProps)(UserDetailView); //fixed
And also fix this:
case FETCH_USER_SUCCESS:
// All done: set loading "false".
// Also, replace the items with the ones from the server
return {
...state,
loading: false,
user: action.payload.user //fixed
};
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.
I followed one of the tutorial from youtube. When I do that it works completely fine. But when I try different api, I'm getting an error items are not defined. Can someone please help me to understand what I am doing wrong.
Thanks
//actions
import axios from 'axios'
export function fetchTweets(brandUrl, responseCode){
let url = brandUrl + '/api/offer/' + responseCode;
return function(dispatch){
axios.get(url)
.then((response) => {
dispatch({
type: 'FETCH_TWEETS_FULFILLED',
payload: response.data
})
})
.catch((error) => {
dispatch({
type: 'FETCH_TWEETS_REJECTED',
payload: error
})
})
}
}
//reducer
export default function reducer(state = {
tweets: [],
fetching: false,
fetched: false,
error: null
}, action) {
switch(action.type){
case 'FETCH_TWEETS_PENDING' :{
return { ...state, fetching: true }
}
case 'FETCH_TWEETS_REJECTED' : {
return { ...state, fetching: false, error: action.payload }
}
case 'FETCH_TWEETS_FULFILLED' : {
return { ...state,
fetching: false,
fetched: true,
tweets: action.payload }
}
}
return state
}
//main component
import React from 'react'
import { connect } from 'react-redux'
import { fetchTweets } from '../actions/tweetsActions'
class Layout extends React.Component{
fetchTweets(){
this.props.dispatch(fetchTweets(brandUrl, responseCode))
}
render(){
const { tweets } = this.props;
if(!tweets.length){
return <button value="Load" onClick={this.fetchTweets.bind(this)}>Load </button>
}
console.log(tweets.response.mainItems.length)
return (
<div>
<p>{tweets.statusMessage}</p>
<ul>
</ul>
</div>
);
}
}
function mapStateToProp(state){
return {
tweets : state.tweets.tweets
}
}
export default connect(mapStateToProp)(Layout)
//store
import { applyMiddleware, createStore } from 'redux'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import logger from 'redux-logger'
import reducer from './reducers'
const middleware = applyMiddleware(promise(), thunk, logger())
export default createStore(reducer, middleware)
// API Response
in main component's mapStateToProps function ,why you are accessing tweets by assigning state.tweets.tweets ,though you dont have reducer named tweets ,and you are not using combineReducer function (which one use in case of multiple reducer).
So you can easily access tweets by writing this.state.tweets .and you can print state in mapStateToProps before returning which might be helpful for debugging......
//main component
function mapStateToProp(state){
console.log(state,"=====>")
return {
tweets : state.tweets
}