state.getStore() or mapStateToProps - React.js / Redux - javascript

So I'm building my first Redux project to learn it. I just got it to output updated state in the middleware logger so that was great.
Now I am trying to have the component update based on the updated state.
Obviously I'm missing some fundamental concept here...I think I'm supposed to mapStateToProps but I just don't understand it.
Isn't there some simple global thing I can use?
How come when I console out store.getState().quote it works but doesn't update the component?
QUOTE COMPONENT
import React from 'react';
import { connect } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise';
import createLogger from 'redux-logger';
import quoteReducer from './reducers/quoteReducer';
const logger = createLogger();
const store = createStore(
quoteReducer,
applyMiddleware(thunk, promise, logger)
);
class quote extends React.Component {
componentWillMount() {
let req = new XMLHttpRequest();
req.responseType = "json";
req.onload = function(){
store.dispatch({type: "ADD_QUOTE", text: req.response.quote, movie: req.response.author});
}.bind(this)
req.open("GET", "http://localhost:3001/quote");
req.send();
}
render(){
return (
<div id="quote">
<h1> "{store.getState().quote}" </h1>
<h2> -{store.getState().author} </h2>
</div>
)
}
}
export default connect()(quote);
REDUCER
const quoteReducer = (state = {quote: "", author: ""}, action) => {
switch (action.type) {
case 'ADD_QUOTE':
return {
quote: action.text,
author: action.movie
}
default:
return state
}
}
export default quoteReducer;
ACTION
export const addQuote = (text, movie) => {
return {
type: "ADD_QUOTE",
text,
movie
}
}
APP.JS
import React, { Component } from 'react';
import Quote from './quote';
import AnswerInput from './answerInput';
import logo from './logo.svg';
import './App.css';
import './quote.css';
import './answerInput.css';
import './assets/styles/global.css';
class App extends Component {
render() {
return (
<div className="App">
<Quote />
<AnswerInput />
</div>
);
}
}
export default App;
INDEX.JS
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import quoteReducer from './reducers/quoteReducer.js';
import './index.css';
let store = createStore(quoteReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);

The answer from #Konstantin Vitkovsky is sort of right, but not exactly.
There's a few problems with your original example:
First, your quote component should have a capitalized name, like Quote. React and JSX use capitalization to know whether you're trying to render a component or an individual tag. First letter uppercase means it's a component, first letter lowercase means it must be a tag.
Second, your component is currently accessing the store directly, and calling store.getState() in its render method. This will work the first time, but because your component has no idea when the store updates, it will not trigger a re-render. (Also, in React, ideally a render() method should only render things based on this.props and this.state.)
Third, you are calling the React-Redux connect method at the end of your file, but you're not actually using it in any way.
Finally, you definitely shouldn't refer to the store directly in the component. You should render a <Provider store={store}> component at the top of your component tree, which will make the store accessible to any connected component inside of it.
As #Konstantin Vitkovsky said, your component will not know that the store has been updated unless your component subscribes to the store. However, that is exactly what connect already does for you. Every time the store updates, the wrapper component generated by connect will re-run the mapStateToProps function you gave it, and pass the data that you return into your "real" component.
So, for your example, your code should look roughly like this:
import React, {Component} from "react";
import ReactDOM from "react-dom";
import {Provider, connect} from "react-redux";
const store = createStore(rootReducer);
// Normally the store definition and the component would be
// defined in different files
class Quote extends Component {
render() {
const {quote, author} = this.props;
return (
<div id="quote">
<h1>{quote}</h1>
<h2>{author}</h2>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
quote : state.quote,
author : state.author
};
}
const ConnectedQuote = connect(mapStateToProps)(Quote);
ReactDOM.render(
<Provider store={store}
<Quote />
</Provider>,
document.getElementById("root")
);
I'd encourage you to read through the Redux docs thoroughly - they contain a lot of good information. Also, I keep a big list of links to high-quality tutorials and articles on React, Redux, and related topics, at https://github.com/markerikson/react-redux-links . It's specifically intended to be a great starting point for anyone trying to learn the ecosystem.

I ended up getting some help from a contact and this is the code that worked!
import React from 'react';
import { connect } from 'react-redux';
import { addQuote } from './actions';
const mapStateToProps = (state) => {
const { quote } = state;
return quote; //This would be the job for selectors (from advanced redux tutorial)
}
const mapDispatchToProps = (dispatch) => ({
addQuote(req) {
const { quote, author } = req.response;
dispatch(addQuote(quote, author)); // Use the action creator
}
})
class Quote extends React.Component {
componentWillMount() {
let req = new XMLHttpRequest();
req.responseType = "json";
req.onload = function(){
this.props.addQuote(req);
}.bind(this)
req.open("GET", "http://localhost:3001/quote");
req.send();
// the whole bit above is best replaced with a redux-fetch middleware
}
render(){
const {quote, author} = this.props;
return (
<div id="quote">
<h1> "{quote}" </h1>
<h2> -{author} </h2>
</div>
)
}
}
const ConnectedQuote = connect(mapStateToProps, mapDispatchToProps)(Quote);
export default ConnectedQuote;

Related

React presentational component unable to read value for <input /> from redux store using react container

I'm new to stackoverflow and quite new to using react/redux. I've been scanning over quite a few posts already to see if a similar post could provide me with an answer but I'm still left puzzled.
I currently have a presentational component "Repetitions" and a container component to get props from redux store and dispatch actions from the presentational component to redux store. I have the presentational component updating the redux store when I enter data into the input field but I am wanting to use the redux store to retrieve the input value so that when a user first comes on to the page the input value is "0" as that is the initial value inside the redux store.
I originally made a simple Counter component using react/redux and it was working ok. I have since made the "Repetition" component and altered the redux store to use a combinedreducer and this is when the problems seemed to start as neither components can read from the redux store.
Rootreducer.ts
import { combineReducers } from "redux";
import countReducer from "./example/reducer";
import repetitionsReducer from "./reps/reducer";
const rootReducer = combineReducers({
countReducer,
repetitionsReducer
})
export default rootReducer;
RepetitionsReducer.ts
import { RepetitionsState } from "../types";
import { AddRepetitionsAction } from "./actions";
export type RepetitionsActionType = AddRepetitionsAction;
export type Dispatch = (action: RepetitionsActionType) => void;
// The reducer updates the count
const initialState: RepetitionsState = {
repetitions: 0
};
const repetitionsReducer = (
state = initialState,
action: RepetitionsActionType
): RepetitionsState => {
switch (action.type) {
case "ADD_REPETITIONS":
return { ...state, repetitions: action.repetitions };
default:
return state;
}
}
export default repetitionsReducer;
RepetitionsContainer.ts
import { connect } from "react-redux";
import { RootState } from "../../store/types";
import { Dispatch } from "../../store/reps/reducer";
import { addRepetitions } from "../../store/reps/actions";
import Repetitions from "../../components/reps/Repetitions";
interface StateFromProps {
repetitions: number ;
}
interface DispatchFromProps {
updateRepetitions: (repetitions: number) => void;
}
export type RepetitionsProps = StateFromProps & DispatchFromProps;
const mapStateToProps = (state: RootState): StateFromProps => ({
repetitions: state.repetitions
});
const mapDispatchToProps = (dispatch: Dispatch): DispatchFromProps => ({
updateRepetitions: (repetitions: number) => dispatch(addRepetitions(repetitions))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Repetitions);
RepetitionsComponent.ts
note: When I try to console.log "repetitions" I am getting undefined at the moment.
import React from "react";
import { RepetitionsProps } from "../../containers/reps/Repetitions";
const Repetitions: React.FunctionComponent<RepetitionsProps> = ({
repetitions,
updateRepetitions
}) => {
console.log(repetitions)
return (
<div>
<h3>Reps</h3>
<input
onChange={(event) => updateRepetitions(Number(event.target.value))}
value={ repetitions } // <-- This is the value i'm wanting to present to the user from the redux store
/>
</div>
);
};
export default Repetitions;
App.ts
import React from "react";
import ReactDOM from "react-dom";
import * as serviceWorker from "./serviceWorker";
import Header from "./components/header/Header";
import { Provider } from "react-redux";
import { createStore } from "redux";
import Counter from "./containers/example/Counter";
import Repetitions from "./containers/reps/Repetitions";
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from "./store/reducer";
const store = createStore(rootReducer, composeWithDevTools());
console.log(store.getState())
function App() {
return (
<div className="App">
<Header title={"Rep count"} />
<Repetitions />
<br />
<br />
<br />
<Counter />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
The expected results I would be hoping to see would be a "0" presented in the input box underneath the "Reps" header when a user first loads the page. Instead the box is empty but the redux store shows the value for repetitions as "0".
reps-input-desired-results
It is also worth noting that the counter below the input field used to read "0" from the redux store when I first loaded the page however now it is also undefined.
Thank you for taking the time to look at my post. Any help would be greatly appreciated!
Hmmm... something is wrong here:
First of all, your state for repetition is currently holding an object. It should hold a simple number.
Secondly, the name of the repetition state on the store (from the snapshot you've attached) is "repetitionReducer" and not "repetition" as you try to fetch it in mapStateToProps:
const mapStateToProps = (state: RootState): StateFromProps => ({
repetitions: state.repetitions // <- this one here...
});
Hope this helps :)

Warning: Did not expect server HTML to contain a <li> in <ul>. in React/Redux Server side Rendering

I am doing Server Side Rendering for the first time with React and Redux and seeming to be having a little difficulty. I am getting the warning:
Warning: Did not expect server HTML to contain a <li> in <ul>.
I have looked this up and it means that there is a html tree mismatch. I am not sure how that is. Is there an obvious way to fix it? Here is the code I have that is throwing the warning.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import actions from '../actions';
class UsersList extends Component {
componentDidMount() {
if (this.props.users.length > 0) {
return;
}
this.props.fetchUsers();
}
render() {
const { users } = this.props;
return (
<div>
<ul>
{users.map(user => {
return <li key={user.id}>{user.name}</li>;
})}
</ul>
</div>
);
}
}
const stateToProps = state => {
return {
users: state.users
};
};
const dispatchToProps = dispatch => {
return {
fetchUsers: () => dispatch(actions.fetchUsers())
};
};
const loadData = store => {
return store.dispatch(actions.fetchUsers());
};
export { loadData };
export default connect(stateToProps, dispatchToProps)(UsersList);
Here is the client.js:
// Startup point for the client side application
import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import Routes from './Routes';
import reducers from './reducers';
const store = createStore(reducers, {}, applyMiddleware(thunk));
ReactDOM.hydrate(
<Provider store={store}>
<BrowserRouter>
<div>{renderRoutes(Routes)}</div>
</BrowserRouter>
</Provider>,
document.querySelector('#root')
);
The problem was that since the server had rendered the list of users already, it had a ul with lis. But when the client loaded there was no initial data so there was only an ul and no lis to accompany it.
To fix this problem, which I think not many will have because you need to do this anyway in server side rendering is to pass some initial state into the createStore redux function:
You will need to do this in two places. In the html on your server side and in your entry point on the client side.
Short example could be:
<html>
<head></head>
<body>
<div id="root">${content}</div>
<script>
window.INITIAL_STATE = ${serialize(store.getState())}
</script>
<script src="bundle.js"></script>
</body>
</html>
As you can see we have to set the initial state to the store that you created on your server side.
On your client side:
// Startup point for the client side application
import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import Routes from './Routes';
import reducers from './reducers';
const store = createStore(reducers, window.INITIAL_STATE, applyMiddleware(thunk));
ReactDOM.hydrate(
<Provider store={store}>
<BrowserRouter>
<div>{renderRoutes(Routes)}</div>
</BrowserRouter>
</Provider>,
document.querySelector('#root')
);
Now you have access to this global variable window.INITIAL_STATE and you just pass that through into the second argument of createStore which is the initial state.
I'm posting here now because I was searching this exact issue with nextJS SSG. Just like Dave Newton mentioned, in the jsx return I would simply add a check and not render the <ul> if it is empty due to a conditional check on the <li>'s. Something like this:
{Object.keys(el[i]).length > 0 ?
<ul>
{getKeys(docs, item, el).map(
([key, value], i) => {
const name = `${docs[item][el][key].name}`
const path = `${docs[item][el][key].path}`
return (name !== "undefined") && (
<li key={i}><a href={path}>{name}</a></li>
)}
)}
</ul>
: ''
}

Print value from props, which is delivered to the component from redux by mapStateToProps

Problem:
I can't display the value from the state of redux, which is delivered by mapStateToProps function to the component.
Project structure:
Create-react-app CLi application built the project.
Inside of the src/ I have the following code structure
Necessary code:
The main page which we are interacting with looks like this:
Underneath it is planned to post the result of the clicking on the buttons.
So how do I bind the redux state and actions to those two components: Calculator and ResultLine?
Let me show the index.js code, where I create the store:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducers from './reducers/';
import App from './components/App';
ReactDOM.render(
<Provider store={createStore(reducers)}>
<App />
</Provider>,
document.getElementById("root")
);
There are only three actions:
import {CALCULATE, ERASE, PUT_SYMBOL} from "./types";
export const putSymbol = (symbol) => {
return {
type: PUT_SYMBOL,
payload: symbol
}
};
export const calculate = () => {
return {
type: CALCULATE
}
};
export const erase = () => {
return {
type: ERASE
}
};
And in the App.js I pass reducers, which are binded to those actions to the Calculator component:
import React, {Component} from 'react';
import Calculator from './Calculator';
import ResultLine from "./ResultLine";
import {calculate, erase, putSymbol} from "../actions/index";
import {connect} from "react-redux";
class App extends Component {
render() {
return (
<div>
<Calculator
onSymbolClick={this.props.onSymbolClick}
onEqualsClick={this.props.onEqualsClick}
onEraseClick={this.props.onEraseClick}/>
<br/>
<ResultLine result={this.props.result}/>
</div>
);
}
}
const mapStateToProps = (state) => {
console.log('mapState', state.calc.line);
return {
result: state.line
}
};
const mapDispatchToProps = {
onSymbolClick: putSymbol,
onEqualsClick: calculate,
onEraseClick: erase
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
And that works fine. Whenever I click the button the state changes, and I observe it in the console log, called in mapStateToProps function.
So I expect, that I can deliver result prop to the Result line easily, and I pass it into the ResultLine component as a parameter. So, let's look at that element:
import React from 'react';
const ResultLine = ({result}) => {
return (
<p>{result}</p>
);
};
export default ResultLine;
And I can see no changes in a result line. Maybe, something wrong with the React/Redux lifecycle management and ResultLine component just does not update on changes in state?
There's an error on mapStateToProps.
Instead of:
const mapStateToProps = (state) => {
return {
result: state.line
}
}
Please use:
const mapStateToProps = (state) => {
return {
result: state.calc.line // calc was missing here
}
}

Immutable state not being loaded in redux-persist-immutable

I'm having an issue with redux-persist-immutable not rehydrating my store, and I'm not sure why it's happening. I'm using the "Delay Render Until Rehydration Complete" recipe from the base redux-persist docs. My exact code is here.
It looks like my app loads properly and everything is rendered properly, but my store.getState() returns the initialState I started with - immutable's Map() - instead of any of the values I worked with in the previous session. Am I missing something? I've added the relevant code below as well.
import React from 'react';
import { Component } from 'react';
import ReactDOM from 'react-dom';
import {compose, createStore} from 'redux';
import {getStoredState, persistStore, autoRehydrate, createPersistor} from 'redux-persist-immutable';
import {Provider} from 'react-redux';
import reducer from './reducer';
import WinTracker from './components/WinTracker';
// hotmodule reloading fix
if (module.hot) {
module.hot.accept();
}
require('./normalize.css');
require('./skeleton.css');
require('./style.css');
const store = createStore(reducer, undefined, autoRehydrate());
class AppProvider extends Component {
constructor() {
super()
this.state = { rehydrated: false }
}
componentWillMount() {
persistStore(store, {}, () => {
this.setState({ rehydrated: true })
})
}
render() {
if(!this.state.rehydrated){
return <div>Loading...</div>
}
return (
<Provider store={store}>
<WinTracker />
</Provider>
)
}
}
ReactDOM.render(
<AppProvider />,
document.getElementById('app')
);
Figures I'd figure out the answer mere minutes after posting the question.
Looks like the initialState needs to be set with the keys you'll be working with later. If your initialState is an empty Map() that then gets updated, you'll be SOL.

React + Redux: Component does not update

Trying out React + Redux, and probably am doing something obviously stupid, because a component that fires an action to fetch data over the network does not get updated (re-rendered) when the data is fetched.
Here are the relevant bits of my code:
The top-level index.js serving as an entry point for the app:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, browserHistory } from 'react-router';
import reduxPromise from 'redux-promise';
import createLogger from 'redux-logger';
const logger = createLogger();
import routes from './routes';
import reducers from './reducers';
const createStoreWithMiddleware = applyMiddleware(reduxPromise, logger)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<Router history={browserHistory} routes={routes} />
</Provider>
, document.querySelector('.container'));
Top-level container App:
import React, {Component} from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions';
import Header from '../components/header';
import Showcase from '../components/showcase';
function mapStateToProps(state) {
return {
resources: state.resources
}
}
function mapDispatchToProps(dispatch) {
return {
fetchResources: () => {
dispatch(Actions.fetchResources());
}
}
}
class App extends Component {
render() {
console.log('props in App', this.props);
return (
<div>
<Header/>
<Showcase
fetchResources={this.props.fetchResources}
resources={this.props.resources}
/>
</div>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
Component that triggers an action to sends a request for data when it is about to mount and is supposed to show the fetched data:
import React, {Component} from 'react';
import {connect} from 'react-redux';
class Showcase extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchResources();
}
render() {
console.log('resources', this.props);
return (
<div>
This is showcase
</div>
);
}
}
export default connect(state => ({resources: state.resources}))(Showcase)
Action Creator:
import * as types from '../constants/ActionTypes';
import axios from 'axios';
export function fetchResources() {
return {
type: types.FETCH_FIRST,
payload: axios.get('/sampledata/1.json')
}
}
Reducer for the fetch action:
import * as types from '../constants/ActionTypes';
export default function resourcesReducer (state={}, action) {
switch (action.type) {
case types.FETCH_FIRST:
console.log('about to return', Object.assign (state, {resources: action.payload.data }))
return Object.assign (state, {resources: action.payload.data });
default:
return state
}
};
and finally the root reducer:
import { combineReducers } from 'redux';
import navigationReducer from './navigation-reducer';
import resourcesReducer from './resources-reducer';
const rootReducer = combineReducers({
navigationReducer,
resourcesReducer
});
export default rootReducer;
So, here is what I am observing. The action to request data is successfully triggered, a request is sent, the reducer receives it when the promise is resolved, and updates the state with the fetched data. At this point, I would expect the top-level App component and the Showcase component to detect that the store has updated, and to re-render, but I do not see it in the console.
Also, I am confused by redux-logger’s console output:
Specifically, I am surprized to see that the state contains reducers from the rootReducer — I don't know if it's right (an example on Redux logger Github page shows a state without reducers). It also seems surprising that the prev state as reported by redux-logger contains the same resourcesReducer object as the next state, although intuitively I would expect prev state to be more or less empty.
Could you please point out what I am doing wrong and how to get React components respond to the state changes?
==================================================
UPDATED:
1) Changed the mapStateToProps function in the App component so that it correctly maps to reducer states:
function mapStateToProps(state) {
return {
resources: state.resourcesReducer
}
}
2) Still passing the resources down to the `Showcase component:
render() {
console.log('props in App', this.props);
return (
<div>
<Header navigateActions={this.props.navigateActions}/>
React simple starter
<Showcase
fetchResources={this.props.fetchResources}
resources={this.props.resources}
/>
</div>
);
3) Trying to display resources on the screen by stringifying it to see what’s actually inside this object:
render() {
console.log('resources', this.props);
return (
<div>
This is showcase {JSON.stringify(this.props.resources)}
</div>
);
}
See this on the screen: This is showcase {}. The component does not seem to re-render.
Here’s the screenshot of the console showing that App’s props have updated with the values from the next state. Still, that did not cause the component to re-render:
UPDATED AGAIN: And my javascript-fu was poor, too. I did not quite realize that by returning Object.assign (state, {resources: action.payload.data }); I was in fact mutating the state, and that a simple inversion of arguments would let me achieve what I intended. Thanks to this discussion on SO for enlightenment.
I am surprized to see that the state contains reducers from the rootReducer
This is how it works. Take a closer look at combineReducers().
const rootReducer = combineReducers({
navigationReducer,
resourcesReducer
});
Recognise that it's not a list of parameters; it's a single object parameter. Perhaps it is clearer in verbose syntax:
var rootReducer = combineReducers({
navigationReducer: navigationReducer,
resourcesReducer: resourcesReducer
});
The resourcesReducer key points to the state returned by the resourcesReducer() function. That is, the state variable within the resourcesReducer() is just one part of the entire state.
The functions passed to connect() take the entire state as an argument. What yours should actually look like is this:
export default connect(state => ({
resources: state.resourcesReducer.resources
}))(Showcase);

Categories