I was learning React and Redux and while doing that I decided to make webpage with a button which on clicking would change the state. Below the button I wanted to display the current state in a different component. Though the button on clicking changes the state, but it is not getting reflected in the component. Here is my code:
App.js
import React from 'react'
import Name from './Name'
import {changeName} from './Action';
export default function App () {
return (
<div>
<button onClick={changeName}>Click me</button>
<Name />
</div>
)
}
Name.js
import React from 'react'
import {store} from './Store'
function Name(props) {
return (
<div>
My name is: {store.getState()}
</div>
)
}
export default Name
Store.js
import { createStore } from 'redux';
import {reducer} from './Reducer';
export const store = createStore(reducer, 'Tarun');
Action.js
import {store} from './Store';
export const changeName = () => {
if (store.getState() === "Tarun"){
store.dispatch({ type: 'name', payload: 'Subhash' });
}
else{
store.dispatch({ type: 'name', payload: 'Tarun' });
}
}
Reducer.js
export const reducer = function(state, action) {
if (action.type === 'name') {
return action.payload;
}
return state;
};
When I click the button, The text inside the Name component does not change. What is the issue?
You need to set up your reducer and initial store properly following the Redux documentation.
You're missing a Provider, which will provide your store to your application.
const store = createStore(reducer, applyMiddleware(thunk));
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
Now, your store is available to your components.
Your reducer needs an initial state too and you're always supposed to return an updated copy of your state. That said, don't change the state directly, but make a copy, change it, then return that copy.
const initialState = {
name: ""
};
const reducer = function(state = initialState, action) {
if (action.type === "name") {
return { ...state, name: action.payload };
} else {
return state;
}
};
export default reducer;
You might have noticed that I added a middleware to your store, and that's because it's usually the way to go when accessing your current reducer's state in your actions. That said, I installed redux-thunk for that, so in your action, you can have something like this:
export const changeName = () => {
return (dispatch, getState) => {
if (getState().name === "Tarun") {
dispatch({ type: "name", payload: "Subhash" });
} else {
dispatch({ type: "name", payload: "Tarun" });
}
};
};
Now, with your store being provided to your app, your reducer being done and your actions being ready to go, you can connect different components to your reducer.
You use the high order component in react-redux called connect for that. For example, in your Name component, we can connect the name to be displayed to your reducer by mapping your state to the component's props:
function Name(props) {
return <div>My name is: {props.name}</div>;
}
const mapStateToProps = state => {
return {
name: state.name
};
};
export default connect(mapStateToProps)(Name);
The nice thing here is that you can also leave the first parameter in the connect high order component empty and just pass the second, which would be the dispatch functions. Well, that's what you would do in your App component, you would connect it to the changeName action.
function App(props) {
return (
<div>
<button onClick={props.changeName}>Click me</button>
<Name />
</div>
);
}
const mapDispatchToProps = dispatch => {
return {
changeName: () => dispatch(changeName())
};
};
export default connect(
null,
mapDispatchToProps
)(App);
Now, when App dispatches a changeName action, your reducer state will be updated and the other components that are connected to the reducer's state will re-render.
Summary: Try to think of your store as an empty jar of candies. Your jar starts empty, but different actions could change what's inside the jar. On top of that, different people in the house that know where the jar is can go get some candy. Translating to your problem, your app begins with an empty name and you have an action that sets up a name. The components that know where to find that name by being connected to your reducer will know when that name changes and will get the updated name.
The final code can be found here:
The only way your name component will rerender is its props or state change, or if a parent component rerenders. Making a change in redux will not automatically do this. In order to see changes to the state, you'd need to subscribe to those changes. You could do this yourself, but a far better solution is to use react-redux, which is designed for connecting react components to redux stores.
For example, you'd add a provider to your app:
import { Provider } from 'react-redux';
import { store } from './Store'
export default function App () {
return (
<Provider store={store}>
<div>
<button onClick={changeName}>Click me</button>
<Name />
</div>
</Provider>
)
}
And then you'd use connect with your Name component:
import { connect } from 'react-redux';
function Name(props) {
return (
<div>
My name is: {props.name}
</div>
)
}
const mapStateToProps = (state) => {
return { name: state };
}
export default connect(mapStateToProps)(Name)
Related
Heyo this is my first time using react/redux for a project and so I've been migrating my "navigation" to a redux style implementation where I (at the start here) want to change a store state (activePage) on the click of a button, and then my App should render whatever page is active through that activePage state.
I'm stuck (should emphasize that I'm not sure what is overboard/overwriting stuff or missing for this, I've followed a few online tutorials for action/reducer/store type stuff (and I was going to do a dispatch call but it seems like I can call changePage right from the button click instead of dispatching (had problems implementing the dispatch)) and I've been banging my head against the desk as to how action is undefined when import it...perhaps I'm not looking at it correctly...am I missing any data that would help diagnose this error?:
TypeError: Cannot read property 'type' of undefined
allReducers
.../client/src/redux/reducer/index.js:9
6 | activePage: 'HomePage',
7 | }
8 | function allReducers(state = initState, action){
9 | switch(action.type){
10 | case CHANGE_PAGE:
11 | return{
12 | ...state,
loginButton.js
const mapDispatchToProps = (state) => {
return {
changePage : state.activePage
}
};
function LoginButtonThing (){
return(
<div>
<button onClick={() =>(changePage('loginPage'))}>Login Page</button>
</div>
)
}
//export default LoginButtonThing;
export default connect(null,mapDispatchToProps)(LoginButtonThing);
actions.js
import {
CHANGE_PAGE,
} from "../constants/action-types";
export const changePage = (activePage) => ({
type: CHANGE_PAGE,
payload: {
activePage,
},
});
action-types.js
export const CHANGE_PAGE = "CHANGE_PAGE";
reducer/index.js
import {CHANGE_PAGE} from "../constants/action-types";
import {changePage} from "../actions/actions"
const initState = {
activePage: 'HomePage',
}
function allReducers(state = initState, action){
switch(action.type){
case CHANGE_PAGE:
return{
...state,
activePage :action.payload.activePage,
};
}
}
//const store = createStore(rootReducer);
export default allReducers;
App.js
class App extends React.Component {
render() {
//this.props.activePage = 'HomePage';
let renderedPage;
if (this.props.changePage === "LoginPage") renderedPage = <LoginPage/>;
else if (this.props.changePage === "HomePage") renderedPage = <HomePage/>;
else renderedPage = <HomePage/>;
//defaultStatus:activePage = "HomePage";
return (
<div id="App">
{renderedPage}
</div>
);
}
}
index.js
import {createStore, combineReducers} from 'redux';
import allReducers from "./redux/reducer"
//import LoginPage from "./loginPage";
import {Provider} from "react-redux";
const store = createStore(
allReducers,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
ReactDOM.render(
<Provider store = {store}>
<React.StrictMode>
<BrowserRouter>
<Route exact path="/" component = {HomePage}>
<Route path= "/loginPage" component ={LoginPage} />
</Route>
</BrowserRouter>
<App />
</React.StrictMode>
</Provider>,
document.getElementById('root')
);
Missing Default Case
Your reducer needs to return state if an unknown action is dispatched, such as the "init" action which sets your state to the initial value.
function allReducers(state = initState, action) {
switch (action.type) {
case CHANGE_PAGE:
return {
...state,
activePage: action.payload.activePage
};
default:
return state;
}
}
Mixing up State & Dispatch
The connect HOC takes two arguments: mapStateToProps and mapDispatchToProps. I think the names are self-explanatory. So why does your mapDispatchToProps function take state as an argument instead of dispatch?
If using connect, the LoginButtonThing should access a version of changePage which is already bound to dispatch from its props. You can use the action object shorthand like this:
import { connect } from "react-redux";
import { changePage } from "../store/actions";
function LoginButtonThing({ changePage }) {
return (
<div>
<button onClick={() => changePage("loginPage")}>Login Page</button>
</div>
);
}
export default connect(null, { changePage })(LoginButtonThing);
But you should use hooks, like this:
import { useDispatch } from "react-redux";
import { changePage } from "../store/actions";
export default function LoginButtonThing() {
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(changePage("loginPage"))}>Login Page</button>
</div>
);
}
Inconsistent Naming
This doesn't actually navigate to the LoginPage component! That's because LoginButtonThing updated the page to "loginPage" (lowercase), but App is looking for the string "LoginPage" (uppercase).
Don't Do This
There is a lot that's wrong with your code and a lot that's just sort of "dated" and we have better ways now, like hooks and Redux-Toolkit.
I actually think that moving navigation state into Redux like you are trying to do is not a good idea and I would recommend that you stick with react-router. You can read here why you don't need or want to do this.
I have implemented a shopping cart using Redux, I have used a dictionary as state object (product id being the key and quantity in cart being the value). Here is how my cart.js looks like:
import React from 'react';
import ReactDOM from 'react-dom';
export const AddItemToCart = (productID) => {
return {
type: 'ADDITEMTOCART',
productID
}
}
export const DeleteItemFromCart = (productID) => {
return {
type: 'DELETEITEMFROMCART',
productID
}
}
export const Counter = (state = {}, action) => {
switch (action.type) {
case 'ADDITEMTOCART':
console.log(action);
return {
...state,
[action.productID]: ( state[action.productID] || 0 ) + 1
}
case 'DELETEITEMFROMCART':
return {
...state,
[action.productID]: ( state[action.productID] || 1 ) - 1
}
}
}
I'm adding an item from App.js like this:
return products.map(products =>
<div key={products.ProductID}>
<h2>{products.ProductName}</h2>
<h2>{products.ProductDescription}</h2>
<h2>{products.ProductQuantity} units available</h2>
<button onClick={() => { store.subscribe(() => console.log(store.getState()));
store.dispatch(AddItemToCart(products.ProductID));}}>Add to cart</button>
</div>
Everything is working just fine but the problem is, I can't render the contents of the cart for user to see. I have tried:
function ShowCartContents() {
var items = Object.keys(store.getState()).map(function(key){
return store.getState()[key];
});
return (
<div>
<h2>{items}</h2>
</div>
);
}
This function throws exception when called:
TypeError: Cannot convert undefined or null to object
Clearly the store itself is not null or undefined, because the change of state is successfully printed to the browser console. So, how would I access all the values in dictionary without keys? And how would I access one specific value by key? Any advise would be highly appreciated. Thanks.
Your Counter reducer has no default case, so your state will be undefined on the first render.
That's the source of your error "TypeError: Cannot convert undefined or null to object".
You need to return the existing state when neither action matches. Every reducer needs a default case because they will be called with actions like the {type: '##redux/INIT'} action which is used to initialize the store.
default:
return state;
You are trying to access the store directly with store.subscribe(), store.getState() and store.dispatch(). This is not the correct way to interact with a Redux store in React. You should use the react-redux package.
You want to wrap your entire app in a Provider component that provides the store instance. Something like this:
import { render } from "react-dom";
import { Provider } from "react-redux";
import App from "./components/App";
import store from "./store";
const rootElement = document.getElementById("root");
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
In your components, use the hook useSelector to select values from the state and useDispatch to access the dispatch function. (You can also use the connect higher-order component, but the hooks are preferred).
I'm not sure if this reducer is your entire state or if you are using combineReducers and have multiple reducers like cart, products, etc. This selector is assuming that it's the entire state.
function ShowCartContents() {
const productIds = useSelector(state => Object.keys(state))
return (
<div>
<h2>Ids In Cart: {productIds.join(", ")}</h2>
</div>
);
}
function ProductsList({ products }) {
const dispatch = useDispatch();
return (
<div>
{products.map((product) => (
<div key={product.ProductID}>
<h2>{product.ProductName}</h2>
<h2>{product.ProductDescription}</h2>
<h2>{product.ProductQuantity} units available</h2>
<button onClick={() => dispatch(AddItemToCart(product.ProductID))}>
Add to cart
</button>
</div>
))}
</div>
);
}
I use React context with hooks as a state manager for my React app. Every time the value changes in the store, all the components re-render.
Is there any way to prevent React component to re-render?
Store config:
import React, { useReducer } from "react";
import rootReducer from "./reducers/rootReducer";
export const ApiContext = React.createContext();
export const Provider = ({ children }) => {
const [state, dispatch] = useReducer(rootReducer, {});
return (
<ApiContext.Provider value={{ ...state, dispatch }}>
{children}
</ApiContext.Provider>
);
};
An example of a reducer:
import * as types from "./../actionTypes";
const initialState = {
fetchedBooks: null
};
const bookReducer = (state = initialState, action) => {
switch (action.type) {
case types.GET_BOOKS:
return { ...state, fetchedBooks: action.payload };
default:
return state;
}
};
export default bookReducer;
Root reducer, that can combine as many reducers, as possible:
import userReducer from "./userReducer";
import bookReducer from "./bookReducer";
const rootReducer = ({ users, books }, action) => ({
users: userReducer(users, action),
books: bookReducer(books, action)
});
An example of an action:
import * as types from "../actionTypes";
export const getBooks = async dispatch => {
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1", {
method: "GET"
});
const payload = await response.json();
dispatch({
type: types.GET_BOOKS,
payload
});
};
export default rootReducer;
And here's the book component:
import React, { useContext, useEffect } from "react";
import { ApiContext } from "../../store/StoreProvider";
import { getBooks } from "../../store/actions/bookActions";
const Books = () => {
const { dispatch, books } = useContext(ApiContext);
const contextValue = useContext(ApiContext);
useEffect(() => {
setTimeout(() => {
getBooks(dispatch);
}, 1000);
}, [dispatch]);
console.log(contextValue);
return (
<ApiContext.Consumer>
{value =>
value.books ? (
<div>
{value.books &&
value.books.fetchedBooks &&
value.books.fetchedBooks.title}
</div>
) : (
<div>Loading...</div>
)
}
</ApiContext.Consumer>
);
};
export default Books;
When the value changes in Books component, another my component Users re-renders:
import React, { useContext, useEffect } from "react";
import { ApiContext } from "../../store/StoreProvider";
import { getUsers } from "../../store/actions/userActions";
const Users = () => {
const { dispatch, users } = useContext(ApiContext);
const contextValue = useContext(ApiContext);
useEffect(() => {
getUsers(true, dispatch);
}, [dispatch]);
console.log(contextValue, "Value from store");
return <div>Users</div>;
};
export default Users;
What's the best way to optimize context re-renders? Thanks in advance!
Books and Users currently re-render on every cycle - not only in case of store value changes.
1. Prop and state changes
React re-renders the whole sub component tree starting with the component as root, where a change in props or state has happened. You change parent state by getUsers, so Books and Users re-render.
const App = () => {
const [state, dispatch] = React.useReducer(
state => ({
count: state.count + 1
}),
{ count: 0 }
);
return (
<div>
<Child />
<button onClick={dispatch}>Increment</button>
<p>
Click the button! Child will be re-rendered on every state change, while
not receiving any props (see console.log).
</p>
</div>
);
}
const Child = () => {
console.log("render Child");
return "Hello Child ";
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
Optimization technique
Use React.memo to prevent a re-render of a comp, if its own props haven't actually changed.
// prevents Child re-render, when the button in above snippet is clicked
const Child = React.memo(() => {
return "Hello Child ";
});
// equivalent to `PureComponent` or custom `shouldComponentUpdate` of class comps
Important: React.memo only checks prop changes (useContext value changes trigger re-render)!
2. Context changes
All context consumers (useContext) are automatically re-rendered, when the context value changes.
// here object reference is always a new object literal = re-render every cycle
<ApiContext.Provider value={{ ...state, dispatch }}>
{children}
</ApiContext.Provider>
Optimization technique
Make sure to have stable object references for the context value, e.g. by useMemo Hook.
const [state, dispatch] = useReducer(rootReducer, {});
const store = React.useMemo(() => ({ state, dispatch }), [state])
<ApiContext.Provider value={store}>
{children}
</ApiContext.Provider>
Other
Not sure, why you put all these constructs together in Books, just use one useContext:
const { dispatch, books } = useContext(ApiContext);
// drop these
const contextValue = useContext(ApiContext);
<ApiContext.Consumer> /* ... */ </ApiContext.Consumer>;
You also can have a look at this code example using both React.memo and useContext.
I believe what is happening here is expected behavior. The reason it renders twice is because you are automatically grabbing a new book/user when you visit the book or user page respectively.
This happens because the page loads, then useEffect kicks off and grabs a book or user, then the page needs to re-render in order to put the newly grabbed book or user into the DOM.
I have modified your CodePen in order to show that this is the case.. If you disable 'autoload' on the book or user page (I added a button for this), then browse off that page, then browse back to that page, you will see it only renders once.
I have also added a button which allows you to grab a new book or user on demand... this is to show how only the page which you are on gets re-rendered.
All in all, this is expected behavior, to my knowledge.
I tried to explain with different example hope that will help.
Because context uses reference identity to determine when to re-render, that could trigger unintentional renders in consumers when a provider’s parent re-renders.
for example: code below will re-render all consumers every time the Provider re-renders because a new object is always created for value
class App extends React.Component {
render() {
return (
<Provider value={{something: 'something'}}>
<Toolbar />
</Provider>
);
}
}
To get around this, lift the value into the parent’s state
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
This solution is used to prevent a component from rendering in React is called shouldComponentUpdate. It is a lifecycle method which is available on React class components. Instead of having Square as a functional stateless component as before:
const Square = ({ number }) => <Item>{number * number}</Item>;
You can use a class component with a componentShouldUpdate method:
class Square extends Component {
shouldComponentUpdate(nextProps, nextState) {
...
}
render() {
return <Item>{this.props.number * this.props.number}</Item>;
}
}
As you can see, the shouldComponentUpdate class method has access to the next props and state before running the re-rendering of a component. That’s where you can decide to prevent the re-render by returning false from this method. If you return true, the component re-renders.
class Square extends Component {
shouldComponentUpdate(nextProps, nextState) {
if (this.props.number === nextProps.number) {
return false;
} else {
return true;
}
}
render() {
return <Item>{this.props.number * this.props.number}</Item>;
}
}
In this case, if the incoming number prop didn’t change, the component should not update. Try it yourself by adding console logs again to your components. The Square component shouldn’t rerender when the perspective changes. That’s a huge performance boost for your React application because all your child components don’t rerender with every rerender of their parent component. Finally, it’s up to you to prevent a rerender of a component.
Understanding this componentShouldUpdate method will surely help you out!
Solution(updated):
I thought any action would cause react-redux-connect to call the mapState functions but when an action doesn't change anything then this is not the case.
I have a localStorage module that dispatches actions but don't change state, instead thy will write to localStorage. The module has selectors that are used in the containers but they won't get called until the state actually changes so the UI would only show correctly after another action was dispatched that would change the state.
Problem
When I put the store on window (window.store=store), add a console.log in the mapStateToProps, then in the console I dispatch an action: store.dispatch({type:'some action'}) then the console.log of the mapStateToProps does not show.
I do memoize the result but the mapStateToProps should be called see here
Full code is here and running example here (you can open a console clicking on 'console' link in the right bottom of the screen).
package.json
store.js:
import { createStore } from 'redux';
export default (initialState, reducer) => {
const store = createStore(
reducer,
initialState,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
window.store = store;
return store;
};
app.js
import React from 'react';
import { connect } from 'react-redux';
import './App.css';
import createStore from './store';
import { Provider } from 'react-redux';
import initCounter from './components/Counter';
import {
createWrapper,
memoize,
} from './components/#common';
const COUNTER = 'COUNTER';
const selectCounterState = state => state.myCounter;
const counter = initCounter({
actionWrapper: createWrapper(COUNTER, 'counter1'),
selectors: { myState: selectCounterState },
connect,
memoize,
});
const initialState = {
myCounter: counter.initialState,
};
const reducer = (state = initialState, action) => {
if (action.emittedBy === COUNTER) {
return {
...state,
myCounter: counter.reducer(
selectCounterState(state),
action.payload
),
};
}
return state;
};
const store = createStore(initialState, reducer);
const Counter = counter.container;
const App = () => (
<Provider store={store}>
<Counter id="counter1" parentId={[]} />
</Provider>
);
export default App;
component/Counter/index:
import component from './component';
const INCREASE = 'INCREASE';
const reducer = (state, action) => {
if (action.type === INCREASE) {
return { ...state, count: state.count + 1 };
}
return state;
};
const makeState = memoize =>
memoize((id, parentId, { count }) => ({
id: parentId.concat(id),
parentId,
count,
}));
const mapStateToProps = ({ myState }, memoize) => () => {
const newState = makeState(memoize);
return (state, ownProps) =>
console.log('in map state to props', new Date()) ||
newState(
ownProps.id,
ownProps.parentId,
myState(state)
);
};
export default ({
actionWrapper,
selectors,
connect,
memoize,
}) => {
const actions = {
increase: ({ id }) =>
actionWrapper({
type: INCREASE,
id,
}),
};
const container = connect(
mapStateToProps(selectors, memoize),
actions
)(component);
return {
container,
reducer,
initialState: { count: 0 },
};
};
components/counter/component.js:
import React from 'react';
export default props => (
<div>
<button onClick={() => props.increase(props)}>
add
</button>
{props.count}
</div>
);
This problem was caused because I had a localStorage module that did dispatch actions but did not change the state, instead it would write to localStorage.
The module had selectors that would get the right data and the containers would use them to build the correct state but since the dispatched action did not change the state in the redux store react-redux would skip calling my mapState functions (probably memoizing state in Provider).
The solution is to let the root reducer return a new state reference {...state} so any action would cause the mapState functions to be called.
Your example codepen works just fine, you just have to trigger an action that gets past your top level guard and is of the expected structure, as to not cause any followup errors:
Post this into the console of your codepen:
store.dispatch({emittedBy: "COUNTER", type: "COUNTER -> INCREASE", id: "counter1", payload: {type: "INCREASE", id: ["counter1"]}})
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.