I am using electron-react-boilerplate to start a small application for task tracking using NeDB for persistance.
When I start my application, for first time, and change first route,
##router/LOCATION_CHANGE is fired but my state is empty, and then after that action (LOAD_NOTES -action that I defined) is fired (loading my data - server request) - First time I have small error flash (undefined variable - my state is empty because data is getting loaded in LOAD_NOTES_REQUEST action which "fires" LOAD_NOTES actions after loading of data is finished).
Any idea why is that happening - how to populate my state at route change in proper way ?
EDIT
actions/project.js
import * as ActionTypes from '../constants/ActionTypes';
import * as axios from 'axios';
import Alert from 'react-s-alert';
const baseURL = 'http://localhost:3000';
export function addProject(project) {
return {
type: ActionTypes.ADD_PROJECT,
project
};
}
export function addProjectRequest(project) {
return dispatch => {
dispatch(addProject(project));
axios.post(`${baseURL}/api/addProject`, project)
.then(function (response) {
Alert.success('Test message 3', {
position: 'top-right',
});
})
.catch(function (response) {
console.log(response);
});
};
}
reducers/projectReducer.js
import * as ActionTypes from '../constants/ActionTypes';
const initialState = { projects: [], project:null};
const projectsReducer = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.ADD_PROJECT :
return {
projects: [{
projectName: action.event.projectName,
projectWorkOrder: action.event.projectWorkOrder,
projectClient: action.event.projectClient,
projectDescription: action.event.projectDescription,
projectStatus: action.event.projectStatus,
_id: action.event._id,
}, ...state.projects],
project: state.project
};
case ActionTypes.ADD_PROJECTS :
return {
projects: action.projects,
project: state.project,
};
case ActionTypes.GET_PROJECT :
return {
projects: state.projects,
project: action.project
};
default:
return state;
}
};
export default projectsReducer;
containers/projectDetailContainer.js
import React, { PropTypes, Component } from 'react';
import { bindActionCreators } from 'redux';
import ProjectDetail from '../components/projects/ProjectDetail';
import { connect } from 'react-redux';
import * as ProjectActions from '../actions/project';
class ProjectDetailContainer extends Component {
constructor(props, context) {
super(props, context);
}
componentDidMount() {
this.props.dispatch(ProjectActions.getProjectRequest(this.props.params.id));
}
render() {
return (
<div>
<ProjectDetail singleProject={this.props.project} />
</div>
);
}
}
ProjectDetailContainer.need = [(params) => {
return Actions.getProjectRequest.bind(null, params.id)();
}];
ProjectDetailContainer.contextTypes = {
router: React.PropTypes.object,
};
function mapStateToProps(store) {
return {
project: (store.projectsReducer.project)
};
}
export default connect(mapStateToProps)(ProjectDetailContainer);
components/ProjectDetail.js
import React, { Component } from 'react';
export default class ProjectDetail extends Component {
constructor(props, context) {
super(props, context);
}
render() {
return (
<div>
<h1>Project details</h1>
<div>{(this.props.singleProject[0].projectName)}</div>
</div>
);
};
}
routes.js
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import App from './containers/App';
import HomePage from './containers/HomePage';
import CounterPage from './containers/CounterPage';
import Dashboard from './containers/Dashboard';
import NewProjectContainer from './containers/NewProjectContainer';
import ProjectsListContainer from './containers/ProjectsListContainer';
import ProjectDetailContainer from './containers/ProjectDetailContainer';
export default (
<Route path="/" component={App}>
<IndexRoute component={Dashboard} />
<Route path="/counter" component={CounterPage} />
<Route path="/new-project" component={NewProjectContainer} />
<Route path="/projects-list" component={ProjectsListContainer} />
<Route path="/projects/:id" component={ProjectDetailContainer} />
</Route>
);
After going through your question and the code I can see that the key part of your question is:
Any idea why is that happening - how to populate my state at route change in proper way ?
Well, you may need to play with component in your routes.js. This React Router main documentation will help: https://github.com/ReactTraining/react-router/blob/master/docs/API.md#route
Further more, following may also assist in case if you are looking for something else:
fetching data before changing route with react-router
redux store state between page route
Related
I have created a small application and connected it to Redux. Unfortunately when creating new components and using the same exact code those new components cannot seem to connect to redux and get undefined when accessing it (using mapStateToProps).
I have tried to create new Components and connect them again to no avail. I'm kind of at loss as to why it isn't working especially since the rest of the application can connect and get the state properly
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store} >
<App />
</Provider>
, document.getElementById('root'));
store.js:
const initialState = {
guessedTimezone: '',
timezone: '',
pseudo: '',
};
function rootReducer(state = initialState, action) {
console.log(action);
if (action.type === 'CHANGE_TIMEZONE') {
return Object.assign({}, state, {
timezone: action.timezone,
guessedTimezone: action.guessedTimezone
})
}
if (action.type === 'CHANGE_PSEUDO') {
return Object.assign({}, state, {
pseudo: action.pseudo,
token: action.token
})
}
return state;
}
export default rootReducer;
new Component not connecting:
import React, { Component } from 'react'
import { connect } from 'react-redux'
export class TestPseudo extends Component {
render() {
console.log(this.props.pseudo);
return (
<div>
{this.props.pseudo}
</div>
)
}
}
const mapStateToProps = state => {
return {
pseudo: state.pseudo
}
}
export default connect(mapStateToProps)(TestPseudo)
Here for example this.props.pseudo returns undefined when, if the connection happens, it should return the value if i understand it correctly and yet it shows undefined
EDIT:
App.js as per requested :
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Homepage from './Components/Homepage';
import moment from 'moment';
import moment_timezone from 'moment-timezone';
import HeaderApp from './Components/HeaderApp';
import { TestPseudo } from './Components/TestPseudo';
export class App extends Component {
async componentDidMount() {
let tz = moment.tz.guess(true);
let date = moment(new Date()).local();
let timezone = date['_i'].toString().split('(')[1].split(')')[0];
this.props.dispatch({
type: 'CHANGE_TIMEZONE',
guessedTimezone: tz,
timezone: timezone
})
console.log(`Guessed timezone: ${tz} (${timezone})`);
}
_showHomepage() {
if (this.props.showHomepage && this.props.loaded) {
return (
<div style={styles.mainWindow}>
{/*<Homepage click={this._handleClick} />*/}
<TestPseudo />
</div>
)
}
}
_showHeader() {
return (
<div>
<HeaderApp />
</div>
)
}
render() {
return (
<div>
{this._showHeader()}
{this._showHomepage()}
</div>
)
}
}
const styles = {
mainWindow: {
height: '100vh',
width: '100vw'
}
}
const mapStateToProps = state => {
return {
guessedTimezone: state.guessedTimezone,
timezone: state.timezone,
};
};
export default connect(mapStateToProps)(App);
I call that new Component instead of my old Component. The homepage can connect but not the new one so i think it's not a problem of emplacement
I think its here
import { TestPseudo } from './Components/TestPseudo';
You are importing the non-connected component. Try this
import TestPseudo from './Components/TestPseudo';
For your understanding, exporting as default can be imported like so;
export default Component
import WhateverName from ....
Named export like const or in your case class;
export class Component
import { Component } from ...
So use brackets when Named, and skip brackets when default.
I'm so confused on why this is undefined. I set up store, and can even view it through Chrome's Redux Tools and it show correctly, however when I try and dispatch to the Store, I always get a Cannot read property 'dispatch' of undefined error. It's really bugging me because I have no idea what is going on. I have followed numerous tutorials letter for letter, and am still stuck with the same error message.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import manageEmails from './reducers/manageEmails';
import App from './App';
import './index.css';
export function configureStore(){
return createStore(
manageEmails,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
};
ReactDOM.render(
<App store={configureStore()} />,
document.getElementById('root')
);
manageEmails.js
export default function manageEmails(state = {
numberOfEmails: 0,
emails: [],
}, action) {
switch(action.type) {
case "ADD_USER":
return Object.assign({}, state, {
emails:state.emails.concat(action.email)
});
default:
return state;
}
}
App.js
import React, { Component } from 'react';
import './App.css';
import $ from 'jquery';
import Typed from 'typed.js';
import AboutDiv from '../src/components/AboutDiv.js'
import ContactDiv from '../src/components/ContactDiv.js'
import Navigation from '../src/components/Navigation.js'
import Background from '../src/components/Background.js'
import Overlay from '../src/components/Overlay.js'
import Emails from '../src/components/Emails.js'
import { BrowserRouter as Router, Route } from "react-router-dom"
$(document).ready(function() {
var options = {
strings: ["dj#khaled.com", "post#malone.com",
"drizzy#drake.com"],
typeSpeed: 175,
backSpeed: 20,
fadeOut: true,
loop: true,
attr: 'placeholder'
}
var typed = new Typed( (".typed"), options);
});
export class App extends Component {
render() {
return (
<Router>
<div className="App">
<Navigation />
<Background />
<Overlay />
<Emails store={this.props.store}/>
</div>
</Router>
);
}
}
export default App;
Emails.js
import React, { Component } from 'react';
export default class Emails extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
};
}
handleOnSubmit(event) {
debugger
event.preventDefault();
this.props.store.dispatch({
type: "ADD_EMAIL",
email: this.state.email,
})
}
render() {
return(
<button onClick={this.handleOnSubmit.bind(this)}>
Click Me
</button>
)
}
};
Check the minimal example I created out of your code and it seems to work as intended. Code can be found at codesandbox
Also, you have a typo there. In the reducer you are checking for a different action then the one being dispatched in the Email component
for starting, i saw many questions which looks like mine on stackoverflow but i think i miss something, this is why i'm asking this question.
I have a Maincomponent in my app which add datas to my store using the action addDatas.
This part works, i can access to my store in the context and in the children of MainComponent with this.props.
But when i go to OtherComponent (which is a basic component where i just want to show all the datas collected in MainComponent) my store seems to be empty.
Can someone tells me what i'm doing wrong and what OtherComponent should looks like for access the datas i set in the store when i was using MainComponent.
Thanks.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducer from './reducers'
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
reducers.js
import { ADD_DATAS } from '../constants';
const reminder = (action) => {
switch (action.type) {
case ADD_DATAS:
return {
datas: action.datas,
id: action.id
};
default:
return {
text: action.text,
id: action.id
};
}
}
const reminders = (state = [], action) => {
let reminders = null;
switch (action.type) {
case ADD_DATAS:
reminders = [...state, reminder(action)];
return reminders;
default:
return state;
}
}
export default reminders;
Action.js
import {ADD_DATAS} from '../constants';
// ADD_DATAS = 'ADD_DATAS' in constants
export const addDatas = (text, id) => {
const action = {
type: ADD_DATAS,
datas: text,
id: id
}
return action;
}
App.js
import React, { Component } from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import RouterComponent from '../Router/RouterComponent';
import OtherComponent from './OtherComponent';
import MainComponent from './MainComponent';
class App extends Component {
render() {
return (
<div className="container-fluid">
<div className="row">
<Sidebar />
<Router>
<RouterComponent>
<Switch>
<Route exact path="/oth" component={OtherComponent}/>
<Route exact path="/main" component={MainComponent}/>
<Route exact path="/" component={MainComponent}/>
</Switch>
</RouterComponent>
</Router>
</div>
</div>
);
}
}
export default App;
MainComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { addDatas } from '../actions'
class MainComponent extends Component {
addDataStore(text, id){
this.props.addDatas(text, id)
}
render(){
return ( .... )
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({addDatas}, dispatch);
}
function mapStateToProps(state){
return {
reminders: state
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MainComponent);
OtherComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
class OtherComponent extends Component {
render(){
console.log(this.props.reminders)
}
}
function mapStateToProps(state){
return {
reminders: state
}
}
export default connect(mapStateToProps)(OtherComponent);
Sidebar.js
import React, { Component } from 'react';
import '../css/sidebar.css';
export default class Sidebar extends Component {
render(){
return (
<nav className="col-2 col-md-2 sidebar">
<div className="sidebar-logo">
<a href="/main">
MainComponent
</div>
</a>
</div>
<ul >
<li >
<a href="/main" >
MainComponent
</a>
</li>
<li >
<a href="/oth" >
OtherComponent
</a>
</li>
</ul>
</nav>
)
}
}
To Understand this we first need to understand the below snippet
export default connect(mapStateToProps, mapDispatchToProps)(MainComponent);
we are passing mapStateToProps function to the connect method which we get from react-redux. So let's Understand how connect works and what it actually does
1. It calls your mapStateToProps function and passes the current value of (redux state/ redux store) to the function.
2. Then whatever value is returned by the mapStateToProps function after execution is passed down as props to the mainComponent(in your case).
So Since the child component for the main component is not having connect statement the props are not available to it.
You can make the redux state available as props by two was
1. Passing it down from main component as follows inside mainComponent.js render method we have
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { addDatas } from '../actions'
class MainComponent extends Component {
addDataStore(text, id){
this.props.addDatas(text, id)
}
render(){
return (
<Child1 reminders={this.props.reminders}/*can be accessed as this.props.reminders*//>
<Child2 reminders={this.props.reminders}/*can be accessed as this.props.reminders*//>
)
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({addDatas}, dispatch);
}
function mapStateToProps(state){
return {
reminders: state
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MainComponent);
2.Another way to do this will be using connect Statement inside your child component as well
export default connect(mapStateToProps, mapDispatchToProps)(MainComponent);
class Child1 extends Component{
.....
}
function mapDispatchToProps(dispatch){
return bindActionCreators({addDatas}, dispatch);
}
function mapStateToProps(state){
return {
reminders: state
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Child1);//need to change here must be same as className
You should connect your OtherComponent to redux store as well using connect HoC.
You need to connect other components with mapStateToProps and mapDispatchToProps functions as you do in MainComponents.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { addDatas } from '../actions'
class OtherComponent extends Component {
addDataStore(text, id) {
this.props.addDatas(text, id)
}
render() {
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addDatas }, dispatch);
}
function mapStateToProps(state) {
return {
...state
}
}
export default connect(mapStateToProps, mapDispatchToProps)(OtherComponent);
You would connect the otherComponent as well using connect HOC, in order to access the Store. Once you have a component connected the Store in the Hierarchy you can pass the data as props on to its children. However you do need to connect the top level component/s to store
import React, { Component } from 'react';
import { connect } from 'react-redux'
class OtherComponent extends Component {
render(){
console.log(this.props.reminders)
}
}
function mapStateToProps(state){
return {
reminders: state
}
}
export default connect(mapStateToProps)(OtherComponent);
Well,
my problem wasn't on my implementation of Redux but on the fact that i used href for navigate between my components.
The problem was that my Sidebar component wasn't in the Router component, then i had to save the history in the redux store and call history.push in my sidebar component with the history i saved before.
In all my components inside the router i added in the constructor:
constructor(props){
super(props);
...
this.props.addHistory(props.history);
}
addHistory add history if it doesn't already exist.
Then in my sidebar, i use the history in the store for use the push function:
for (var e in this.props.reminders){
if(this.props.reminders[e].history !== undefined ){
this.props.reminders[e].history.push('/oth');
}
}
I don't know if it's the cleanest way to do this, but it works.
I am utilizing thunk with react and redux. My action creator executes an API call and returns data in a property "data" and my reducer returns that object. I have this returned object mapped to props in my component. Its an array of 16 items (each item is an image url). when I console.log(this) I can click through and see the data, but if I go further like console.log(this.props.showGallery.imageLinks) it shows undefined.
Another situation is that when I render { this.props.showGallery.imageLinks } I can clearly see all the text of the items in the array on my web page but when I use .map on it, the console says cannot read property "map" of undefined and the web page is just empty. Am I doing this wrong? How can I make this data like normally?
Am I understanding redux concepts wrongly?
actions.js
export const SHOW_GALLERY = 'SHOW_GALLERY';
import axios from 'axios';
// FETCH THE IMAGES
export const actionGallery = () => {
return ( dispatch ) => {
axios.get('../images')
.then(res => {
if (res.status === 200) {
return dispatch({ type: SHOW_GALLERY, data: [...res.data] });
}
})
.catch(err => console.error(err));
}
}
reducer
images.js
import { HEAD_SELECTED, SHOW_GALLERY } from '../actions/actions';
import { actionSelectHeadImg } from '../actions/actions';
import { actionGallery } from '../actions/actions';
// Show the image links from an array
export function showGallery(state={}, action) {
switch(action.type) {
case SHOW_GALLERY:
return Object.assign({}, state, { imageLinks: new Array(action.data) })
default:
return state;
}
}
combined Reducers for above:
import React from 'react';
import { combineReducers } from 'redux';
import { showGallery, headSelected } from './images';
// Combine all your reducers to this
const allReducers = combineReducers({
showGallery,
headSelected
});
export default allReducers;
component / container
Gallery.js
import React from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actionGallery } from '../actions/actions';
import cheerio from 'cheerio';
class Gallery extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
}
this.props.actionGallery();
}
render() {
return (
<div>
<h1>This is the Gallery.</h1>
<br />
<div className="container">
<div className="row">
<div className="col-md-8">
<h2>H2 h2 h2 2h2 </h2>
{ this.props.showGallery.imageLinks.forEach((i, index) => {
<p>i</p>
}) }
</div>
</div>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
showGallery: state.showGallery,
headSelected: state.headSelected,
newState: state
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
actionGallery,
actionSelectHeadImg
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Gallery);
A regular component that just holds the other container/components
import React from 'react';
import ReactDOM from 'react-dom';
import Gallery from './containers/Gallery';
import HeadingImg from './containers/HeadingImg';
class App extends React.Component {
render() {
//console.log(this.props.actionGallery())
return (
<div>
<center>
<h3>SELECTED IMAGE:</h3>
<br />
<HeadingImg />
<hr />
<Gallery />
</center>
</div>
);
}
}
export default App;
TOP LEVEL COMPONENT (MAIN COMPONENT)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import App from '../Components/Earth/app';
import allReducers from '../Components/Earth/reducers';
import thunk from 'redux-thunk';
const store = createStore(allReducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App store={store}/>
</Provider>
, document.getElementById("root"));
I'm assuming when you create your store, you only have the one reducer. If that is the case then your assumption about 'state.showGallery' existing doesn't. Instead imageLinks will be in state without the 'showGallery'.
If my assumption is correct, then you should change your mapStateToProps to have showGallery as:
showGallery: { imageLinks: state.imageLinks },
I'm trying to display a list of posts in a react component using redux, but I get the following warning:
Warning: Failed propType: Required prop posts was not specified in Posts. Check the render method of PostsContainer.
I have the following code:
The posts container:
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { getInitalPosts } from 'actions/postsActions'
import { Link } from 'react-router'
import _ from 'lodash'
import Posts from 'components/PostApp/Posts'
class PostsContainer extends Component {
static fetchData({ store }) {
return store.dispatch(getInitalPosts())
}
componentDidMount() {
this.props.getInitalPosts()
}
render() {
return (
<div>
<h2>Posts</h2>
<Posts posts={this.props.posts} />
<Link to="/">Back to Home</Link>
</div>
)
}
}
function mapStateToProps (state) {
return { posts: state.posts }
}
export { PostsContainer }
export default connect(mapStateToProps, { getInitalPosts })(PostsContainer)
and the posts component:
import React, { Component, PropTypes } from 'react'
import { List } from 'immutable'
class Posts extends Component {
render() {
return (
<div>
Posts component
{
this.props.posts
}
</div>
)
}
}
Posts.propTypes = {
posts: PropTypes.instanceOf(List).isRequired
//posts: PropTypes.posts
}
export default Posts
What am I doing wrong?
[EDIT]
Here is the post reducer:
import * as ActionType from 'actions/postsActions'
import Immutable from 'immutable'
let defaultState = Immutable.fromJS([])
function postsReducers (state = defaultState, action) {
switch(action.type) {
case ActionType.INITIAL_POSTS:
return Immutable.fromJS(action.response)
break
default:
return state
}
}
export default postsReducers
And as far as importing the container it's in the routes index:
8 import Questions from 'containers/Questions'
9 import Question from 'containers/Question'
10: import Posts from 'containers/Posts'
11
12 export default function(history) {
..
14 <Router history={history}>
15 <Route path="/" component={App}>
16: <Route path="posts" component={Posts} />
17 <Route path="questions" component={Questions} />
18 <Route path="questions/:id" component={Question} />
How are you importing PostsContainer?
If you're doing it like this:
import { PostsContainer } from PostsContainer;
You won't be importing the connected version and therefore won't have the posts prop you're trying to use inside PostsContainer.
Can you post the importing code?
Since you're running getInitialPosts on componentDidMount of PostsContainer, the first time it renders it doesn't have any posts yet so the value for this.props.posts is null. That's what gets passed to Posts, breaking your propTypes requirement.
You might add an if statement inside the render method of PostsContainer to only render Posts if there are posts, otherwise render a loading spinner.