Redux silently not rendering connect()ed component - reactjs

I am writing an app using Redux and can't get a Redux connect()ed component to render at all.
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';
var store = createStore((s, a) => s, {hello: "WORLD"});
class App extends React.Component {
render() { return <h1> Hello, world! </h1>; }
}
var connectedApp = connect(function(s) { debugger })(App);
$(document).ready(function() {
var target = document.getElementById("root");
// DOES render
React.render(<App/>, target);
// Never renders
React.render(<connectedApp/>, target);
});
The app is using babel, babelify, redux and redux-react.
Returning an object inside of connect() did not seem to modify this.props in the component, either. The debugger statement passed to connect never fires.
Is there something wrong with this code? Why isn't the component rendering? Why does the debugger statement never fire?

JSX converts component types that start with a capital letter into React.createElement calls to that type:
<App/> // => React.createElement(App);
However, it converts component types that start with lowercase letters into DOM nodes (by passing it as a string instead of a reference):
<connectedApp/> // => React.createElement("connectedApp");
In fact, if you look at the DOM via your browser's inspector, you'll likely see
<connectedApp data-reactid=".0"></connectedApp>
Try capitalizing connectedApp:
var ConnectedApp = connect(...)(App);
// ...
React.render(<ConnectedApp/>, target);

You are not passing a valid ReactElement to your second render method.
The first <App/> component is valid an therefor is being rendered as a DOM node.
The second <connectedApp/> however is not a ReactElement. So it won't be rendered at all. It is just a function. var connectedApp = connect(function(s) { debugger })(App);
Taken from the API from REDUX the typical use of the connect function is as follows:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(App)
with the arguments:
[mapStateToProps(state, [ownProps]): stateProps] (Function)
The App component subscribes to the redux store updates, and this function is always called if the component updates. The return of this function should be a object.
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function)
As object, every function inside will be recognized as a valid action creator
You don't need to pass the connect to a render method, just subscribe your App to the REDUX store.
So taken from the official REDUX page, this is how you set up the subscription:
import { React } from 'react'
import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actionCreators, dispatch) }
}
class TodoApp extends React.Component {
//your App
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)`

Related

Does Redux update the React component it is connected to automatically?

I was under the impression that when my Redux store gets updated (via dispatching an action to the reducer), my Provider should make the new store available to all it's child components. So when I connect a container component to the store by using mapStateToProps(), that component should re-render when it receives the new props that go along with the store being updated, without the need for componentWillReceiveProps(). Am I wrong in thinking that?
After several hours of reading docs and other stack overflow answers, something just isn't clicking for me. I'm pretty sure my reducer is working correctly and is returning a new object, my components have access to the store, and my initial state is rendering just fine. If anyone could give me a better idea about the flow of things, I would be forever grateful.
Basically, I have a header component that imports the store, uses Provider and renders a "FAQ" component:
import React from 'react';
import FAQ from './Faq';
import {Provider} from "react-redux";
import store from '../store/index'
class Header extends React.Component {
render() {
return (
<Provider store = {store}>
<FAQ />
</Provider>
)
}
}
export default Header;
The "FAQ" component is a container that is connected to the store via mapStateToProps(), it imports the "FAQTest" component, which is a presentational component that will get passed this.state.thisFood as props so that it can render the data from the store. There is also an "addFood" function that dispatches my action, which can be seen in mapDispatchToProps() at the bottom.
import React from 'react';
import {connect} from 'react-redux';
import FAQTest from './faqTest';
class FAQ extends React.Component {
constructor(props) {
super(props);
this.state = {
thisFood: props.breakfast
};
}
//adding this makes the component state update, but I think it should work without it
componentWillReceiveProps(nextProps){
this.setState({thisFood: nextProps.breakfast})
}
addFood = () => {
this.props.addFood();
}
render() {
return (
<React.Fragment>
<button onClick={this.addFood}> Add Food </button>
<FAQTest food = {this.state.thisFood} />
</React.Fragment>
)
}
}
const mapStateToProps = function(state) {
return {
breakfast: state.faq.breakfast
}
}
function mapDispatchToProps(dispatch) {
return {
addFood: () => dispatch({type: 'ADD_FOOD', food: 'Waffles'})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(FAQ);
When I click the "Add Food" button, my store gets updated, and the props of my FAQ container component get updated, because of mapStateToProps(). I thought this would trigger my FAQ component to update its state, however the state does not get updated unless I use componentWillReceiveProps. Is this working as expected?
Just in case I'm doing something silly, here is my reducer:
const initialState = {
breakfast: ["eggs", "bacon"]
}
export default function faqReducer(state = initialState, action) {
switch (action.type) {
case "ADD_FOOD":
return Object.assign({}, state, {
breakfast: [...state.breakfast, action.food]
})
default:
return state;
}
}
Here is my root reducer with my combineReducers() function:
import { combineReducers } from "redux";
import faq from './faqReducer'
export default combineReducers({
faq: faq
});
The problem is that you're copying data from props to state, but only doing that when the component is mounted, and then expecting the state to somehow be updated when new props arrive.
Copying data from props to state is almost always the wrong approach. Please don't do that. Just use the value from props.
Two additional suggestions for improving the code:
Prefer using the "object shorthand" form of mapDispatch, rather than writing it as a function
We recommend using our new official Redux Starter Kit package as the standard way to write your Redux logic. It includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once.

Dispatch is not available in this.props

I'm very new to React and trying to write an application which outputs a portfolio to one part of the page and, based on user interaction with that portfolio, displays some information in a lightbox/modal elsewhere in the DOM.
This requires that my two rendered components have some kind of shared state, and my understanding is that the best (or one of the best) way to achieve this is with Redux. However, being new to React and now adding Redux into the mix, I'm a little out of my depth.
I've created some (for now very dumb) action creators and reducers, all I'm trying to do initially is fetch some JSON and add it to my store. However, I'm not able to access dispatch from within my component and I'm not really sure where I'm going wrong.
If I console.log this.props from within my component I get an empty object, "{}".
Here are the main parts, any pointers would be really appreciated:
App.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import store from './redux/store';
import { Portfolio } from './redux/components/portfolio';
ReactDOM.render(
<Provider store={store}>
<Portfolio />
</Provider>,
document.getElementById('portfolioCollection')
);
actions/actionCreators.js:
export const populatePortfolio = obj => ({
type: POPULATE_PORTFOLIO,
obj
});
export const populateLightbox = obj => ({
type: POPULATE_LIGHTBOX,
obj
});
portfolio.js:
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actionCreators from '../actions/actionCreators';
export class Portfolio extends React.Component {
componentDidMount() {
this.getPortfolioData();
}
getPortfolioData() {
fetch('/data.json')
.then( (response) => {
return response.json()
})
.then( (json) => {
// dispatch action to update portfolio here
});
}
render() {
return(
// render component
);
}
}
function mapStateToProps(state){
console.log('state', state);
return {
state: state
}
};
function mapDispatchToProps(dispatch) {
console.log('dispatch', dispatch);
return {
actions: bindActionCreators({ populatePortfolio: populatePortfolio }, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Portfolio);
this.props is empty because you have not passed any props. You are using the unconnected component instead of the one that has been connected to redux.
To fix this, replace this line:
import { Portfolio } from './redux/components/portfolio';
with
import Portfolio from './redux/components/portfolio';
You are exporting both the connected and the unconnected component. You probably only want the last export. Since the connected component is exported as default you import it without using {} deconstruction.
unless you need to import the unconnected component in tests or something like that, you can remove the export statement from this line, since it makes no sense to export something that you don't intend to import in another file.
export class Portfolio extends React.Component {
You aren't meant to manually call dispatch in your components. The action creator function is automatically bound to dispatch for you. Simply call this.props.populatePortfolio() in your component.

MobX observables change do not always trigger observer components render

My project is built on React, and for state management Mobx is being used.
We are not using the decorators, so the components that need to observe the observables, need to be wrapped in the following way:
import React from 'react';
import {observer} from 'mobx-react';
import MyComponent from '../app/my-component.jsx';
import myFilter from './my-filters.jsx';
export default observer(() => {
return <MyComponent filter={myFilter}/>;
});
The component MyComponent is receiving the observables as props:
static propTypes() {
return {
myFilter: React.PropTypes.function.isRequired
};
}
And it is used in the render method:
render() {
if (this.props.myFilter.filterValue1 !== null) {
// some code here
}
}
myFilter in this case is the observable, which looks somehow like this:
import {observable} from 'mobx';
const myFilter = observable({
filterValue1: null,
filterValue2: null,
addOrRemoveItem() {
// some function here
}
});
export default myFilter;
In this case, if some component alters myFilter, the observer MyComponent which receives the observable as props, does not always re-render. In some cases this can be solved by addressing the observable object by attribute before the call of the component. E.g.:
export default observer(() => {
console.log(myFilter.filterValue1);
return <MyComponent filter={myFilter}/>;
});
But this is not stable.
Is there a valid workaround to avoid this?
Declare MyComponent as an observer.
export in app/my-component.jsx should look like this
export default observer(MyComponent);
This piece of code
export default observer(() => {
return <MyComponent filter={myFilter}/>;
});
turns an anonymous stateless component, not MyComponent, into an observer.
MobX documentation clearly articulates that you need to apply observer to all components that render observable data or you will encounter problems.
The other way to solve the problem would be passing plain data from this anonymous stateless component into MyComponent.
export default observer(() => {
return <MyComponent filterValue1={myFilter.filterValue1}/>;
});
This way MyComponent will be rerendered because it receives new props each time its parent is rerendered.
Alik is right, you need MyComponent to be an observer as well.
Otherwise, your code <MyComponent filter={myFilter}/> inside the only observable you have means that you want refresh only when myFilter object (ie., reference to it) changes. It does not access any of its properties and thus refresh is not needed when those properties change. That's why your component was refreshed when you accessed the filterValue1 property in the console.log statement.

Accessing redux store inside functions

I would prefer to have a function exposed from a .js file , within that function I would prefer to have access to the variables in the store.
Snippet of the code : -
import { connect } from 'react-redux';
function log(logMessage) {
const {environment} = this.props;
console.debug('environment' + environment + logMessage );
....
}
function mapStateToProps(state) {
return {
environment : state.authReducer.environment
};
}
export default function connect(mapStateToProps)(log);
I have many components, which attach the class through connect, can I attach functions through connect()?
Edit 1
Some_File.js
import store from './redux/store.js';
function aFunction(){
var newState =store.getState();
console.log('state changed');
}
store.subscribe(aFunction)
I am assuming you have created store and reducers as redux expects.
~~~~~~~~~~~~~~~
Original Answer Starts
This is a sort of hack, I don't know what you are doing so I can't say you should or you should not do it, but you can do it this way. I have copy-pasted some of your code with some modifications.
Class XYZ extends React.Component{
componentWillReceiveProps(props){
//in your case this.props.storeCopy is redux state.
//this function will be called every time state changes
}
render(){
return null;
}
}
function mapStateToProps(state) {
return {
storeCopy : state
};
}
export default function connect(mapStateToProps)(XYZ);
Put this component somewhere at top, may just inside provider, whenever state changes this componentWillReceiveProps of this component will be invoked.
If you have a pure functional component then you can access the redux state directly like this:
import store from './redux/store';
function getStoreDetails() {
console.log(store.getState());
}
The proper place to access the store is through a container, connect is used to connect a container to a component, you cannot connect a random function to it.
There is a logger middleware for redux that you might wan't to take a look at, it does what you're trying to achieve.
To use it, just pass it as a middleware to your store:
import createLogger from 'redux-logger';
const store = createStore(
reducer,
applyMiddleware(logger)
);
A more proper way to debug a redux app is to use React Dev Tools, if you use Chrome, I recommend you to use the React Dev Tools Extension. Just install it and use it as a middleware
let store = createStore(reducer, window.devToolsExtension && window.devToolsExtension());
With it, at any given moment you can see the whole state of your store, see the actions being fired and how they affect the store, and even rewind your application by un-doing actions.
Yes. You can attach functions via connect as below;
const mapDispatchToProps = (dispatch) => {
return {
testFunction: (param1) => dispatch(testFunction(param1)),
testFunction1: () => dispatch(testFunction1())
};
};
export default function connect(mapStateToProps, mapDispatchToProps)(log);
redux state can be accessed as prop in a function by using below format.
1:
import { connect } from 'react-redux';
// log accepts logMessage as a prop
function log(props) {
const { environment, logMessage } = props;
console.debug('environment' + environment + logMessage );
....
}
function mapStateToProps(state) {
return {
environment : state.authReducer.environment
};
}
export default connect(mapStateToProps)(log);
How to use log function?
log({ logMessage: "Error message" });
2:
// import redux store
import { store } from './Store';
function log(logMessage) {
const currentState = store.getState();
const environment = currentState.authReducer.environment;
console.debug('environment' + environment + logMessage);
....
}

Initialize component with Async data

I'm trying to figure out how and where to load the data (ie call dispatch on my action) for my select box in react + redux + thunk. I'm not sure if it should go in the constructor of my App container, or should i load it inside my component (in my example: "MyDropdown")
My main App:
import MyDropdown from '../components/mydropdown';
// Should i import my action here and then...
// import { loadData } from '../actions';
class App extends Component {
render() {
return (
<div className="page-content">
<div className="option-bar">
// SEND it as a PROP inside MyDropdown...
<MyDropdown />
</div>
</div>
);
}
}
export default App;
My Component
// OR.. Should i load it in my MyDropdown component here?
import { loadData } from '../actions';
class MyDropdown extends Component {
// If i load it here on load, how do i do it?
render() {
return(
<select>
{renderOptions()}
</select>
);
}
}
I've tried componentDidMount() inside my App class, but it didnt seem to work. It seems to make sense to put the initialize data and call to actions there as it'll be all centralized, instead of calling actions inside my child components. Also, i'll have multiple select boxes that need to be loaded on startup, so my App class might grow quite a bit, is that the correct way to do it? I'm not sure what the best practice is as i've only just started learning react.
You should separate data components from presentation components (see post here).
So in your small example, MyDropdown should be passed all the data it needs in order to render the component. That would mean fetching the data in App (or some parent component of the component actually rendering the view.
Since you're working with React and Redux, the react-redux library provides a helper function to generate containers that fetch the data required for your presentation component.
To do that, change App to:
import { connect } from 'react-redux'
import MyDropdown from '../components/mydropdown';
import { loadData } from '../actions';
// This class is not exported
class App extends Component {
componentDidMount() {
this.props.loadData()
}
render() {
return (
<div className="page-content">
<div className="option-bar">
<MyDropdown data={this.props.data}/>
</div>
</div>
);
}
}
function mapStateToProps(state) {
const { data } = state
return {
data
}
}
function mapDispatchToProps(dispatch) {
return {
loadData(){
dispatch(loadData())
}
}
}
// Export a container that wraps App
export default connect(mapStateToProps, mapDispatchToProps)(App);
Alternatively, you could keep App the same and change MyDropdown to:
import { connect } from 'react-redux'
import { loadData } from '../actions';
// Exporting this allows using only the presentational component
export class MyDropdown extends Component {
componentDidMount() {
this.props.loadData()
}
render() {
return(
<select>
{renderOptions(this.props.data)}
</select>
);
}
}
function mapStateToProps(state) {
const { data } = state
return {
data
}
}
function mapDispatchToProps(dispatch) {
return {
loadData(){
dispatch(loadData())
}
}
}
// By default, export the container that wraps the presentational component
export default connect(mapStateToProps, mapDispatchToProps)(MyDropdown);
In both cases, look at what is actually being exported as default at the end. It's not the component; it's the return of connect. That function wraps your presentational component and returns a container that is responsible for fetching the data and calling actions for the presentational component.
This gives you the separation you need and allows you to be flexible in how you use the presentation component. In either example, if you already have the data you need to render MyDropdown, you can just use the presentation component and skip the data fetch!
You can see a full example of this in the Redux docs here.

Resources