I am using React redux with firebase realtime database.
In App.js I am dispatching an action fetchAllPosts
App.js
class App extends Component {
componentDidMount() {
this.props.fetchAllPosts();
}
render() {
return (
<div className="App">
// something ...
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return {
fetchAllPosts: () => {dispatch(allPosts())}
}
}
My action looks like this (I am using redux-thunk):
action
export function allPosts() {
return (dispatch) => {
firebase.database().ref('posts/').on('value', (snapshot) => {
dispatch({type: "ALL_POSTS", postsArray: snapshot.val(), loading: false})
})
}
}
Then I am combining reducers (I know in this case it is not necessary):
const rootReducer = combineReducers({
allPosts: postsReducer
})
My reducer looks like this:
reducer
const initialState = {
allPosts: []
}
const postsReducer = (state = initialState, action) => {
switch(action.type) {
case "ALL_POSTS" :
console.log("action payload all posts", action.postsArray)
return {
...state,
loading: false,
allPosts: action.postsArray
}
break;
default:
return state
}
return state
}
And finally: my SinglePostview component looks like this:
SinglePostview.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
class SinglePostview extends Component {
render() {
console.log("ppp", this.props)
return (
<h2>{this.props.post.title}</h2>
)
}
}
const mapStateToProps = (state, ownprops) => {
const postId = ownprops.match.params.postid
return {
post: state.allPosts.allPosts[postId]
}
}
export default connect(mapStateToProps)(SinglePostview);
Here when the render method is executing, this.props.post is undefined and I have the error:
TypeError: Cannot read property 'title' of undefined.
The problem is: when the app loads for the first time, props.post is undefined (so I have an error) and after about 1 second it receives the value but it doesn't change anything - the error still exists and the value is not displaying.
Could anyone help me?
Assuming your reducer is fine, you can fix this by
changing this
render() {
return (
<h2>{this.props.post.title}</h2>
)
}
To this:
render() {
if (!this.props.post){
return null;
}
return (
<h2>{this.props.post.title}</h2>
)
}
or
render() {
return (
<h2>{this.props.post && this.props.post.title}</h2>
)
}
You are defining allPosts to be an array
const initialState = {
allPosts: []
}
But you are trying to access it like an object.
state.allPosts.allPosts[postId]
Hence, if your state.allPosts.allPosts is an array , try using the ES6 find() method to get a post from the array with the postId.
Assuming
state.allPosts.allPosts = [
{postId: 1,title:'abcd'},
{postId:2,title:'def'}
]
state.allPosts.allPosts.find(post => postId === post.postId)
Related
I have a redux State HOC to manage the connection
I Have a problem when I add a new post to the store
import React, { useEffect } from "react";
import { connect } from "react-redux";
export default function withState(WrappedComponent) {
function mapStateToProps(reduxState) {
let state = {};
for(let t of Object.entries(reduxState)) {
state = {...state, ...t[1]}
}
return {
...state,
};
}
return connect(
mapStateToProps,
null
)(function (props) {
useEffect(() => {}, [props.posts, props.comments]) /*tried this but didn't work*/
return (
<React.Fragment>
<WrappedComponent {...props} />
</React.Fragment>
);
});
}
I am trying to make the program render the response from my back-end without me reloading the page manually
I tried using the useEffect
and I saw through the dev tools that the state change correctly
my reducer
import { GET_ALL_POSTS, CREATE_NEW_POST } from "../actions"
const initialState = {
posts: []
}
export default function postReducer(state = initialState, action) {
let newState = {...state}
switch(action.type){
case GET_ALL_POSTS:
return {
...newState,
posts: [...action.posts],
}
case CREATE_NEW_POST:
const posts = [...newState.posts, action.post]
return {
...newState,
posts
}
default:
return {
...newState,
}
}
}
I also read that react changes doesn't respond to shallow copies so I changed the whole array in the post reduces when I add a new post
Your withState HOC is very strange. I'm not sure why you don't just use connect directly (or use hooks). But try this:
export function withState(WrappedComponent) {
return connect(
(state) => ({
posts: state.postsReducer.posts,
comments: state.commentsReducer.comments
}),
null
)(WrappedComponent);
}
I'm a newbie in redux and react.js,
I am trying to make a button disappear on a component in react.js by putting an if condition on the state variable (articlesTable/index.js), which is connected to the redux library function on another file (actions/actionArticles.js), when a button on articlesTable/index.js is clicked, the component is connected with actions/actionArticles.js and dispatch a function in actions/actionArticles.js, which is called loadMoreData().
The function I am trying to configure the state in redux is,
in articlesActions.js
export const loadMoreArticles = () => async (dispatch, getState) => {
const lastArticleKey = Object.keys(getState().articlesMap).pop();
const lastArticle = getState().articlesMap[lastArticleKey];
console.log("articleMap", getState().articlesMap);
console.log("Last article", lastArticleKey, lastArticle);
let filteredArticles = {};
const uid = getState().auth.uid;
const userLevel = getState().profile.userLevel;
} else {
const filteredArticlesArray = [];
var lastArticleReached = false;
...
var lastArticleInArray = filteredArticlesArray[filteredArticlesArray.length-1];
if (lastArticleInArray[0]===lastArticleKey) {
console.log("Bingo, last article reached!");
lastArticleReached = true;
}
else if (lastArticleInArray[0]!== lastArticleKey)
{
console.log("Not last article");
lastArticleReached = false;
}
filteredArticles = Object.fromEntries(filteredArticlesArray.reverse());
}
dispatch({type: LAST_ARTICLE_REACHED, payload: lastArticleReached})
...
};
I dispatch this function with
dispatch({ type: LOAD_MORE_ARTICLES, payload: filteredArticles });
in the code snippet above
The root reducer looks like this,
reducers/index.js
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import articlesStatusReducer from './articlesStatusReducer';
const rootReducer = combineReducers({
...
articlesStatus: articlesStatusReducer,
form: formReducer,
...
});
export default rootReducer;
In articleStatusReducer,
import {LAST_ARTICLE_REACHED} from "../actions/types";
export default function(state = {}, action) {
switch(action.type) {
case(LAST_ARTICLE_REACHED):
console.log(action.payload);
return action.payload;
default:
return state;
}
}
In the articlesTable/index.js, I connect like this
const mapStateToProps = (state) => {
return {
articlesMap: state.articlesMap,
appStatus: state.appStatus,
profile: state.profile,
lastArticleReached: state.articlesStatus,
}
};
const mapDispatchToProps = (dispatch) => {
return {
getArticlesWithData: () => dispatch(getArticlesWithData()),
loadMore: () => dispatch(loadMoreArticles())
}
};
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(ArticlesTable)
For some reason, articleStatus isn't recognised and when I do
console.log(this.props.articleStatus)
state.articleStatus is undefined
How can I reference state.articleStatus which should be boolean ?
Edit:
For some reason when I put it in a conditional JSX brackets in the render method, it prints out undefined
render () => {
{
console.log(this.props.lastArticleReached),
!this.props.lastArticleReached
: <Button> </Button>
?
<div><div>
}
}``
In function mapStateToProps, you should map state.articleStatus to a props.
somethings like this:
const mapStateToProps = (state) => {
return {
articlesMap: state.articlesMap,
appStatus: state.appStatus,
profile: state.profile,
lastArticleReached: state.articlesStatus,
articleStatus: state.articleStatus
}
};
So this.props.articleStatus will works . :)
The problem is in your reducer. Each case of your reducer must return the state but in your case, your return action.payload.
try something like this.
case(LAST_ARTICLE_REACHED):
console.log(action.payload);
return {...state, articleStatus: action.payload};
like this, articlesStatus became an object with one props, articleStatus, your boolean.
I tried another name for the props but with similar method as Thomas Caillard,
Reducer.js
case(REACH_LAST_ARTICLE):
return {...state, lastArticleReached: action.payload}
in component index.js
const mapStateToProps = (state) => {
return {
...
lastArticleReached: state.articlesMap.lastArticleReached
...
}
};
Thanks for all the helps so far
So i'm doing a API GET request and set the data on reducer, but the component render twice, first before dispatch and another after, the first one is causing map function problem
what can i do to avoid render twice and solve map function problem?
App.js
componentDidMount(){
this.props.carregarLojas();
}
render(){
const { lojasTeste } = this.props;
//rendering 2 times
console.log(lojasTeste);
return(
<div>
lojasTeste.map((i, index) => (
<h1>{i.name}</h1>
))
</div>
)
}
const mapStateToProps = store => ({
lojasTeste: store.lojaState.lojasTeste
});
const mapDispatchToProps = dispatch => {
return {
carregarLojas: () => {
dispatch(carregarLojas());
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Action.js
export const setarLojas = (lojas) =>{
return {
type: SETAR_LOJAS,
data: lojas
}
}
export const carregarLojas = () => {
return (dispatch) => {
return API.get('loja')
.then(response => {
dispatch(setarLojas(response.data))
})
.catch(error => {
throw(error);
})
}
Reducer.js
const initialState ={
lojasTeste: {}
}
export const lojaReducer = (state = initialState, action) => {
switch (action.type){
case SETAR_LOJAS:
return {
...state,
lojasTeste: action.data
}
default:
return state;
}
}
The double render is totally normal:
Your component render once, then call the carregarLojas method which is async. When resolved, the method will update your redux store, which is connected with the props of your component (mapStateToProps). When a prop is updated, it cause automatically a rerender.
Also, for your map problem, you didn't initialized lojasTeste as an array, but as an object. You can't use map on an object (cf https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map)
Is is correct to pass a reducer as props when i'm using a rootreducer ?
This is my rootReducer.js :
import { combineReducers } from 'redux';
import simpleReducer from './simpleReducer';
import messageReducer from './messageReducer';
import NewReducer from './NewReducer';
export default combineReducers({
simpleReducer,messageReducer,NewReducer
});
And this is one of my action creators addMessage.js
export const addMessage = (message) => dispatch => {
dispatch({
type: 'ADD',
message: message
})
}
Here is the first reducer messageReducer.js
export default (state = [], action) => {
switch (action.type) {
case 'ADD':
return [
...state,
action.message
];
default:
return state;
}
};
And here is another one simpleReducer.js
export default (state = {}, action) => {
switch (action.type) {
case 'SIMPLE_ACTION':
return {
result: action.payload
}
default:
return state
}
}
And finally here is my last reducer NewReducer.js
export default (state = '', action) => {
switch (action.type) {
case 'AnyThing':
return action.WhatToDisplay;
default:
return state;
}
};
Here is my mapping in the App.js
const mapStateToProps = state => ({
...state
})
const mapDispatchToProps = dispatch => ({
simpleAction: () => dispatch(simpleAction()),
submitNewMessage: (message) => {
dispatch(addMessage(message))
},
NewAction: () => dispatch(NewAction())
})
And here is my ِApp Component.Notice my last 2 h2 tags as well as my ul tag .Without me adding the reducer at the end of the prop , it doesn't work.So
is what i'm doing right ? or is there another way to show the redux state in
my react ?.Note that i currently have no errors and the code functions well.I
just wana know if what i am doing is right or wrong and if there is a better
syntax to show the redux state in my create react app.
class App extends Component {
constructor(props) {
super(props);
this.state = {
input: ''
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
this.props.submitNewMessage(this.state.input);
this.setState({
input: ''
});
}
simpleAction = (event) => {
this.props.simpleAction();
}
localNormalFunction=(event)=>{
this.props.NewAction()
}
render() {
return (
<div >
<h1>fjasgdasdsg</h1>
<button onClick={this.simpleAction}>Test redux action</button>
<pre>
{
JSON.stringify(this.props)
}
</pre>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.props.messageReducer.map( (message,idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul><br/><br/>
<button onClick={this.localNormalFunction}>dsadsdsa</button>
<h2>{this.props.NewReducer}</h2>
<h2>{this.props.simpleReducer.result}</h2>
</div>
);
}
}
It is better practice to get only the props you need from redux in each component. If you pass the whole redux state in mapStateToProps then whenever anything in redux changes you will have everything rerendering even if nothing you use changed.
One common reason you might be getting errors is that you are trying to use the props in render and they get instantiated afterwards.
Try this give default values to the props if you can't get them from redux:
App.defaultProps = {
result: '',
NewReducer: '',
messageReducer: []
}
const mapStateToProps = state => ({
result: state.simpleReducer.result,
NewReducer: state.NewReducer,
messageReducer: state.messageReducer
})
and then change this.props.simpleReducer.result to this.props.result
without redux it works so that not a api connection problem
I have an express app connected to react with proxy I have already managed to display my data in react but now i want to make that in redux soo:
There is my problem, i have maked all the reducers/action, store and combine reducer but I didn't see any datas in my page and i haven't any errors
There is my code :
Action
export const api = ext => `http://localhost:8080/${ext}`;
//
// ─── ACTION TYPES ───────────────────────────────────────────────────────────────
//
export const GET_ADVERTS = "GET_ADVERTS";
export const GET_ADVERT = "GET_ADVERT";
//
// ─── ACTION CREATORS ────────────────────────────────────────────────────────────
//
export function getAdverts() {
return dispatch => {
fetch("adverts")
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERTS, payload });
});
};
}
export function getAdvert(id) {
return dispatch => {
fetch(`adverts/${id}`)
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERT, payload });
});
};
}
reducer
import { combineReducers } from "redux";
import { GET_ADVERTS, GET_ADVERT } from "../actions/actions";
const INITIAL_STATE = {
adverts: [],
advert: {}
};
function todos(state = INITIAL_STATE, action) {
switch (action.type) {
case GET_ADVERTS:
return { ...state, adverts: action.payload };
case GET_ADVERT:
return { advert: action.payload };
default:
return state;
}
}
const todoApp = combineReducers({
todos
});
export default todoApp;
index.js
//imports
const store = createStore(todoApp, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("app")
);
My advertlist page :
//imports..
class Adverts extends Component {
componentDidMount() {
this.props.getAdverts();
}
render() {
const { adverts = [] } = this.props;
return (
<div>
<Header />
<h1>Adverts</h1>
{adverts.map(advert => (
<li key={advert._id}>
<a href={"adverts/" + advert._id}>
{advert.name} {advert.surname}
</a>
</li>
))}
<Footer />
</div>
);
}
}
const mapStateToProps = state => ({
adverts: state.adverts
});
export default connect(
mapStateToProps,
{ getAdverts }
)(Adverts);
I think your problem is here:
function mapStateToProps(state) {
return {
**adverts: state.adverts**
};
}
It should work if you change state.adverts to state.todos.adverts:
function mapStateToProps(state) {
return {
adverts: state.todos.adverts
};
}
Because your reducer is called todos, and it has state { adverts }, that's why you cannot access adverts even tho they are obtained.
You can check out working version here: https://codesandbox.io/s/olqxm4mkpq
The problem is, when you just create a store with one reducer without using combine reducer, it is possible to refer it directly in the ContainerS, like this:
const mapStateToProps = state => {
return{
*name of var*: state.adverts /*direct refers to adverts*/
}
}
But, when it use combined-reducer , it has to refer to an exact reducer that you want to use.like this :
const mapStateToProps = state => {
return{
*name of var* : state.todos.adverts (indirect refers to adverts from combined-reducer todos)
}
}