Redux- mapStateToProps not working - javascript

I have a react component that makes an AJAX call in componentWillMount and backs the data received in response to a redux store. Here is code
componentWillMount() {
var self = this;
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
console.log(this.responseText);
var json = JSON.parse(this.responseText);
var data = {
json
};
self.props.dispatch({
type: "ADD_Exams",
data
});
}
};
xmlhttp.open("GET", "http://127.0.0.1:8000/getExams/", true);
xmlhttp.send();
}
In the reducer, I am assigning the data received in action to an array defined in the reducer state.
const initialState = {
exams:[]
}
const examreducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_Exams":
return {
...state,
exams: [...state.exams, action.data.json]
};
default:
return state;
}
};
But when I use mapStateToProps to read exams variable I get undefined.
const mapStateToProps = (state) => {
return {
exams: state.exams
}
}
export default connect(mapStateToProps)(Exam);
I am creating store like this
import { Provider } from "react-redux";
const store = createStore(loginReducer, examReducer);
ReactDOM.render(
<Provider store={store}>
<Exam />
</Provider>,
document.getElementById("root")
);
registerServiceWorker();
console.log(this.props.exams) prints undefined. What is the problem here?

I ran into the same problem. My mistake was because I was doing wrong export/import of the component.
export:
export class MyComponent extends React.Component
...
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
import:
import { MyComponent } from './MyComponent';
to solve I removed the export on the class and used the default:
import MyComponent from './MyComponent';

I spent countless hours finding what was wrong, why my props werent getting any information.
I followed #Romain Pellerin and was able to discovered something
I originally had my mapStateToProps like this:
const mapStateToProps = (state) => {
return {
userInfo: state.userInfo,
loading: state.loading,
error: state.error
};
}
Following Romain's answer, i console logged and watched the output of my state object, and found that i had to specify the specific reducer that had the state that i needed to access! Because i had used combineReducers, maybe you have to specify which reducer you want to access in mapStateToProps. Soo i had to change my code to this:
const mapStateToProps = (state) => {
console.log(state);
return {
userInfo: state.THE_SPECIFIC_REDUCER.userInfo,
loading: state.THE_SPECIFIC_REDUCER.loading,
error: state.THE_SPECIFIC_REDUCER.error
};
}
Heres my code where i combine the reducers:
const rootReducer = combineReducers({
loginReducer,
THE_SPECIFIC_REDUCER
});
Soo, given that i was trying to get "THE_SPECIFIC_REDUCER" state, i had to specify it in the mapStateToProps like this:
myProp: state.THE_SPECIFIC_REDUCER.theThingIWantToAccess
I hope this works for someone!

Can you edit your mapStateToProps like this to see the actual content of your state?
const mapStateToProps = (state) => {
console.log(state);
return {
exams: state.exams
}
}
I suspect your reducer is not at the root of your reducers. If so, you might need to do state.examreducer.exams.
Also, is your component wrapped a in Provider? You need it to access the context (through which your state is accessible).
UPDATE
Make sure to initialize your store before rendering any React component.
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
const store = createStore(combineReducers({loginReducer, examReducer}), {loginReducer:{}, examReducer:{}});
ReactDOM.render(
<Provider store={store}>
<Exam />
</Provider>,
document.getElementById('root')
)
Then update your mapStateToProps:
const mapStateToProps = (state) => {
return {
exams: state.examReducer.exams || []
}
}

I think there's an issue in how you retrieve data or initialize store. Try this:
import { createStore, combineReducers } from 'redux'
import loginReducer from '../path';
import examReducer from '../path';
const rootStore = combineReducers({
loginReducer,
examReducer
})
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<Exam />
</Provider>,
document.getElementById("root")
);
and then:
const mapStateToProps = (state) => {
return {
exams: state.examReducer.exams
}
}

Related

Data is not being stored in redux store

store imageI am going to store the data into the react-redux-store but it is not getting stored. I don't understand what I am missing...I have given my code below.
i am trying to store the data from the api but it is not working...
INDEX.JS
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./features/store";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
STORE.JS
import { configureStore } from "#reduxjs/toolkit";
import moviesReducer from "./movies/movieSlice";
export const store = configureStore({
reducer: moviesReducer,
});
MOVIE SLICE.JS
import { createSlice } from "#reduxjs/toolkit";
const initialstate = {
movies: [],
};
const movieSlice = createSlice({
name: "movies",
initialstate,
reducers: {
addMovies: (state, { payload }) => {
state.movies = payload;
},
},
});
export const { addMovies } = movieSlice.actions;
// export const getAllMovies = (state) => state.movies.movies;
export default movieSlice.reducer;
COMPONENT
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
import MovieAPI from "../config/MovieAPI";
import { addMovies } from "../features/movies/movieSlice";
const Home = () => {
const dispatch = useDispatch();
const fetchMovies = async () => {
const response = await MovieAPI.get(`?apiKey=1234&s=harry&type=movie`);
console.log(response.data);
dispatch(addMovies(response.data));
};
useEffect(() => {
fetchMovies();
}, []);
For the very first: createSlice expecting to recieve object with property named initialState instead initialstate, notice camelCase naming.
The next one: acording to location and slice name "movies" I may suspect you should define it as: const initialState = [];, due to it is "movies slice" initial state definition itself, otherwise you will have state with something like
state = {movies: {movies: []}}.
Also, you may wish to rewrite addMovies reducer in something like:
addMovies: (moview_slice_state, { payload }) => {
console.log("add movies", payload);
moview_slice_state.push(...payload);
}
where moview_slice_state - state of movies slice of whole state, e.g. state.movies.
By the way, due to #reduxjs/toolkit use immer under the hood you may "modify" state OR return new state, as Andrej Kirejeŭ propose. But NOT the both of them.
P.S. For the future, feel free to create minimal demo for your question or answer, some thing like live demo based on your code
return new state:
addMovies: (state, { payload }) => ({
...state,
movies: payload
}),
by the way, how do you know it is not stored. Please, show the code where you use state data to render some component.

controlled range-slider component in react with redux

i am learning redux with react and trying to create an app where i have a range-slider, whose value dictates how many Box components will be rendered on the screen.
i am trying to make the range-slider a controlled component but can't make it change the store. i am getting no errors.
the component:
import React from 'react';
import { connect } from 'react-redux';
import { setBoxNumber } from '../actions/actions';
const Slider = ({ boxNumber, handleChange }) => {
return(
<div>
<div>
{boxNumber}
</div>
<div>
<input
onChange={handleChange}
value={boxNumber}
type="range"
min="12"
max="480"
/>
</div>
</div>
)
}
const mapStateToProps = (state) => {
return { boxNumber: state.boxNumber }
}
const mapDispatchToProps = (dispatch) => {
return {
handleChange: (event) => dispatch(setBoxNumber(event.target.value))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Slider);
the reducer:
import { combineReducers } from 'redux';
export const boxNumberReducer = (boxNumber = 40, action) => {
switch(action.payload) {
case 'SET_BOX_NUMBER':
return action.payload;
default:
return boxNumber;
}
}
export default combineReducers({
boxNumber: boxNumberReducer
})
the action:
export const setBoxNumber = (number) => {
return {
type: 'SET_BOX_NUMBER',
payload: number
}
}
i also tried to call the handleChange method with an arrow function on change, like i would do with a controlled react component without redux, but it's making no difference
I think your reducer is configured incorrectly. You can pass all the initial states inside the variable initialState like this.
//reducer.js
import { combineReducers } from "redux";
const initialState = {
boxNumber: 40,
};
const boxReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_BOX_NUMBER":
return {
...state,
boxNumber: action.payload,
};
default:
return state;
}
};
export default combineReducers({
boxReducer,
});
This is how your index.js file should look like:
//index.js
import React from "react";
import ReactDOM from "react-dom";
import Slider from "./Slider.js";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducer from "./redux/reducer";
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<Slider />
</Provider>,
document.getElementById("root")
);
You need to update your mapStateToProps in Slider.js to access the states in your reducer.
//Slider.js
const mapStateToProps = (state) => {
return { boxNumber: state.boxReducer.boxNumber };
};
This is a simple fix. As your app gets bigger, you'll need more reducers and thus it's better to keep a separate file for that.

React Redux not re-rendering when Store changes

So I have been trying to figure this out for a day now.
I think I have set up everything correctly, however, the view does not re-render nor the prop updates. However, I can see the change in Redux Developer tools. I know there are other questions like this on Stackoverflow but none of them really helps me.
Am I not seeing something?
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import Store from './store';
import * as serviceWorker from './serviceWorker';
const store = createStore(Store, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
ReactDOM.render(
<Provider store={store} >
<App />
</Provider>
,
document.getElementById('root'));
//actions.js
const initPurchases = (payload) => {
return {
type: "INITILIZE_PURCHASES",
payload
}
}
module.exports = {
initPurchases,
}
// store.js
const initalState = {
inventory: [],
}
const rootReducer = (state = initalState, action) => {
switch(action.type) {
case "INITILIZE_PURCHASES":
state.purchases = [...action.payload];
break;
default:
return state;
}
return state;
}
export default rootReducer
import React from 'react';
import { connect } from 'react-redux';
import actions from './actions';
class App extends React.Component {
state = {}
componentDidMount = () => {
this.getPurchases();
}
getPurchases = async () => {
// call to api which returns t
this.props.initPurchases(t)
}
render() {
console.log(this.props.purchases) // Returns empty array []
return (
<div className="App">
// Some view
</div>
);
}
}
export default connect(
(state) => {return {purchases: state.purchases}},
actions,
)(App);
Logs from React Redux Developer Tools
Can somebody please help me? I can't figure out what's wrong here. I ommited most of the things that i are not related to my problem (at least I do not think they are). I can upload the entire repo to github to see the bigger context
Your reducer needs to return the new state, otherwise the state remains unchanged:
const rootReducer = (state = initalState, action) => {
switch(action.type) {
case "INITILIZE_PURCHASES":
return { ...state, purchases: [...action.payload] };
break;
default:
return state;
}
return state;
}
I think you need to implement something like:
import actions from './actions'
...
class App extends React.Component {
...
componentDidMount = () => {
this.props.initPurchases();
}
render() {
...
}
}
const mapDispatchToApp = (dispatch) => (
{
initPurchases: () => (
dispatch(actions.initPurchases())
),
}
)
...
export default connect(
(state) => {return {purchases: state.purchases}},
mapDispatchToApp,
)(App);
This is because you need to dispatch actions to the store

Why is my reducer returning an empty array in react/redux?

In my reducer, it returns an array of objects that i got from an api. I do a console.log on the list and I'm able to see the array, but when I get access to the reducer in my react class, it shows up as an empty array, why is that so?
Inside the render() function in my react file, it does print for some odd reason, but I have a function where I'm trying to render seperate divs using that data from the reducer and the array shows up empty.
getList() {
let arr = [];
if(this.props.popular){
arr = this.props.popular.map(item => {
return (
<div key={item.id} className="movie">
<img
src={`https://image.tmdb.org/t/p/w300${item.poster_path}`}
//onClick={() => this.displayModal(item)}
/>
</div>)
})
}
// console.log(arr)
// this.props.updateCurrentShowList(arr);
return arr;
}
I use this.props.popular from the mapstatetoprops function i have below.
import { FETCH_POPULAR, RESET_POPULAR } from "../Actions/types";
let initialList = [];
export default function(state = initialList, action){
switch(action.type){
case FETCH_POPULAR:
//return action.payload || false;
initialList = initialList.concat(...action.payload);
//console.log(initialList);
return initialList;
case RESET_POPULAR:
initialList = action.payload;
return initialList;
default:
return state;
}
}
Here the initialList is printed and works and i then return it.
This is my mapStateToProps function that i have in my other file where I want to get access to the array. I used combinereducers in one of my reducers file.
function mapStateToProps(state) {
return {
popular: state.popular
};
}
Why does this.props.popular print correctly when i do it in render(), but whenever i use it anywhere else, it doesnt?
action function
export const fetchPopular = (searchTypeFormat, page) => async (dispatch) => {
let url = `https://api.themoviedb.org/3/discover/${searchTypeFormat}?api_key=${APIKEY}&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=${page}`;
//console.log(url);
const res = await axios.get(url);
//console.log(res.data.results)
dispatch({type: FETCH_POPULAR, payload: res.data.results});
};
my store creation
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import reducers from './Reducers/index';
import reduxThunk from 'redux-thunk';
const store = createStore(reducers, {}, applyMiddleware(reduxThunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
I combined my reducers this way
import { combineReducers } from 'redux';
import authReducer from './authReducer';
import popularReducer from './popularReducer';
import genreListReducer from './genreListReducer';
import searchByGenreReducer from './searchByGenreReducer';
import { reducer as formReducer } from 'redux-form';
import modalReducer from './modalReducer';
import detailsReducer from './moreDetailReducer';
import userDisplayList from './userDisplayList';
export default combineReducers({
auth: authReducer,
form: formReducer,
popular: popularReducer,
genreList: genreListReducer,
searchByGenre: searchByGenreReducer,
modalData: modalReducer,
details: detailsReducer,
displayList: userDisplayList
})
the whole component
import React, { Component } from 'react';
import { withRouter } from "react-router-dom";
import { connect } from 'react-redux';
import * as actions from '../Actions';
class SearchPopular extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
page: 1
}
this.getList = this.getList.bind(this);
}
componentWillMount() {
//console.log(this.props.match.params.format)
this.props.fetchPopular(this.props.match.params.format, this.state.page);
console.log(this.props.popular)
console.log(this.getList());
}
getList() {
let arr = [];
if(this.props.popular){
arr = this.props.popular.map(item => {
return (
<div key={item.id} className="movie">
<img
src={`https://image.tmdb.org/t/p/w300${item.poster_path}`}
//onClick={() => this.displayModal(item)}
/>
</div>)
})
}
//console.log(arr)
// this.props.updateCurrentShowList(arr);
return arr;
}
render() {
console.log(this.props.popular);
return (
<div>
</div>
);
}
}
function mapStateToProps(state) {
return {
popular: state.popular,
updatedList: state.displayList
};
}
export default withRouter(connect(mapStateToProps, actions)(SearchPopular));
You are doing to state update in a wrong way. What you have done is it will always take empty array initially and then append into it.
case 'FETCH_POPULAR':
return [...state, ...action.payload];
Try this in your reducer.
****To your main issue
You are trying to fetch store.popular but you donot have popular in store
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const configureStore = () => {
const store = createStore(
combineReducers({
popular: Your reducer here
}),
composeEnhancer(applyMiddleware(thunk))
);
return store;
}
**** New update
I think that's the issue of function loosing the reference of this.
This is why we are using this.getList.bind(this) in the constructor
So when we call this.getList the function gets the reference of this and can use it. so when you are calling it directly from any other function then use this.getList.bind(this)
componentWillMount() {
//console.log(this.props.match.params.format)
this.props.fetchPopular(this.props.match.params.format, this.state.page);
console.log(this.props.popular)
console.log(this.getList.bind(this));
}
Don't mutate variables in Redux reducers! You'll get lots of weird effects and race conditions. You want to always return fresh new objects from a reducer, unless no action matches in the default case, then return the current state.
So firstly, don't define your initial state with a let and then mutate it in your reducers, that's completely wrong.
Secondly, if you want to return new state based on the previous state, as in your FETCH_POPULAR action, then use the state argument (that's what it's for).
Rewrite like this,
export default function(state = [], action){
switch(action.type){
case FETCH_POPULAR:
return [...state, ...action.payload];
case RESET_POPULAR:
return [];
default:
return state;
}
}

Redux - application state has the name of reducer as key

Could someone please help me with this problem?
I've started to learn React and Redux but I'm stuck from a couple of days on configuring redux.
I'm assuming that when something triggers an action, redux through the reducers stack of functions should return an object that represents my application state.
Unfortunately, It returns an object with { reducerName => reducer result } basically means that if I've 4 reducers, the function store.getState() returns something like
{
'reducerOne': entireApplicationState
'reducerTwo': entireApplicationState
'reducerThree': entireApplicationState
'reducerFour': entireApplicationState
}
I'll really appreciate if someone can help me because I've finished all the ideas :)
This is my application.js:
import React from 'react';
import ReactDom from 'react-dom';
import HomePage from 'root_views/home';
import {store} from 'root_services/redux/store';
class Application extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<HomePage/>
)
}
}
var Provider = React.createClass({
childContextTypes: {
store: React.PropTypes.object.isRequired
},
getChildContext: function () {
return {store: this.props.store}
},
render: function () {
return this.props.children;
}
});
ReactDom.render(
<Provider store={store}>
<Application/>
</Provider>,
document.getElementById('application')
);
My store.js
import { createStore } from 'redux';
import {rootReducer} from './reducers/container';
export const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
My container.js that basically contains all my reducers
import {combineReducers} from 'redux';
// This is just the action label
import {DATA_EXCHANGE_LOAD} from 'root_services/redux/actions/container'
const initialState = {
data_exchange: {},
}
function dataExchange(state = {}, action) {
switch (action.type) {
case DATA_EXCHANGE_LOAD:
return Object.assign({}, state, {
data_exchange:{'reducerOne':'dataExchange'}
});
break;
default:
return initialState;
break;
}
};
function testReducer(state = {}, action) {
switch (action.type) {
case DATA_EXCHANGE_LOAD:
return Object.assign({}, state, {
data_exchange:{'reducerTwo':'testReducer'}
});
break;
default:
return initialState;
break;
}
};
// Export the combined reducers
export const rootReducer = combineReducers({
dataExchange,
testReducer
});
This is the action that triggers the event:
export function dataExchangeLoad(){
return {
type: DATA_EXCHANGE_LOAD,
}
};
This is my component where the action is triggered:
import React from 'react'
import "../components/layouts/header/header.less";
import {dataExchangeLoad} from "root_services/redux/actions/container"
export default class HomePage extends React.Component {
constructor(props, {store}) {
super(props);
store.dispatch(dataExchangeLoad());
console.log(store.getState());
}
render() {
return (
<div>
<h1>test</h1>
</div>
)
}
};
HomePage.contextTypes = {
store: React.PropTypes.object,
}
This is the result:
Object {dataExchange: Object, testReducer: Object}
As was already answered in comments combineReducers indeed works that way. In case you want to chain reducers so that action will go through all of them sequentially updating state in each one you can use reduce-reducers. Using this helper function it's possible to do something like that (looks like that is what you want to achieve):
import reduceReducers from 'reduce-reducers';
const reducer1 = (state = {}, action) => {
if (action.type === 'foo') {
return ({
...state,
touchedBy: ['reducer1'],
})
}
return state;
};
const reducer2 = (state = {}, action) => {
if (action.type === 'foo') {
return ({
...state,
touchedBy: state.touchedBy.concat('reducer2'),
})
}
return state;
};
const reducer = reduceReducers(reducer1, reducer2);
expect(reducer({}, { type: 'foo' }))
.toMatchObject({ touchedBy: ['reducer1', 'reducer2'] });
In case anyone is looking, the link provided above in the comments is broken. This link works and explains well how to rename the state coming from your reducers. If you don't want to read, rename your reducer import or rename it inside your combineReducer.
Example1:
import billReducer as billState from "./reducers";
Example2:
const rootReducer = combineReducer({billState: billReducer});
Using combineReducers

Categories