I'm totally new with Redux and reducers. I can't make my first code work. Can anybody tell me why (index.js) refer to BooksReducer? I have no such a prop, component, or file. And it throws me an error like this:
"Uncaught Error: Reducer "books" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state." How can I initialize it?
reducer_book.js:
export default function(){
return
[
{title: 'Crash bandicoot'},
{title: 'Crash bandicoot 2: Cortex strikes back'},
{title: 'Crash bandicoot 3: Warped'},
{title: 'Crash Team Racing'},
{title: 'Crash Bash'}
]
}
index.js:
const rootReducer = combineReducers({
books: BooksReducer
});
export default rootReducer;
App.js:
export default class App extends Component {
render() {
return (
<div>
<BookList />
</div>
);
}
}
book_list.js:
class BookList extends Component{
renderList(){
return this.props.books.map((book) => {
return(
<li key={book.title} className="list-group-item">{book.title}</li>
);
});
}
render(){
return(
<ul className="list-group col-sm-4">
{this.renderList()}
</ul>
)
}
}
function mapStateToProps(state){
return {
books: state.books
};
}
export default connect(mapStateToProps)(BookList);
It looks like you've got a newline after your return statement in the reducer, so that's probably going to result in Javascript interpreting it as just return;, rather than return [ ];. Try moving the open bracket for the array up to the same line as the return keyword.
Related
I'm trying to render a quiz object from an api and am using useEffect to do so, but it doesn't seem to be working.
Here is my Quiz component:
export function Quiz(props) {
const {
quiz,
fetchQuiz,
} = props;
useEffect(() => {
fetchQuiz();
}, [])
return (
<div id="wrapper">
{ quiz.question }
</div>
)
}
const mapStateToProps = state => {
return {
quiz: state.quiz,
}
}
export default connect(mapStateToProps, {
fetchQuiz,
})(Quiz)
my action creator:
export function fetchQuiz() {
return function (dispatch) {
axios. get('http://localhost:9000/api/quiz/next')
.then(res => {
dispatch({ type: SET_QUIZ_INTO_STATE, payload: res.data })
})
.catch(err => console.error(err))
}
}
my reducer:
const initialQuizState = null
function quiz(state = initialQuizState, action) {
switch(action.type) {
case SET_QUIZ_INTO_STATE:
return action.payload
default:
return state
}
}
So when I console.log "quiz" in my Quiz component, I get the object that I am expecting and it's all good. But when I try and call "quiz.question" I get the error: Cannot read properties of null (reading question). Which leads me to believe useEffect is not loading the data from the api correctly. Am I doing something else wrong?
When accessing an object from an API response in the DOM, make sure you have a conditional to check for completion like so:
return (
<div id="wrapper">
{ quiz ? quiz.question : 'No Quiz Here' }
</div>
)
}
I'm looking at a React-Redux app and try to understand how everything is working.
Inside one of the components, I saw these lines of code:
import { bindActionCreators } from "redux";
...
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchPhotos }, dispatch);
}
export default connect(
null,
mapDispatchToProps
)(SearchBar);
If I change the above code to the following, everything still works, without any errors:
function mapStateToProps(photos) {
return { photos };
}
export default connect(
mapStateToProps,
{ fetchPhotos }
)(SearchBar);
To me, it seems that my way of using connect is easier to understand and it also doesn't need to import an extra library.
Is there any reasons, to import bindActionCreators and use mapDispatchToProps?
I'm a Redux maintainer.
Yes, the second example you showed uses the "object shorthand" form of mapDispatch.
We recommend always using the “object shorthand” form of mapDispatch, unless you have a specific reason to customize the dispatching behavior.
I personally avoid using bindActionCreators explicitly. I prefer to directly dispatch the functions with mapDispatchToProps which internally uses bindActionCreators.
const mapStateToProps = state => ({
photos: state.photos.photos
});
const mapDispatchToProps = dispatch => ({
fetchPhotos: () => dispatch(fetchPhotos())
// ...Other actions from other files
});
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
There are two cases in which you'll use bindActionCreators explicitly, both are not best practices:
If you have a child component to SearchBar that does not connect to redux, but you want to pass down action dispatches as props to it, you can use bindActionCreators.
Best practice would be doing same with example I. You can just pass this.props.fetchPhotos to childcomponent directly without using bindActionCreators.
class SearchBar extends React.Component {
render() {
return (
<React.Fragment>
<ChildComponentOfSearchBar fetchPhotos={this.props.fetchPhotos} />
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
photos: state.photos.photos
});
const mapDispatchToProps = () => bindActionCreators({ fetchPhotos }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
There is another unlikely scenario where you can use bindActionCreators, defining actionCreator inside the component. This isn't maintainable & is not a good solution since action types are hard coded and not reusable.
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.fetchPhotosAction = bindActionCreators({ fetchPhotos: this.searchFunction }, dispatch);
}
searchFunction = (text) => {
return {
type: ‘SEARCH_ACTION’,
text
}
}
render() {
return (
<React.Fragment>
// Importing selectively
<ChildComponentOfSearchBar fetchPhotos={this.fetchPhotosAction} />
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
photos: state.photos.photos
});
export default connect(mapStateToProps, null)(SearchBar)
I am having a TypeError (TypeError: Object(...) is not a function) when I want to dispatch an action. I'm not using any middleware and don't know what I can do to solve it. I had this error already yesterday but somehow managed to solve it (i donk know how i did this)
This is the App.js:
import React from "react";
import { store } from "../store";
import { withdrawMoney} from "../actions";
const App = () => {
return (
<div className="app">
<div className="card">
<div className="card-header">
Welcome to your bank account
</div>
<div className="card-body">
<h1>Hello, {store.getState().name}!</h1>
<ul className="list-group">
<li className="list-group-item">
<h4>Your total amount:</h4>
{store.getState().balance}
</li>
</ul>
<button className="btn btn-primary card-link" data-amount="5000" onClick={dispatchBtnAction}>Withdraw $5,000</button>
<button className="btn btn-primary card-link" data-amount="10000" onClick={dispatchBtnAction}>Witdhraw $10,000</button>
</div>
</div>
</div>
);
}
function dispatchBtnAction(e) {
store.dispatch(withdrawMoney(e.target.dataset.amount));
}
export default App;
Here is the actioncreator:
function withdrawMoney(amount) {
return {
type: "ADD_TODO",
amount
}
}
If you need here is the reducer:
export default (state, action) => {
console.log(action);
return state
}
As you can see I'm a very new to redux but I'd like to know what mistake I make all the time when dispatching an action. Thanks
I believe the issue is that you aren't exporting the withdrawMoney function, so you aren't able to call it in the component that you're attempting to import into.
try:
export function withdrawMoney(amount) {
return {
type: "ADD_TODO",
amount
}
}
Another subtle mistake that will cause this error is what I tried to do, don't accidentally do this:
import React, { useSelector, useState ... } from 'react'
it should be:
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
Try to install :
npm i react#next react-dom#next and run again
const mapStateToProps = state => ({
Bank: state.Bank,
});
function mapDispatchToProps(dispatch) {
return {
dispatch,
...bindActionCreators({ getBanks, addBank }, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(BankComponent);
this worked like a Charm for me .
bit Late to party,
to me it was about casing.
export const addTodo = text => ({
type: ADD_TODO,
desc: text
});
export const removeTodo = id=> ({
type: REMOVE_TODO,
id: id
})
export const increaseCount = () => ({
type: INCREASE_COUNT
});
export const decreaseCount = () => ({
type: DECREASE_COUNT
})
when I renamed all those like
export const AddTodo = text => ({
type: ADD_TODO,
desc: text
});
export const RemoveTodo = id => ({
type: REMOVE_TODO,
id: id
})
export const IncreaseCount = () => ({
type: INCREASE_COUNT
});
export const DecreaseCount = () => ({
type: DECREASE_COUNT
})
it worked.
I spent hours debugging this, turns out I was doing this:
import React, { connect } from "react";
Instead of
import React from "react";
import { connect } from "react-redux";
It's weird the former didn't throw an error, but it will cause the problem!
First problem I see right off the bat is your reducer is not setup to do anything with the dispatched withdrawMoney action creator.
export default (state, action) => {
switch (action.type) {
case "ADD_TODO": {
return {
...state,
amount: action.amount,
};
}
default:
return state;
}
};
If this does not help, the code from your other files would be helpful.
It may not be the main issue, but it looks like you're not using the React-Redux library to work with the store. You can reference the store directly in your React components, but it's a bad practice. See my explanation for why you should be using React-Redux instead of writing "manual" store handling logic.
After updating react-redux to version 7.2.0+ I was receiving this error anytime I wrote:
const dispatch = useDispatch()
I stopped my application and re-ran it with npm start, and everything is working now.
I was using redux toolkit and for me the problem was an extra '}'
such that my 'reducers' object, in 'createSlice' function, already had a closing curly brace before my second reducer
and my 2nd reducer was actually outside the 'reducers' object,
making it not a reducer and hence not working even when you export or import it properly.
So the problem is not in your export or import, but actually where your function is defined.
This may be different for other users, but in my case this turned out to be the cause of this error.
In #ngrx/store 2.0 we could provide the root reducer as a function and from there we split our logic inside the application. After I updated to #ngrx/store 4.0 I cannot use this feature any more from what I can see the reducers need to be a map of reducers which will create objects under the same keys in the state. Is there a way to use the old behavoir in #ngrx/store 4.0 In my state components are aware one of another and I need to be able to split my state dynamically also I need to be able to dispatch actions to the right reducer in my own way. Also app is splitted in multiple lazy loaded routes which in some cases reuse the data from another feature.
StoreModule.provideStore(reducer, {
auth: {
loggedIn: true
}
})
StoreModule.forRoot(reducers, {
initialState: {
auth: {
loggedIn: true
}
}
})
I need reducers to be a function which gets the full state and dispatches it to the correct reducer, Is there a way to achieve this behavior?
After I had a second look over ngrx repo I figured it out. To achieve the wanted result we need to replace the #ngrx/store reducer factory with a new implementation. I injected a new reducer factory and right now the application works as before. Simple code sample on how to replace the reducer factory it.
// This factory replaces #ngrx combine reducers so we can manage how we split the keys inside the state
export function combineReducersFactory(
reducers: any,
initialState: any = {}
): ActionReducer<any, Action> {
return function combination(state = initialState, action) {
const nextState: any = reducers(state, action);
return nextState !== state ? nextState : state;
};
}
export const NG_RX_STORE_PROVIDER = [
StoreModule.forRoot(rootReducer, createEmptyState()),
];
export const NG_RX_REDUCER_FACTORY = [
{
provide: REDUCER_FACTORY,
useFactory: () => combineReducersFactory
}
];
#NgModule({
imports: [
...NG_RX_STORE_PROVIDER
],
declarations: [...APP_COMPONENTS, ...AG_GRID_COMPONENTS],
providers: [...NG_RX_REDUCER_FACTORY]
})
export class AppModule {
}
You can set up a meta reducer to receive every event and manipulate the state from its root. Here is an example way to set it up:
const myInitialState = {
// whatever you want your initial state to be
};
export function myMetaReducer(
reducer: ActionReducer<RootStateType>
): ActionReducer<RootStateType> {
return function(state, action) {
if (iWantToHandleThisAction) {
state = doWhatIWantWith(state);
}
return reducer(state, action);
};
}
#NgModule({
imports: [
StoreModule.forRoot(myInitialState, { metaReducers: [myMetaReducer] })
]
})
export class AppModule {}
The StoreModule forRoot() function accepts a reducerFactory which can be used as follows:
export function myReducerFactory(reducers: any, initState: any) {
return (state = myInitialState, action) => myCustomReducer(state, action);
}
#NgModule({
// ...
imports: [
StoreModule.forRoot(null, { reducerFactory: myReducerFactory })
]
// ...
})
export class AppModule {
}
This works for me:
// your old reducer that handled slicing and dicing the state
export function mainReducer(state = {}, action: Action) {
// ...
return newState;
}
// new: metaReducer that just calls the main reducer
export function metaReducer(reducer: ActionReducer<AppState>): ActionReducer<AppState> {
return function (state, action) {
return MainReducer(state, action);
};
}
// new: MetaReducer for StoreModule.forRoot()
export const metaReducers: MetaReducer<any>[] = [metaReducer];
// modified: app.module.ts
#NgModule({
// ...
imports: [
// neglect first parameter ActionReducerMap, we don't need this
StoreModule.forRoot({}, {
metaReducers: metaReducers,
initialState: INITIAL_STATE // optional
}),
]
})
Newbie here trying to learn some Redux.
GOAL: to get a button to click and login/logout, updating the store as true/false status whichever way.
const store = createStore(myReducer)
Created my store, passing in my reducer.
This has a default state of logged out. And returns the opposite, whenever the button is clicked.
I know this action works through debugging.
function myReducer(state = { isLoggedIn: false }, action) {
switch (action.type) {
case 'TOGGLE':
return {
isLoggedIn: !state.isLoggedIn
}
default:
return state
}
}
The problem starts here - when i try to access the store.getState() data.
class Main extends React.Component {
render() {
return (
<div>
<h1>Login Status: { state.isLoggedIn }</h1>
<button onClick={this.props.login}>Login</button>
</div>
)
}
}
const render = () => {
ReactDOM.render(<Main status={store.getState().isLoggedIn} login={() => store.dispatch({ type: 'TOGGLE' })}/>, document.getElementById('root'));
}
store.subscribe(render);
render();
I've tried store.getState().isLoggedIn & store.getState() & this.props.status and then assigning the store.getState().isLoggedIn in the Main component - but nothing works.
Can anyone tell me where i'm going wrong?
You don't directly access the store using getState to find data. The Redux docs explain the process in-depth, but basically you'll connect each component to the Redux store using connect method of the react-redux package.
Here's an example of how this could work for your above component:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Main from '../components/Main'
class MainContainer extends Component {
render() {
return <Main {...this.props} />
}
}
const mapStateToProps = state => ({
isLoggedIn: state.isLoggedIn,
})
const mapDispatchToProps = dispatch => ({
login() {
dispatch({type: 'TOGGLE'})
},
})
MainContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(MainContainer)
export default MainContainer
You would then want to render the MainContainer in place of the Main component. The container will pass down isLoggedIn and login as props to Main when it renders it.