I put together a simple component to try and get my head around some React and Redux concepts.
I'm now trying to seperate the Redux logic into a seperate component, so that there's two components in total.
When I try to compile, all the methods/objects in NewComponent are undefined. When I add context with this I get the same errors.
Below is my original component (which works fine) before trying to put the Redux methods/objects in to a seperate component.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { createStore, combineReducers } from 'redux'
import { Provider, connect } from 'react-redux'
const incrementAction = {
type: 'INCREMENT',
value: 1
}
const decrementAction = {
type: 'DECREMENT',
value: 1
}
function reducer(state = 0, action) {
if (action.type === 'INCREMENT') {
return state + 1
} else if (action.type === 'DECREMENT'){
return state - 1
} else {
return state
}
}
// Map Redux state to component props
function mapStateToProps(state) {
return {
value: state
}
}
// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
return {
onIncrementClick: () => dispatch(incrementAction),
onDecrementClick: () => dispatch(decrementAction)
}
}
const store = createStore(reducer)
class View extends Component {
showState() {
console.log(store.getState())
}
render() {
const { value, onIncrementClick, onDecrementClick } = this.props
return (
<div className="App">
<header className="App-header">
<h1 className="App-title"></h1>
</header>
<h2>Counter</h2>
<div onClick = {onIncrementClick}> <p className="button"> + </p></div>
<div onClick = {onDecrementClick}> <p className="button"> - </p></div>
<div onClick = {this.showState}> <p className="button"> Show State </p></div>
</div>
);
}
}
View = connect(
mapStateToProps,
mapDispatchToProps
)(View)
const WrappedApp = () => (
<Provider store={store}>
<View />
</Provider>
);
export default WrappedApp;
EDIT
Here are the components after I tried to break up the above.
NewComponent:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { createStore, combineReducers } from 'redux'
import { Provider, connect } from 'react-redux'
class NewComponent extends Component {
constructor(props){
super(props)
const incrementAction = {
type: 'INCREMENT',
value: 1
}
const decrementAction = {
type: 'DECREMENT',
value: 1
}
const store = createStore(reducer)
}
reducer(state = 0, action) {
if (action.type === 'INCREMENT') {
return state + 1
} else if (action.type === 'DECREMENT'){
return state - 1
} else {
return state
}
}
// Map Redux state to component props
mapStateToProps(state) {
return {
value: state
}
}
// Map Redux actions to component props
mapDispatchToProps(dispatch) {
return {
onIncrementClick: () => dispatch(incrementAction),
onDecrementClick: () => dispatch(decrementAction)
}
}
}
export default NewComponent;
And View:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { createStore, combineReducers } from 'redux'
import { Provider, connect } from 'react-redux'
import NewComponent from './NewComponent'
class View extends Component {
showState() {
console.log(this.store.getState())
}
render() {
const { value, onIncrementClick, onDecrementClick } = this.props
return (
<div className="App">
<header className="App-header">
<h1 className="App-title"></h1>
</header>
<h2>Counter</h2>
<div onClick = {onIncrementClick}> <p className="button"> + </p></div>
<div onClick = {onDecrementClick}> <p className="button"> - </p></div>
<div onClick = {this.showState}> <p className="button"> Show State </p></div>
</div>
);
}
}
View = connect(
this.mapStateToProps,
this.mapDispatchToProps
)(View)
const WrappedApp = () => (
<Provider store={this.store}>
<View />
</Provider>
);
export default WrappedApp;
I'm now getting:
Could not find "store" in either the context or props of "Connect(View)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(View)".
I bet you have a technical background in OOP languages, right ? :)
A React Component is basically a function responsible of rendering something on the screen.
If you want to organize your Redux related code, you don't have to wrap it in a React Component. Simply declare the functions and objects you need in separate files and use JavaScript module import/export feature.
You can then follow the guidelines from Redux on how to organize your Redux code.
For example, for your Redux store, you can end up with a store.js file which contains something like
...
export const store = createStore(reducer);
...
And then use it where you want by simply importing it
import store from 'store.js'
You are not supposed to define the store twice as you do after splitting the code into two files. And you define a component that is not a real component. And these are only two things that are not great...
I don't think a good way to start building a react/redux project is by fixing the above code. A better way, that will serve you in the future, is the following:
First, download a basic redux code example. One that I recommend is ReduxSimpleStarter. It is a good example of how to organize your folder tree in a redux project.
Once you have reviewed the above example, create a new project using create-react-app, run it, then start modifying it based on the redux example.
Related
I am learning React, Redux and firebase; and creating a small word game.
There are three reducers (A, B and C).
The problem is that in the component shown below (simplified, but tested) mapStateToProps is only called when the states of B or C change; never with A.
There are several components and this component is the only one that experiences this problem. All the other components receive mapStateToProps call as expected.
I tested it by making an update to B/C whenever A is updated.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Keyboard from './Keyboard';
const intiState = {
key: 0,
}
class PlayGame extends Component {
state=intiState;
componentDidMount() {
this.props.newGame();
}
render() {
return (
<div key={this.state.key}>
<div className="">
<Keyboard />
</div >
</div>
)
}
}
let mapDispatchToProps = {
newGame: () =>{return {type: 'NEW_GAME'}},
}
let mapStateToProps = (state) =>{
console.log("MapStateToProps ",state);
return{completeState: state};
}
export default connect(mapStateToProps, mapDispatchToProps)(PlayGame)
I have been building a React-Redux application to display some weather data (openweathermap.org API) if a button gets clicked.
Somehow when the Container is rendered the data are not arriving, even if I managed to handle the promise using Axios.
As you can see in the console.log, the 'tempo' object is empty once it arrives in the container. Then, once the button is clicked, the request correctly arrives on the container and 'tempo' gets the data I want to render.
The problem occurs when I try to access those properties arrived after that the onClick() event was fired. They do not exist yet, so the whole components throw an error.
I think there is some problem with the async await response managed in the Axios request but I cannot find it.
Sorry if the explanation was not properly technical.
I remain at disposal for clarifications.
Action Creator with the API request
import axios from 'axios';
export const GET_CECCIOLA = 'GET_CECCIOLA';
export function submitWeather() {
const url = 'https://api.openweathermap.org/data/2.5/weather?appid=ce6111c5cb481755173214d6bf62f51a&q=Cecciola,it';
const cecciola = axios.get(url);
return {
type: 'GET_CECCIOLA',
payload: cecciola
}
}
Container responsible for the rendering when button is clicked
import React, { Component } from 'react';
import {connect} from 'react-redux';
class CecciolaTime extends Component {
render() {
console.log(this.props.tempo)
return (
<div>
<h2>{this.props.tempo}
</h2>
</div>
);
}
}
function mapStateToProps ({ tempo }) {
return { tempo };
}
export default connect(mapStateToProps)(CecciolaTime);
Container with the onClick() method
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {submitWeather } from '../actions/index';
class SearchBar extends Component {
constructor(props) {
super(props)
this.getWeather = this.getWeather.bind(this);
}
getWeather(e) {
e.preventDefault();
this.props.submitWeather(e);
}
render() {
return (
<form>
<button onClick={this.getWeather}>
tempo a Cecciola
</button>
</form>
)
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ submitWeather }, dispatch);
}
export default connect(null, mapDispatchToProps)(SearchBar);
Reducer
import { GET_CECCIOLA } from '../actions/index';
export default function(state = [], action) {
switch (action.type) {
case GET_CECCIOLA:
return [action.payload.data, ...state];
}
return state;
}
Reducer_Index
import { combineReducers } from 'redux';
import CecciolaReducer from './cecciola_reducer';
export default combineReducers({
tempo: CecciolaReducer
})
Store (I am using Redux-Promise as middleware)
import React from 'react';
import './index.css';
import App from './components/App';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers'
import ReduxPromise from 'redux-promise';
const storeWithMiddleware = applyMiddleware(ReduxPromise)(createStore);
render(
<Provider store={storeWithMiddleware(rootReducer)}>
<App />
</Provider>,
document.getElementById('root')
)
If you are trying to display non-existing property in tempo object and it fails - the most common way to handle it - just check if this property exists, like that:
import React, { Component } from 'react';
import {connect} from 'react-redux';
class CecciolaTime extends Component {
render() {
const { name } = this.props.tempo
return (
<div>
{/* Check if name exists then display */}
<h2>{name && name}</h2>
</div>
);
}
}
function mapStateToProps ({ tempo }) {
return { tempo };
}
export default connect(mapStateToProps)(CecciolaTime);
NOTE: You're trying to render an object { this.props.tempo } in h2 tag, which can cause another error.
UPDATE (from comments): I've find the issue, it was because you're setting result into array and it's actually keeped in 0 index in array. So you can access to your variables via this.props.tempo[0].name. To avoid this mess just use object instead of array as initial state, it's much easier to handle then.
I've created sandbox for you with working code (click to see).
Hope it will helps.
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 am starter in react & redux. I want to make todolist app. but when I insert a list into a store the displayList() function is not re-render. How can I fix this.
This is my reducer code.
export default function(state = ['start1','start2'], actions) {
switch(actions.type) {
case 'APPEND_ITEM':
state.push(actions.payload.item)
return state
break
}
return state
}
And this is my todolist.js code.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import ReactDOM from 'react-dom'
import React from 'react'
class TodoList extends React.Component {
displayList(){
return this.props.dispatchs.map((item) => {
return(
<li key={ Math.random() }>{item}</li>
)
})
}
render(){
return(
<div>
{ this.displayList() }
</div>
)
}
}
var mapStateToProps = (state) => {
return {
dispatchs: state.dispatch
}
}
export default connect(mapStateToProps)(TodoList)
pin 2,3 does not re-render on the screen
Thank you for help.
Instead of using push in the reducer, return [ ... state, action.payload.item ]
case 'APPEND_ITEM':
return [ ...state, action.payload.item ]
This is because React will see the new state (object reference) equal to the old one, and decide not to re-render since it seems like nothing have changed