We have set up a project with redux. In this project, we get an info objecat from an api and insert it into the store. Now we noticed that the function components re-render even if the api return the same state as in the previous request.
We think it's because we are overwriting the store but we are not sure.
ChatContainer.js
const mapStateToProps = function (state) {
return {
content: state.info.content,
loading: state.info.loading,
}
}
const ChatContainer = connect(
mapStateToProps,
)(Chat)
export default ChatContainer
Chat.js
function Chat(props) {
const { content, loading } = props;
return (
<Info content={content} loading={loading} />
)
}
action.js
export function setInfo(info) {
return {
type: SET_INFO, info: {
content: info,
loading: false
}
}
}
reducer.js
function setInfo(state = { content: [], loading: true }, action) {
switch (action.type) {
case SET_INFO:
return action.info
default:
return state
}
}
const appReducer = combineReducers({
...
info: setInfo,
...
})
export default appReducer
If state.info.content is an object, every time you change it with setInfo it will have a new reference. React-redux does a shallow compare on the result of mapStateToProps, so if your content is a different reference every time your component will re-render. connect HOC has an options parameter that you can use to implement a custom compare.
My advice would be to add a check to your setInfo or to the code calling setInfo and not calling your API if data is already loaded/didn't change(don't know your business logic).
Related
I am new to react and redux ... so please forgive me for noobie mistakes. I read several documentations of redux and came to conclusion that this is how i should store state of react component. I require redux because there are many more nested components which need quick access of data. however... when I try to export store ... I can't find how to do so.
My App.js
export default class App extends Component {
state = {
xx: null,
yy: null
}
componentDidMount(){
//some logic
// State gets data from api
this.setState({
xx: someval,
yy: someval2
});
}
render() {
const obj = {
xx: this.state.xx,
yy: this.state.yy
};
userReducer(obj,updateUserDetails());
const store = createStore(userReducer);
return (
<Provider store={store} >
<UserDetails props ={this.state} />
</Provider>
);
}
}
// Reducer function
export const userReducer = (state, action) => {
console.log("in reducer " + JSON.stringify(state));
switch(action.type) {
case 'UPDATE_USER_INFO':
state = {
...state,
xx: action.payload.xx,
yy: action.payload.yy
}
break;
}
return state;
}
// Doesn't work
export const store = createStore(userReducer)
// Action
export const updateUserDetails = () => {
return {
type: 'UPDATE_USER_INFO'
}
}
I can't figure out way to export store so that it is accessible to nested components. Kindly help
Thanks in advance!
From looking on your code, I can see a few issues that each one can be your problem.
while reducer first loads, it has to hold initial value for the state to begin with, not related to the one you want to be set at component mount
// assiging empty obj as initial value
export const userReducer = (state = {}, action)
Actions of redux are higher order functions, returning object to be dispatch
export const updateUserDetails = () => (dispatch, getState) => {
return dispatch ({
type: 'UPDATE_USER_INFO'
})
}
About your createStore, declare here as well initial value
// assign empty obj as initial value
export const store = createStore(userReducer, {})
hope it is helpful, anyhow I recommended on looking through the docs again
I'm currently trying to implement redux to my react-app for the first time.
I created a reducer, connected react with redux and setup an action within component A - so far so good. But how do I listen to a state change inside of component B?
Something like this:
changes.subscribe(value => {
if (value === 1) {
this.runSpecificFunction();
}
});
In other words, I want to subscribe to the changes, and react to them by executing some functions inside of component B. How can I do so?
What I've got so far for Component B - the receiving component:
const mapStateToProps = state => {
return {
nav: state.navigation
};
};
export default connect(mapStateToProps)(withRouter(CartHolder));
Reducer:
const initialState = {
navigation: 0 // inactive
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'START_NAVIGATION':
return {
navigation: action.value
};
case 'STOP_NAVIGATION':
return {
navigation: action.value
};
default:
return state
}
};
export default rootReducer;
Any state change (redux based or not) will re-render your child component. So using the useEffect hook should fit your use case (https://reactjs.org/docs/hooks-effect.html).
Something like this in your example :
const CartHolder = () => {
React.useEffect(() => {
myCallbackFunction(nav);
}, [nav]);
return (...);
};
I've tried all of the related questions here in Stack Overflow and still didn't find a solution to this problem.
I have a reducer called me and I'm trying to update an array of objects in it called folders, whenever I update the me reducer the component doesn't update.
Here's how I'm updating the reducer in my component:
class ComponentA extends Component {
...
updateUploadedFiles(file) {
console.log(this.props.store);
const newFolders = this.props.me.folders.map(
folder =>
folder._id === file.parent._id
? {
...folder,
files: [...folder.files, file.file]
}
: folder
);
this.props.updateMe({
...this.props.me,
folders: newFolders
});
}
...
}
function mapStateToProps(state) {
return {
me: state.me,
path: state.path,
filesToUpload: state.uploads
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
updatePath,
updateMe,
updateUploads
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps, null, {
pure: false
})(Upload);
this is my updateMe action's code:
export const updateMe = state => ({
type: "UPDATED_ME",
payload: state
});
And this is the me reducer's code:
export default function(state = "NOT_AUTHENTICATED", action) {
switch (action.type) {
case "UPDATED_ME":
return action.payload;
default:
return state;
}
}
Also here's how I'm combining the reducers:
import me from "./me";
...
import { combineReducers } from "redux";
const reducers = combineReducers({
me,
...
});
export default reducers;
This is not how redux works.
In order to update any part of your Redux store you must dispatch an action in order to let Redux "know" that the store changed and update any dependent component.
You state object must be immutable.
I am trying to get familiar with the flow of the react-boilerplate.
Till now I love how neat clean and easy to understand are things, I although feel that I miss a piece of the puzzle. Would be nice if someone with more experience could help me with that.
The problem I am facing at the moment goes as follows.
I am triggering an action within componentWillMount() of a specific component.
The action is being created in actions.js, its a simple get request made with axios.
The data are being processed in a promise middleware library redux-promise.
The promise is now being passed into the reducer of the specific component, where the whole state and the data that I need are being returned.
Trying to catch this state at the component is where I fail. I am trying to mapStateToProps but cannot find the data that I need there instead a Map {} is being received.
How do I Map this object with my props ?
I am sure I miss something important.
Here is my repo.
https://github.com/paschalidi/blog-react-redux
And here is my code so you can have a brief look.
index.js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { fetchPosts } from './actions'
import selectPostsIndex from './selectors'
export class PostsIndex extends React.Component { // eslint-disable-line react/prefer-stateless-function
componentWillMount() {
this.props.fetchPosts();
}
render() {
return (
<div>
<h3>Posts</h3>
<ul className="list-group">
A list would render here.
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log(state.posts)
//return { posts: state } //****I dont get why the redux state is not being given here.
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchPosts }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsIndex);
actions.js
import axios from 'axios'
import { FETCH_POSTS } from './constants';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=dsklhfksdhfjkdshfkjdshkj';
export function fetchPosts() {
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
store.js
import promise from 'redux-promise';
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
promise
];
reducer.js
import { fromJS } from 'immutable';
import {
FETCH_POSTS
} from './constants';
const initialState = fromJS({ all:[], post: null });
function postsIndexReducer(state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return { ...state, all: action.payload.data };
default:
return state;
}
}
export default postsIndexReducer;
Also the action is being registered in reducers.js
import PostsReducer from 'containers/PostsIndex/reducer'
export default function createReducer(asyncReducers) {
return combineReducers({
route: routeReducer,
language: languageProviderReducer,
posts: PostsReducer,
form: reduxFormReducer,
...asyncReducers,
});
}
Note I didn't test your code, but it looks like your reducer puts the fetched data in the field all of your global states posts field, but your mapStateToProps doesn't pick that up. Note that mapStateToProps should slice the part of the global state that the given component is interested in.
After a successful fetch the state you receive in mapStateToProps should look something like this:
{
posts: {
all: // whatever fetch returned
post: null
}
}
So your mapStateToProps could look something like this (note that this method receives the global state as an argument, not just for the specific reducer):
function mapStateToProps(state) {
// in component this.props.posts is { all: /* fetch result */, post: null }
return { posts: state.posts }
}
Also try to debug these methods, it becomes clearer once you see the flow of the data!
This GitHub issue covers this exact problem: https://github.com/reactjs/react-redux/issues/60.
I had to manually extract the values from the Map in mapStateToProps function:
const mapStateToProps = (state) => {
return {
posts: state.get('posts'),
};
}
Thanks to this StackOverflow post.
I'm using React and Redux on a project, and I'm having problems implementing a feature to enable/disable a button. I've been able to:
trigger a method
have that method trigger an action creator
dispatch an action
catch that action in the reducer and create a new, updated state
see the updated state in Redux DevTools
However, the enable/disable functionality still doesn't work, as it seems that mapStateToProps and connect aren't actually mapping the state to the props. I'm tracking canSubmit, which changes within the state but is undefined in the props. What am I missing to successfully map the state to the props?
Relevant code:
UserFormView.js
const mapStateToProps = (state) => ({
routerState: state.router,
canSubmit: state.canSubmit
});
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(ActionCreators, dispatch)
});
class UserFormView extends React.Component {
...
}
export default connect(mapStateToProps, mapDispatchToProps)(UserFormView);
Actions:
export function enableSubmit(payload) {
return {
type: ENABLE_SUBMIT,
payload: payload
};
}
export function disableSubmit(payload) {
return {
type: DISABLE_SUBMIT,
payload: payload
};
}
Reducer (using a createReducer helper function):
const initialState = {
canSubmit: false
};
export default createReducer(initialState, {
[ENABLE_SUBMIT]: (state) => {
console.log('enabling');
return Object.assign({}, state, {
canSubmit: true
});
},
[DISABLE_SUBMIT]: (state) => {
console.log('disabling');
return Object.assign({}, state, {
canSubmit: false
});
}
});
Seems like you're not creating reducer with key canSubmit. It depends on your store configuration, to be more specific — on how you import your default export from reduces file. Another thing to mention here, it's likely you'll have reducer with the name canSubmit and a key canSubmit, so you'll need to reference it in code like state.canSubmit.canSubmit — you're returning object from action handlers on reducer, not simple true or false boolean values.