After one of the Redux tutorials decided to implement that facny action->reducer->store->view chain for simple app with only login part.
Seems like all setted up but when I run my app - in mapStateToProps(currentState) no any sign of the any custom state fields which I expected to see! (default state from reducer). But the action function is fine, as you can see on the screenshot
I can't see whats wrong here so, decided to ask it.
So here is the code
So, first of all - store.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
then login reducer
const initialState = {
user: {
name: '',
password: ''
},
fetching: false
}
export default function login(state = initialState, action) {
switch (action.type) {
case LOGIN_REQUEST: {
return { ...state, fetching: true }
}
case LOGIN_SUCCESS: {
return { ...state, user: action.data, fetching: false }
}
case LOGIN_FAIL: {
return { ...state, user: -1, fetching: false }
}
default: {
return state;
}
}
}
and the root (reducers/index.js):
import login from './login/login';
import { combineReducers } from 'redux'
export default combineReducers({
login
});
the action
import {
LOGIN_REQUEST,
LOGIN_SUCCESS,
LOGIN_FAIL
} from '../../constants/login.js';
export function onLoginAttempt(userData) {
return (dispatch) => {
dispatch({
type: LOGIN_REQUEST,
user: userData
})
tryLogin(userData);
}
};
function tryLogin(userData) {
let url = 'SignIn/Login ';
return (dispatch) => {
axios.post(url, userData)
.then((response) => dispatch({
type: LOGIN_SUCCESS,
data: response.data
})).error((response) => dispatch({
type: LOGIN_FAIL,
error: response.error
}))
}
};
So here is entrance point:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './containers/app.js';
import configureStore from './store/configureStore';
let store = createStore(configureStore);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("content")
);
and here is the app.js (Login is just sompe custom div with two fields nothing special)
import React, { Component } from 'react';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux';
import Login from '../components/login/Login';
import * as pageActions from '../actions/login/login'
class App extends Component {
render() {
const { user, fetching } = this.props;
const { onLoginAttempt } = this.props.pageActions;
return <div>
<Login name={user.name} password={user.password} fetching={fetching} onLoginAttempt={onLoginAttempt} />
</div>
}
}
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
I see that you have state which looks like:
reduxState = {
login: {
user: {
name: '',
password: ''
},
fetching: false
}
}
but then you try to access properties that don't exist.
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
I think you need to:
function mapStateToProps(currentState) {
return {
user: currentState.login.user,
fetching: currentState.login.fetching
}
}
You have this line let store = createStore(configureStore); on your entry point. However, inside configureStore, you have a call to createStore()
Basically you're calling something like createStore(createStore(reducers)). That's probably the cause of the problem.
You should probably call it like
let store = configureStore( /* pass the initial state */ )
Related
I am working on a signup React app that uses redux. Everything other thing works quite right with the exception of state update.
I've gone through several recommendations already given here and I don't seem to see what's wrong with the code.
The authAction.js
import { API_URL } from '../../../constants/constants';
const LOGIN_SUCCESSFUL = 'LOGIN_SUCCESSFUL';
const LOGIN_LOADING = 'LOGIN_LOADING';
const LOGIN_FAILED = 'LOGIN_FAILED';
const login = values => {
let url = API_URL + 'login';
return async (dispatch) => {
dispatch({
type: LOGIN_LOADING
})
const response = await fetch (url, {
method: 'POST',
body: JSON.stringify(values),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
const data = await response.json();
console.log(data);
if(response.status >=200 && response.status <= 299)
{
sessionStorage.setItem('_token', data.data.jwt)
dispatch({
type: LOGIN_SUCCESSFUL,
payload: {
isAuthenticated: true,
jwt: data.data.jwt ?? ''
}
});
}
dispatch({
type: LOGIN_FAILED,
payload: {
isAuthenticated: false,
jwt: '',
message: data?.message ?? 'Authentication failed.'
}
})
}
}
export { login, logout };
authReducer.js
const LOGIN_SUCCESSFUL = 'LOGIN_SUCCESSFUL';
const LOGIN_FAILED = 'LOGIN_FAILED';
const LOGIN_LOADING = 'LOGIN_LOADING';
const initialState = {
jwt: '',
isAuthenticated: false,
message: '',
loading: false,
error: false,
};
const authReducer = (state = initialState, action) => {
if(action.type === LOGIN_LOADING)
{
return {
...state,
message: 'Authenticating...',
loading: true
}
}
if(action.type === LOGIN_SUCCESSFUL)
{
return {
...state,
isAuthenticated: true,
jwt: action.payload.jwt,
message: action.payload.message,
laoding: false,
error: true
}
}
if(action.type === LOGIN_FAILED)
{
return {
...state,
jwt: '',
isAuthenticated: false,
loading: false
};
}
return initialState;
}
export default authReducer;
rootReducer.js where I combined other reducers
import { combineReducers } from "redux";
import userReducer from "./users/userReducer";
import authReducer from './users/authReducer';
import signupReducer from './users/signupReducer';
import postReducer from './postReducer'
const rootReducer = combineReducers({
user: userReducer,
auth: authReducer,
signup: signupReducer,
posts: postReducer
});
export default rootReducer;
signup.js that handles the view
import {useFormik } from 'formik';
import React, { useEffect } from 'react';
import { Helmet } from 'react-helmet';
import { Link, Navigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import * as Yup from 'yup';
import logo from '../../assets/img/logo1.jpeg';
import Error from '../../components/forms/Error';
import LandingLayout from '../layouts/landing';
import signup from '../../redux/actions/users/signupActions';
import Toast from '../../components/alerts/Toast';
const Signup = () => {
const {loading, error, status} = useSelector(state => state.signup);
const dispatch = useDispatch();
useEffect(()=>{
if(status)
{
setTimeout(() => {
return <Navigate to='/login' />
}, 2000);
}
}, [dispatch, status])
...
onSubmit: (values) => {
dispatch(signup(values));
}
...
export default Signup;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import rootReducer from './redux/reducers/rootReducer';
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(thunk)));
ReactDOM.render(
<React.StrictMode>
<Provider store = {store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
when a log the response from the API call, I get the expected response but nothing is effected on the UI.
Well, it appears that the error was coming from somewhere else. Just as previously stated, everything I did was quite right aside the fact that one of the reducers - userReducer - included in the rootReducer had its action creator returning the wrong payload.
I commented that out and everything else worked.
However, should subtle bug from one reducer affect the entire workings of the store?
I'm using React-Laravel for my project.
The problem is when I tried to use redux-thunk for the asynchronous dispatch function.
My dispatch function won't get executed.
Please do help me figure out this problem.
I have already tried to use promise or redux-devtools-extension library
https://codeburst.io/reactjs-app-with-laravel-restful-api-endpoint-part-2-aef12fe6db02
app.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import Layout from './jsx/Layout/Layout';
import marketplaceReducer from './store/reducers/marketplace';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const appReducer = combineReducers({
marketplace: marketplaceReducer
});
const rootReducer = (state, action) => {
return appReducer(state, action);
}
const store = createStore(rootReducer, composeEnhancers(
applyMiddleware(logger, thunk)
));
const render = (
<Provider store={store}>
<BrowserRouter>
<Layout />
</BrowserRouter>
</Provider>
);
ReactDOM.render(render, document.getElementById('root'));
marketplace.js (action)
import * as actionTypes from './actionTypes';
import axios from '../../axios';
export const loadMarketplace = () => {
console.log("Load Marketplace");
return {
type: actionTypes.LOAD_MARKETPLACE
};
}
export const successMarketplace = (data) => {
console.log("Success Marketplace");
return {
type: actionTypes.SUCCESS_MARKETPLACE,
data: data
}
}
export const failedMarketplace = () => {
console.log("Failed Marketplace");
return {
type: actionTypes.FAILED_MARKETPLACE
}
}
export const showMarketplace = () => {
console.log("Show Marketplace Action")
return dispatch => {
//This is the problem
//Inside this function, I can't see any console.log, even loadMarketplace() didn't get called.
console.log("Show Marketplace in dispatch");
dispatch(loadMarketplace());
axios.get('/marketplaces')
.then(response => {
dispatch(successMarketplace(response));
})
.catch(error => {
dispatch(failedMarketplace());
});
};
}
marketplace.js (reducer)
import * as actionTypes from '../actions/actionTypes';
const initial_state = {
data: [],
loading: false
}
const loadMarketplace = (state, action) => {
console.log("Load Marketplace Reducer")
return {
...state,
loading: true
};
}
const successMarketplace = (state, action) => {
console.log("Success Marketplace Reducer", action.data)
return {
...state,
loading: false,
data: action.data
};
}
const failedMarketplace = (state, action) => {
return {
...state,
loading: false
};
}
const reducer = (state = initial_state, action) => {
//This is called when the first init, never got it through showMarketplace() function.
console.log("Marketplace Reducer", action);
switch (action.type) {
case actionTypes.LOAD_MARKETPLACE: return loadMarketplace(state, action);
case actionTypes.SUCCESS_MARKETPLACE: return successMarketplace(state, action);
case actionTypes.FAILED_MARKETPLACE: return failedMarketplace(state, action);
default: return state;
}
}
export default reducer;
Marketplace.js (jsx view)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../../store/actions';
class Marketplace extends Component {
componentDidMount() {
console.log('[ComponentDidMount] Marketplace')
this.props.showMarketplace();
}
render() {
return (
<React.Fragment>
Marketplace
</React.Fragment>
);
}
}
const mapDispatchToProps = dispatch => {
return {
showMarketplace: () => dispatch(actions.showMarketplace)
};
}
export default connect(null, mapDispatchToProps)(Marketplace);
This is the result of my console.log (when loading the first time for Marketplace.js)
Please do help, I've been struggling for 2 hours or more, only because of this problem. (This is my first time using React-Laravel).
Thank you.
I already found the problem. It is not redux-thunk problem.
It is actually a normal Redux problem we found anywhere.
Marketplace.js (jsx view)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../../store/actions';
class Marketplace extends Component {
componentDidMount() {
console.log('[ComponentDidMount] Marketplace')
this.props.showMarketplace();
}
render() {
return (
<React.Fragment>
Marketplace
</React.Fragment>
);
}
}
const mapDispatchToProps = dispatch => {
return {
showMarketplace: () => dispatch(actions.showMarketplace) //THIS IS THE PROBLEM, IT IS NOT EXECUTING PROPERLY. THIS ONE SHOULD BE
showMarketplace: () => dispatch(actions.showMarketplace()) //SHOULD BE LIKE THIS.
};
}
export default connect(null, mapDispatchToProps)(Marketplace);
Edited: I think it is something about thunk is not added right to redux.
First of all try to add only thunk.
const store = createStore(rootReducer, composeEnhancers(
applyMiddleware(thunk)
));
If it works, maybe try to change the order of them.
I have set up my very first Redux projet and I am trying to create a store for my current user by fetching it from my rails backend.
Although everything seems fine, this.props.user gives null in the component.
store.js:
import { createStore, applyMiddleware } from "redux";
import thunk from 'redux-thunk';
import rootReducer from "./reducers";
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
export default store;
actions.js
import { CREATE_USER, PROPAGATE_LOGIN, PROPAGATE_LOGOUT } from "./actionTypes";
import axios from 'axios';
export const getCurrentUser = () => {
return dispatch => {
axios.get("/users/get_current_user", {})
.then(response => {
if (response.data.user) {
dispatch(propagateLogin(response.data.user));
} else {
console.log("pas d'utilisateur connecté.")
dispatch(propagateLogout());
}
});
};
};
export const propagateLogin = (user) => ({
type: PROPAGATE_LOGIN,
payload: {
user
}
});
export const propagateLogout = () => ({
type: PROPAGATE_LOGOUT,
payload: { }
});
users.js reducer:
import { CREATE_USER, PROPAGATE_LOGIN, PROPAGATE_LOGOUT } from "../actionTypes";
const initialState = {
user: null
};
export default function(state = initialState, action) {
switch (action.type) {
case PROPAGATE_LOGIN: {
return {
user: action.payload.user
}
}
case PROPAGATE_LOGOUT: {
return {
user: null
}
}
default:
return state;
}
}
AppRouter.js (the connected component):
class AppRouter extends React.Component {
defaultState() {
return {
isReady: false,
user: this.props.user,
loginModalOpen: false,
signupModalOpen: false
}
}
constructor(props) {
super(props)
this.state = this.defaultState()
}
componentDidMount() {
this.getUser();
}
getUser(history = undefined) {
this.props.getCurrentUser();
this.setState({
isReady: true
});
}
render (){
// [...]
}
};
const mapStateToProps = (state /*, ownProps*/) => {
return {
user: state.users.user
}
}
const mapDispatchToProps = { getCurrentUser, propagateLogout }
export default connect(
mapStateToProps,
mapDispatchToProps
)(AppRouter);
And here is a screenshot from the React dev console: for the Provider Component:
As you write in user.js reducer user inital state is null
const initialState = {
user: null
};
Since get user action is async, you are just assining null value to user in inital state
defaultState() {
return {
isReady: false,
user: this.props.user, //this.props.user null here
loginModalOpen: false,
signupModalOpen: false
}
You can use this.props.user without assing it state value
I'm working with React-Native and Redux.
I need to access my User state on react redux after an action.
I try this with a simple console.log after the action on my Component (console.log(this.props.User)) but it is always undefined.
This is my Combine Reducer:
import { combineReducers } from 'redux'; import User from './userReducer';
export default combineReducers({
User });
This is my User Reducer:
import {
REGISTER_USER,
SIGNIN_USER } from '../types';
export default function(state=INITIAL_STATE, action){
switch(action.type){
case SIGNIN_USER:
return {
...state,
userData:{
uid: action.payload.localId || false,
token: action.payload.idToken || false,
refreshToken: action.payload.refreshToken || false,
}
};
break;
default:
return state
} }
This is my action:
export function signIn(data){
const request = axios({
method:'POST',
url:SIGNIN,
data:{
email: data.email,
password: data.password,
returnSecureToken:true
},
headers:{
"Content-Type":"application/json"
}
}).then(response => {
return response.data
}).catch( e=> {
console.log(e)
});
return {
type: SIGNIN_USER,
payload: request
}
}
Component where I try to get the state after action:
import { connect } from 'react-redux';
import { signIn } from '../Store/Actions/userActions';
import { bindActionCreators } from 'redux';
class LoginComponent extends React.Component {
state = {
email:'',
password:''
};
signInUser () {
try {
this.props.signIn(this.state).then( () => {
**console.log(this.props.User)**
})
} catch (error) {
console.log(error)
}
}
}
const mapStateToProps = (state) => {
return{
User: state.User
}
}
const mapDispatchToProps =( dispatch ) => {
return bindActionCreators({signIn}, dispatch)
}
export default connect(mapDispatchToProps,mapDispatchToProps)(LoginComponent);
The response on log just is: undefined
What is my mistake?? Thanks!
The order of the arguments that you are passing into connect method is wrong it expects mapStateToProps first and mapDispatchToProps as second
parameter. So your code has to be export default connect(mapStateToProps, mapDispatchToProps)(LoginComponent);
Here is the signature
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
I'm developing a react-redux app and I can get access to the reducers via routes. Now I'm facing the trouble of getting access to a specific reducer without using routes.
Here is my reducers.js:
const initialState = {
loading: false,
topics: []
};
export default createReducer(initialState, {
[LOADING_DATA]: (state, action) => {
return Object.assign({}, state, {
loading: action.loading
});
}
});
This is my actions.js:
export function loading (loading) {
return {
type: LOADING_DATA,
payload: {loading}
};
}
And this is what I have on my component:
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux';
import * as moduleActionCreators from '...';
import * as actionCreators from '...';
class MyComponent extends Component {
...
render () {
return (<div>
...
</div>;
}
}
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(Object.assign({}, moduleActionCreators, actionCreators), dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
Normally in the mapStateToProps I reference the reducer variables as loading: state['my_reference_to_reducer'].loading but I can't figure it out how to tell the component to reference my reducers.js in order to get loading as props.
I would appreciate a light on this.
You need to set up the state in mapStateToProps function in order to access it:
const mapStateToProps = (state) => {
return {
loading: state.loading
}
}
Then you should be able to use it as this.props.loading in MyComponent.
Your reducer can look like this:
export default function reducer(state = {}, action) {
switch(action.type) {
case 'LOADING_DATA':
return Object.assign({}, state, {
...state,
loading: action.payload.loading
})
I recommend you to use redux ducks pattern as it keeps action creators and reducers at the same file, saves you time and makes it easier to read and use. For example:
loading.js
// Actions
const LOADING_DATA = 'LOADING_DATA'
// Action Creators
export const loadingData = (data) => {
return {
type: LOADING_DATA,
payload: {
loading: data
}
}
}
// Reducer
export default function reducer(state = {
loading: 'DATA zeroed'
}, action) {
switch(action.type) {
case 'LOADING_DATA':
return Object.assign({}, state, {
...state,
loading: action.payload.loading
})
default:
return state
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import MyComponent from './MyComponent';
import configureStore from './configureStore'
const store = configureStore()
ReactDOM.render(
<MyComponent store={store}/>,
document.getElementById('root')
);
configureStore.js
import { createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import loadingData from './loading'
const configureStore = () => {
return createStore(loadingData, composeWithDevTools())
}
export default configureStore
MyComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { loadingData } from './loading';
class MyComponent extends Component {
constructor(props){
super(props)
this.onLoadingData = this.onLoadingData.bind(this)
}
componentDidMount() {
this.props.loadingData('no more undefined')
}
onLoadingData() {
this.props.loadingData('DATA')
}
render() {
console.log(this.props.loading)
return (
<div>
<h2>MyComponent</h2>
<button onClick={this.onLoadingData}>Load Data</button>
<p>{this.props.loading}</p>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.loading
}
}
const mapDispatchToProps = {
loadingData
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyComponent)