The page does not render, citing TypeError: state is undefined, tracing back to this line in SelectForm.js: const filter = useSelector(state => state.filter);.
I've spent hours trying to figure out what I'm doing wrong. I've tried createSelector but that didn't work. I've tried dispatching a "Fetch Initial State" action, and that didn't work. The component is wrapped in provider tags. I'm not sure why I don't have access to the state. At this point I'm unable to see any flaws I've been looking at it for so long.
Code Snippets
reducer.js
let initialState = {
filter: {
country: null,
state: null,
level: null,
team: null
},
isDBConnecting: false,
isDBConnected: false,
isDBError: false
}
const SelectorReducer = (state=initialState, action) => {
switch (action.type) {
case 'DB_CONNECT_INIT':
return {
...state,
isDBConnecting: true,
isDBConnected: false,
isDBError: false,
};
...
...
}
export default SelectorReducer;
actions.js
export const initializeDBConnection = () => {
return {
type: 'DB_CONNECT_INIT'
}
};
ParentComponent.js
import React from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux'; //import provider to provide component access to the state
//Component imports
import SelectForm from './components/SelectForm'
import SelectorReducer from '.../reducer.js'
const SelectorStore = createStore(SelectorReducer);
const ParentComponent = () => {
return (
<div className="page-container">
<div id="carousel">
<div id="wrapper">
<Provider store={SelectorStore}>
<SelectForm />
</Provider>
</div>
</div>
</div>
)
}
SelectForm.js (Child Component, wrapped in Provider tags above)
//IMPORTS
import React from 'react'; //import react
import { useSelector, useDispatch } from 'react-redux';
//COMPONENT IMPORTS
import FormGroup from '../FormGroup';
import { * as actions } from '.../actions.js';
const SelectForm = (props) => {
//STATEFUL IMPORTS
//filter
const filter = useSelector(state => state.filter);
Credit to #NicholasTower for the answer in the comments. My reducer did not have a default case in which
default: return state
Putting that in solved the issue.
let filter = useSelector(state => {
console.log('State: ', state);
return state.pieChart.filter;
});
Make sure that you are using the correct object for state.
Add a console for debugging and check the state object. Many times we use multiple reducers which makes our component state nested in global object.
We should import useSelector form react-redux and then select user details form store in the following way
import { useSelector } from 'react-redux'
const User = () => {
const userInfo= useSelector(state => state.user)
return <div>{userInfo.name}</div>
}
Inside ParentComponent.js you are saying <Provider store={TeamSelectorStore}>. I think you meant to say <Provider store={SelectorStore}>
In my case I was storing the data coming from Redux state at didMount stage whereas the Redux states weren't fully uploaded yet.
const currentUser = useAuth();
useEffect(() => {
form.setFieldValue('email', currentUser.email);
}, []);
Adding currentUser to useEffect dependency array, get it resolved for me:
useEffect(() => {
form.setFieldValue('email', currentUser.email);
}, [currentUser]);
Sometimes having a break in the switch block of the reducer can cause the prop not defined in the state.
This error can also happen because of an uppercase / lowercase mistake.
let horns = useSelector(state => state.largeRhinohorns);
let horns = useSelector(state => state.largeRhinoHorns);
Related
I created a slice with createSlice using the redux toolkit.
import { createSlice } from "#reduxjs/toolkit";
export const selectAppColorChange = state => state.lightMode;
const appColorChangeSlice = createSlice({
name: 'appColorChange',
initialState: {lightMode: true},
reducers: {
changeState: state => {
state.lightMode = state.lightMode ? false : true;
}
}
});
export const { changeState } = appColorChangeSlice.actions;
export default appColorChangeSlice.reducer;
Then I created a button with the initial state as the sun image which will change itself to the moon image when clicked and opposite.
import Sun from '../../images/icon-sun.svg';
import Moon from '../../images/icon-moon.svg';
import { useSelector, useDispatch } from 'react-redux';
import { selectAppColorChange, changeState } from '../App/appColorChangeSlice';
export function TodoHeader(){
const dispatch = useDispatch();
const buttonIconBool = useSelector(selectAppColorChange);
function changeBackground () {
dispatch(changeState());
console.log(buttonIconBool);
}
return (
<div className='todoHeader'>
<h1>TODO</h1>
<img className='changeColorButton'
src={buttonIconBool ? Sun : Moon}
onClick={changeBackground}
alt='change_state_button'/>
</div>
)
}
But an error occur that the moon always appears and when I clicked on the moon it stays remained, to find out more and fix the problem I console.log(buttonIconBool) to the console. Instead of giving the boolean value that I want it returns undefined.
How can I fix this? Thanks for answering my question!
Looks like you have the wrong selector.
Assuming your have given appColorChange in your store.js like this,
import appColorChange from "******/appColorChange**.js";
export const store = configureStore({
reducer: {
appColorChange: appColorChange
},
})
then the selector should be,
export const selectAppColorChange = state => state.appColorChange.lightMode;
or
const buttonIconBool = useSelector(state => state.appColorChange.lightMode);
The selector will be called with the entire Redux store state as its only argument.
Reference: https://react-redux.js.org/api/hooks#useselector
I'm trying to fetch and display data on the initial load of an application using react and redux. But the component that should display the data does not have the data by the time it is rendered. It eventually gets the data but doesn't re-render for some reason.
Here are my two components in question:
App.js
import React, {useEffect} from 'react';
import './App.css';
import RecordList from './components/RecordList';
import CreateRecord from './components/CreateRecord';
import {useDispatch} from 'react-redux';
import { initRecords } from './actions/recordActions';
function App() {
const dispatch = useDispatch();
// Gets initial record list.
useEffect(() => {
dispatch(initRecords());
}, [dispatch])
return (
<div className="App">
<CreateRecord />
<RecordList/>
</div>
);
}
export default App;
RecordList.js
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
export default function RecordList() {
const dispatch = useDispatch();
const records = useSelector(state=>state);
console.log('state: ', records)
return (
<div>
<h3>Albums</h3>
{records.map(record =>
<div key={record.id}>
{record.albumName} by {record.artist}
</div>
)}
</div>
)
}
The issue I'm having is that initial data fetch in App.js isn't returning fast enough by the time the RecordList.js component is rendered. So in RecordList.js this bit throws an error saying map is not a function or cannot map on undefined:
{records.map(record =>
<div key={record.id}>
{record.albumName} by {record.artist}
</div>
)}
The component does eventually get the data if you comment out the JSX throwing the error. Initially it logs records as undefined but after a second it logs it with correct values.
Here are my reducer and actions:
recordActions.js
import recordService from '../services/records';
export const initRecords = () => {
return async dispatch => {
const records = await recordService.getAll();
console.log('from actions: ', records);
dispatch({
type: 'INIT_RECORDS',
data: records
})
};
}
reducer
const recordReducer = (state = [], action) => {
console.log('state now: ', state)
console.log('action', action)
switch(action.type) {
case 'CREATE':
return [...state, action.data];
case 'INIT_RECORDS':
return action.data;
default: return state;
}
}
export default recordReducer
Lastly, here is where I am making the axios call:
service
const getAll = async () => {
const response = await axios.get('someapi.com/records');
return response.data;
}
I've tried to conditionally render both the entire recordsList component and the records.map but the conditions only check once on the first load and never check again.
From my understanding, useSelector() should re-render the component when there's a state change, is it possible the state is just being mutated and not changed and how can I fix this?
Figured it out! Turns out in useSelector(state=>state) I needed to change it to useSelector(state=>state.records.Items) to get to the array.
My app is simply supposed to increase the age (initialized to 20) or decrease the age if ADD button or SUBTRACT button is clicked, respectively.
I am not understanding why I keeping getting this error. I check in reducer.js to make sure to initialize the state with the 'age' property accordingly, so I am completely lost as to what might be causing this. I double checked that I had all the right dependencies, as well as made sure all the syntax is correct.
One other thing I think might be causing it is that I am not using the spread operator in my reducer.js for the line of code const newState = {state} . For some reason, vscode is not letting me use the spread operator. Everytime I do and run my app, nothing appears on screen. Is there a dependency that needs to be installed to use the spread operator?
The following is my code:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reducer from './reducer';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// App.js
import React from 'react';
import './App.css';
import { connect } from 'react-redux';
class App extends React.Component {
render() {
return (
<div className="App">
<div>Age: <span>{this.props.age}</span></div>
<button onClick={this.props.onAgeUp}>Add</button>
<button onClick={this.props.onAgeDown}>Sub</button>
</div>
);
}
}
const mapStateToProps = (state) => {
return { age: state.age };
};
const mapDispatchToProps = (dispatch) => {
return {
onAgeUp: () => dispatch({ type: 'ADD' }),
onAgeDown: () => dispatch({ type: 'SUBTRACT' })
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
// reducer.js
import { ADD, SUBTRACT } from './actionTypes';
const initialState = {
age: 20
};
const reducer = (state=initialState, action) => {
const newState = {state};
switch(action.type) {
case ADD:
newState.age+=1;
break;
case SUBTRACT:
newState.age-=1;
break;
default:
return state;
}
};
export default reducer;
Quick fix, You can use Object.assign instead of spread operator:
const newState = Object.assign({}, state);
For using spread operator you must check couple of things:
first make sure #babel/plugin-proposal-object-rest-spread dependency is in package.json, then add it to plugins of babel config file:
{
"plugins": ["#babel/plugin-proposal-object-rest-spread"]
}
for more info visit babel rest spread
I see three problems here:
You're declaring newState as {state}. This creates object within object.
Since you're declaring newState with const, it can not be changed. But you're trying to change newState in both of your case.
You're not returning anything to the state. Main idea of the redux is to update the state by returning the new state.
newState looks like this the way you define it:
{
state: {
age: 20
}
}
That's why age is undefined, it's not an immediate child property.
Eg:
const letters = { alpha: 'a' }
const spreadLetters = { letters }
spreadLetters will be a new object, with one property, "letters".
The value of this property will be an exact copy of the original letters variable.
{
letters: {
alpha: a
}
}
I have three pages, PageA, PageB and PageC, that contain a form element formField.
State in globalReducer.js
import { fromJS } from 'immutable';
const initialState = fromJS({
userInteractionBegun: false,
pageActive: '',
refreshData: true,
})
I want to dispatch an action that sets pageActive to corresponding page value(One of A, B or C) when the component(page) mounts and refreshes formField to blank if userInteractionBegun === false.
For every page component, to get pageActive state in props from globalReducer, I do,
function PageA(props) {
//.....
}
// globalState is defined in conigureStore, I am using immutable.js. Link provided below this code.
const mapStateToProps = state => ({
pageActive: state.getIn(['globalState', 'pageActive']),
})
export default connect(mapStateToProps, null)(PageA);
Link to immutable.js getIn()
store.js
import globalReducer from 'path/to/globalReducer';
const store = createStore(
combineReducers({
globalState: globalReducer,
//...other reducers
})
)
I want to abstract the logic to update pageActive every time a component(page) mounts.
I know how to abstract this logic using an HOC, but I don't know how to do it using react hooks, so that every time pageA, pageB or pageC mounts, an action to setPageActive is dispatched and formField is set to blank if userInteractionBegun is false.
For instance, I would do in pageA.js
import usePageActive from 'path/to/usePageActive';
const [pageActive, setPageActive] = useReducer(props.pageActive);
usePageActive(pageActive);
Then in usePageActive.js
export default usePageActive(pageActive) {
const [state, setState] = useState(pageActive);
setState(// dispatch an action //)
}
I haven't had much time to dip my toes into react hooks yet, but after reading the docs and playing with it for a minute, I think this will do what you're asking. I'm using built-in state here, but you could use redux or whatever else you like in the effect. You can see a working example of this code here The trick is using a hook creator to create the custom hook. That way the parent and children can keep a reference to the same state without the useEffect affecting the parent.
import React, { useState, useEffect } from 'react';
import ReactDOM from "react-dom";
const activePageFactory = (setActivePage) => (activePage) => {
useEffect(() => {
setActivePage(activePage)
return () => {
setActivePage('')
}
}, [activePage])
return activePage
}
function App() {
const [activePage, setActivePage] = useState('');
const [localPage, setLocalPage] = useState('Not Selected');
const selectedPage = () => {
switch(localPage) {
case 'A':
return <PageA useActivePage={activePageFactory(setActivePage)} />
case 'B':
return <PageB useActivePage={activePageFactory(setActivePage)} />
default:
return null;
}
}
return (
<div>
<p>Active page is {activePage}</p>
<button onClick={() => setLocalPage('A')}>
Make A Active
</button>
<button onClick={() => setLocalPage('B')}>
Make B Active
</button>
{
selectedPage()
}
</div>
);
}
function PageA({useActivePage}) {
useActivePage('A');
return (
<div>
<p>I am Page A</p>
</div>
)
}
function PageB({useActivePage}) {
useActivePage('B');
return (
<div>
<p>I am Page B</p>
</div>
)
}
So I'm trying to learn React with Redux and so far I think I've been able to work out most of the code needed to make it work but I'm having an issue with getting my state passed down to my component. I am using Visual Studio 2017's ASP.NET Core project template that has react and redux boilerplate codes and they used this:
export default connect(
state => state.weatherForecasts,
dispatch => bindActionCreators(actionCreators, dispatch)
)(FetchData);
I tried doing the same thing with my own component like so:
export default connect(
state => state.lecture,
dispatch => bindActionCreators(actionCreators, dispatch)
)(LectureTable);
but when trying to access the contents of my props, the properties I want to get are tagged as undefined. I checked through Redux devtools that my initial state exists but my component is unable to see the props I'm trying to pass to it. The weird thing is I just imitated the boilerplate code but it isn't working yet the boilerplate code works just fine (ie I can go to the component and log out its initial state).
Since I'm following the format used by Visual Studio,my actioncreators, reducers, and constants are in one file shown below:
const GET_LECTURES = "GET_LECTURES";
const initialState = {
lectures: [],
selectedLecture: {},
isLoading: false,
test: 0
};
export const actionCreators = {
requestLectures: isLoading => async (dispatch) =>
{
if (!isLoading) {
// Don't issue a duplicate request (we already have or are loading the requested data)
return;
}
dispatch({ type: GET_LECTURES });
const url = `api/lecture/`;
const response = await fetch(url);
const lectures = await response.json();
dispatch({ type: RECEIVE_LECTURES, payload: lectures });
}
};
export const reducer = (state = initialState, action) => {
switch (action.type) {
case GET_LECTURES:
return { ...state, isLoading: true };
default:
return state;
}
};
I'm sorry if its all messy. I'm really just starting to begin to understand redux..
Edit
My component code:
import React, { Component } from 'react';
import {Button, Table, Label, Menu, Icon} from 'semantic-ui-react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {actionCreators} from './../../store/Lecture';
export class LectureTable extends Component {
componentWillMount(){
// this.props.requestLectures(this.props.isLoading);
console.log(this.props.test);
}
render() {
return (
<Table size='large'>
{/*removed to make it cleaner..currently only has static data too lol*/}
</Table>
)
}
}
export default connect(
state => state.lecture,
dispatch => bindActionCreators(actionCreators, dispatch)
)(LectureTable);
where my store is configured:
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import * as Lecture from './Lecture';
import * as Counter from './Counter';
import * as WeatherForecasts from './WeatherForecasts';
export default function configureStore(history, initialState) {
const reducers = {
lecture: Lecture.reducer,
counter: Counter.reducer,
weatherForecasts: WeatherForecasts.reducer
};
const middleware = [
thunk,
routerMiddleware(history)
];
// In development, use the browser's Redux dev tools extension if installed
const enhancers = [];
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment && typeof window !== 'undefined' && window.devToolsExtension) {
enhancers.push(window.devToolsExtension());
}
const rootReducer = combineReducers({
...reducers,
routing: routerReducer
});
return createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers)
);
}
my index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import { createBrowserHistory } from 'history';
import configureStore from './store/configureStore';
import App from './pages/App';
import registerServiceWorker from './registerServiceWorker';
// Create browser history to use in the Redux store
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
const history = createBrowserHistory({ basename: baseUrl });
// Get the application-wide store instance, prepopulating with state from the server where available.
const initialState = window.initialReduxState;
const store = configureStore(history, initialState);
const rootElement = document.getElementById('root');
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
rootElement);
registerServiceWorker();
The first argument to connect() should be a function that returns an object - with the props you want added as keys, and their value being the value from state. e.g.
state => ({ lecture: state.lecture })
I found the solution. First of all I'm a noob both to stackoverflow and to react so I apoligize for all my inconsistencies (if thats the right term?).
What I found out:
I am using react router
I was doing the connect method to a subcomponent of the component being rendered by the router
I placed the connect method to the parent component and it worked
Some notes:
state => state.lecture still works
I will take all of your advices to heart and change my code accordingly
The only reason I was adamant with solving the problem using the code I had was because I couldn't accept the fact that boilerplate code wouldn't work unless I had done something specifically different from what the boilerplate did. I just didn't take into account that the router played a huge role with it.
I repeat...I'm a react noob so I'm sorry for wasting your time T_T
Edit again:
I was able to connect a different child component with the Redux store. I'm trying to look at why I still can't do it for that specific component that caused me to ask this question. I'll update my answer once I find the reason.
I think in their example weatherForecasts is an object. In your example lectures seems to be an array so I suggest to rewrite your mapStateToProps function like this if you only need to get the lectures prop
state => ({ lectures: state.lectures})
if you need the whole state you can have state => state so you can access the props this.props.test and this.props.lectures
Keep in mind that mapStateToProps should return an object, not an array. By the way, in your reducer the field name is lectures (plural) not lecture so state => state.lecture will be undefined
Rick, your connect argument should be something like:
export default connect( state => {
return {
test: state.lecture // Or any value
}
})(LectureTable);
You're trying to console log the test prop, so you should include it in your connect call.
I think by doing the following steps, you can solve the issue:
First you need to call two functions when you want to connect your component to application state, one is mapDispatchToProps and another one is mapStateToProps, for your code to be clean, its better to define these functions separately and then pass them by name to connect, but if you want to use your own way you should do these changes: (assuming your reducer name is lecture from your combineReducers, and assuming you are calling requestLectures with this syntax: this.props.lectureActions.requestLectures() and importing lectureActions from the file you have written lecture related actions) :
export default connect(
state => state.lecture.lectures,
dispatch => {lectureActions: bindActionCreators(lectureActions, dispatch)}
)(LectureTable);
from above code, you do not need to export an object that contains the actions like actionCreators, you should export the requestLectures function out of it independently
add below case to your reducer so that when getting the lectures succeeds the state of the application gets updated with the lectures:
case RECEIVE_LECTURES:
return { ...state, isLoading: false, lectures: payload.lectures };
default:
return state;
}
You have two problems here.
You are defining mapStateToProps function as the first argument to connect wrong. As many of answers explain this now you should use it like, this:
export default connect(
state => ( { lecture: state.lecture } ),
dispatch => bindActionCreators(actionCreators, dispatch)
)(LectureTable);
Now, you have a lecture prop as your state. You can reach it with this.props.lecture. But in your componentWillMount method, you are trying to log it like this.props.test. It should be this.props.lecture.test.
By the way, try to use componentDidMount instead of componentWillMount since it will be deprecated in the future releases.