I am new in redux.
My code :
Home Screen
<Text> {{this.props.mycity}} </Text>
const mapStateToProps = function(state) {
return {
mycity: state.layersFlag.baseDistrictADhabi //consist true/false
}
}
export default connect(mapStateToProps)(HomeScreen);
Sidemenu Screen :
UI
<Switch onValueChange={(flag) => {
this.props.toggleCity();
} value={this.state.city} />
const mapDispatchToProps = dispatch => {
return {
toggleCity: () => {
dispatch({ type: "changeCity" })
}
};
};
export default connect(null, mapDispatchToProps)(SideMenuScreen);
Store and reducer setup :
const initialState = {
city : {
mycity: true
}
};
const reducer = (state = initialState, action)=>{
switch (action.type) {
case "changeCity":
return Object.assign({}, state, {
mycity: action.payload.mycity
})
default:
return state;
}
}
const Store = createStore(reducer);
I am stuck in sidemenu. How to dispach in mapDispatchToProps method:
How to pass action in mapDispatchToProps in sidemenu?
If my assumptions on what your Switch component does is correct, it would trigger the onValueChange event-listener when you pass in this.state.city to the value prop. You end up calling this.props.toggleCity() to dispatch your changeCity action. I think the set-up is correct for here...
However, it looks like your reducer is expecting an action.payload which you never passed in as part of the action.
const reducer = (state = initialState, action)=>{
switch (action.type) {
case "changeCity":
return Object.assign({}, state, {
mycity: action.payload.mycity
})
default:
return state;
}
}
So yes the dispatch is working correctly, but you are not passing all the necessary data for your reducer to return a new piece of state.
You need to update your mapDispatchToProps, your event-handler and your reducer to something like
<Switch onValueChange={(flag) => {
this.props.toggleCity(this.state.city);
} value={this.state.city} />
const mapDispatchToProps = dispatch => {
return {
toggleCity: (myCity) => {
dispatch({ type: "changeCity", payload: myCity })
}
};
};
export default connect(null, mapDispatchToProps)(SideMenuScreen);
Your reducer also seems to have an extra key, you don't need to access the mycity prop in payload if its already the payload. Update to:
const reducer = (state = initialState, action)=>{
switch (action.type) {
case "changeCity":
return Object.assign({}, state, {
mycity: action.payload
})
default:
return state;
}
}
Adding on, if you want your Hone component to re-render with the new data in your redux-state, you can do something like this.
In your HomeScreen component, make use of a state-variable to save your abudhabi or whatever city-value and call componentDidUpdate() to setState and re-render your component.
class HomeScreen extends React.Component{
state = {
abudhabi: false
}
//when the component gets the new redux state this will trigger
componentDidUpdate(prevProps){
if(this.props.abudhabi !== prevProps.abudhabi){
this.setState({
abudhabi: this.props.abudhabi
})
}
}
}
Related
So I simplified my code but basically I have a straight-forward redux store:
/* store.js */
import { createStore } from 'redux';
const reducer = (state = {}, action) => {
if (action.type === 'action') state.data = data;
return state;
}
const store = createStore(reducer);
store.subscribe(() => {
console.log(store.getState()); // returns the right state, updates properly
});
export default store;
A Loader that that pulls the data from the server and dispatches it to the store:
/* Loader.js */
class Loader {
dispatch (allDocuments) {
store.dispatch({
type: 'action',
data: data
});
}
async fetchData () {
try {
const allDocuments = await ajaxCall('GET', '/fetchData');
this.dispatch(allDocuments);
return allDocuments;
} catch (e) {
console.error(e);
}
}
}
export default Loader;
And then this is my App.js file where I fire the Loader fetch method every 5 seconds and map the store state to a React component:
/* App.js */
import Loader from './Loader';
const loader = new Loader();
setInterval(async () => {
await loader.fetchData();
}, 5000);
const App = ({
data
}) => {
console.log(data); //doesn't update
return (
<div>
<p>{data}</p>
</div>
)
};
const mapStateToProps = state => ({data: state.data,})
export default connect(mapStateToProps)(App);
So the problem here is that the component does not update. Loader dispatches properly, and the redux store does get updated but the data prop in App remains an empty object, and doesn't refire the render method.
Why is mapStateToProps not updating the component when the store state changes?
mapStateToProps expects that you will not mutate the state. The problem is your reducer, which is mutating the state variable by assigning directly to state.data.
To avoid mutating the state, you'll want to return a new copy of the object whenever you change the data. Like this:
const reducer = (state = {}, action) => {
if (action.type === 'action') {
return {
...state,
data: action.payload
}
return state;
}
Of course if you only have one type of action than redux is not the right tool for the job.
Your reducer doesn't save the action.data payload. It also isn't returning a new state object reference.
const reducer = (state = {}, action) => {
if (action.type === 'action') state.data = data; // <-- mutation
return state;
}
When the action type matches then you should return a new state object reference with the action.data payload.
const reducer = (state = {}, action) => {
if (action.type === 'action') {
return {
...state,
data: action.data;
};
}
return state;
}
I am having a code that looks like this
reducer
const initState = { isLoggedIn: false };
const isLoggedInReducer = (state = initState, action) => {
switch (action.type) {
case "LOG_IN":
return { isLoggedIn: true };
case "LOG_OUT":
return { isLoggedIn: false };
default:
return {isLoggedIn:false};
}
};
export default isLoggedInReducer;
action
export const logIn = () => {
return {
type:'LOG_IN'
}
}
export const logOut = () => {
return {
type:'LOG_OUT'
}
}
screen
import React from 'react'
import {useDispatch,useSelector} from 'react-redux'
import {logIn , logOut} from '../redux/actions/isLoggedInAction'
const AuthScreen = () => {
console.log('auth page re-rendered')
let status = useSelector(state => state.isLoggedIn)
console.log(status)
const dispatch = useDispatch()
return <>
<h1> auth is {status} status</h1>
<button onClick={()=>dispatch(logIn())}>authenticate me</button>
<button onClick={()=>dispatch(logOut())}>un auth me</button>
</>
}
export default AuthScreen
The problem is, something causes the app to render twice, and update the store
The variable should not have changed unless I dispatch an action, which I clearly did not. Also the value of the variable is logged out but doesnt print inside the h1 tag.
If I change the default case of the reducer to something like
const initState = { isLoggedIn: false };
const isLoggedInReducer = (state = initState, action) => {
switch (action.type) {
case "LOG_IN":
return { isLoggedIn: true };
case "LOG_OUT":
return { isLoggedIn: false };
default:
return {isLoggedIn:' hello world'};
}
};
export default isLoggedInReducer;
Then I get this output
The above output suggests that the default case was somehow run. But again, I did not dispatch any action to it. I am only reading the data using the "useSelect" but something is dispatching actions that I dont know about.
I am very new to redux and trying to learn. Thanks for your time.
In your default case, return the state as is:
default:
return state;
If you return a new object, React will treat the state as having changed and rerender the component, as equality is checked by ref by default.
I use mapStateToProps to get an nested Object from an object by Id. The problem is, the props don't get updated and componentDidUpdate won't fire when the redux store state changes.
Here are my reducers:
export const programmReducers = (state = initialState, action) => {
let programms = state.programms;
switch (action.type) {
case actionTypes.FETCH_CATEGORIES:
return Object.assign({}, state, {
categories: action.payload
})
case actionTypes.FETCH_PROGRAMM:
programms[action.payload.id] = action.payload;
console.log(programms);
return {
...state,
programms: Object.assign({}, programms)
}
case actionTypes.FETCH_PROGRAMM_COMPONENTS:
programms[action.programmId].components = action.payload;
console.log('Added Components')
return {
...state,
programms: Object.assign({}, programms)
}
case actionTypes.FETCH_PROGRAMM_SECTIONS:
programms[action.programmId].sections = action.payload;
console.log('Added Sections')
return {
...state,
programms: Object.assign({}, programms)
}
default:
return state
}
}
Here is my components:
class ProgrammPage extends Component {
static async getInitialProps({ store, query: {id} }) {
if (!store.getState().programm.programms[id]) {
console.log('Programm not! found');
await store.dispatch(loadProgramm(id));
await store.dispatch(loadProgrammComponents(id));
} else {
console.log('Programm found')
}
return {
programmId: id
}
}
constructor(props) {
super(props);
if (this.props.user) {
console.log('Loading init!');
this.props.loadProgrammComponents(this.props.programmId)
this.props.loadProgrammSections(this.props.programmId);
}
}
componentDidUpdate(prevProps) {
console.log('Update')
if (!prevProps.user && this.props.user) {
console.log('Loading update');
this.props.loadProgrammComponents(this.props.programmId);
this.props.loadProgrammSections(this.props.programmId);
}
}
render() {
return (
<div>
<h1>Programm</h1>
<h2>{this.props.programm.name}</h2>
<h2>{this.props.programm.id}</h2>
<h3>Components: {this.props.programm.components ? this.props.programm.components.length : 'None'}</h3>
<h3>Sections: {this.props.programm.sections ? this.props.programm.sections.length : 'None'}</h3>
<br></br>
<h1>User: { this.props.user ? this.props.user.uid : 'None'}</h1>
<button onClick={() => this.props.loadProgramm('ProgrammLevel2')}>Load Programm</button>
<button onClick={() => this.props.loadProgrammComponents(this.props.programmId)}>Load Components</button>
</div>
)
}
}
function mapStateToProps(state, ownProps) {
return {
programm: state.programm.programms[ownProps.programmId],
// programms: state.programm.programms <--- Fixed the problem
user: state.auth.user
}
}
const mapDispatchToProps = dispatch => bindActionCreators({
loadProgramm,
loadProgrammComponents,
loadProgrammSections
}, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProgrammPage)
When the Reducer for FETCH_PROGRAMM_COMPONENTS or FETCH_PROGRAMM_SECTIONS changes the redux state, componentDidUpdate isn't called and the component doesn't dispay the changes.
The problem seems to be related to the mapStateToPropsmethod, because, when I add programms: state.programm.programms everything works fine. However I don't need the whole programms object.
Why are doesn't the component recognize that the programm has updated when I map only a nested object to my props?
Your problem is within the programmReducers, your component doesn't rerender because you don't change the state.
After changing mapStateToProps you need to make changes in your component.
The next code probably breaks when you change programms: state.programm.programms to programm: state.programm.programms[ownProps.programmId]
export const programmReducers = (state = initialState, action) => {
let programms = state.programms;
...
}
So I'm guessing your reducers aren't doing what you intended.
I have the following React component connected to the Redux store, and even though the state of the store changes (I checked), the component prop userIsLogged won't change its value. Any help is appreciated!
const mapDispatchToProps = (dispatch) => bindActionCreators({deauthenticateUser}, dispatch);
const mapStateToProps = (state) => ({ userIsLogged: state.auth.loggedUser !== null });
const Logout = (props) => {
const { userIsLogged } = props;
return (
userIsLogged?
<Button
variant="outlined"
color="primary"
onClick={(e) => {
props.deauthenticateUser();
history.push('/login');
}}>
Exit
</Button>
:<div />
);
}
Logout.propTypes = {
userIsLogged: PropTypes.bool.isRequired
};
export default connect(mapStateToProps, mapDispatchToProps)(Logout);
The reducer is as follow:
const initialState = {
jwt: null,
loggedUser: null,
isLoading: false
}
export default function auth(state = initialState, action) {
switch (action.type) {
case 'GOT_JWT':
return Object.assign(state, { jwt: action.jwt });
case 'USER_LOGGING_IN':
return Object.assign(initialState, { isLoading: action.isLoading });
case 'USER_LOGGED_IN':
return Object.assign(state, { loggedUser: action.loggedUser, isLoading: false });
case 'NO_JWT':
return initialState;
case 'USER_LOGGED_OUT':
return initialState;
default:
return state;
}
}
In your reducer code you're mutating the passed state object.
What happens next is that react treats the state as unchanged (it's the same object), hence it does not re-render it.
To fix it change the
Object.assign(state, { jwt: action.jwt });
to
Object.assign({}, state, { jwt: action.jwt });
It would create a new object and copy properties from the original state + the new ones.
Hi when i console log my components props (passed down from redux) i get the initial state which is null. however using the react inspector i have the result of the axios request. I tried reading dozens of similar problems but cannot seen to resolve my issue.
Actions
import { searchService } from '../api/searchService';
export const actions = {
FETCH_USERS: 'FETCH_USERS',
}
export const searchUsers = () => dispatch => {
searchService.get('/search')
.then((result) => {
dispatch({
type: actions.FETCH_USERS,
payload: result
})
})
}
Reducers
import { actions } from '../actions';
export default (state = null, action) => {
switch(action.type) {
case actions.FETCH_USERS:
return action.payload;
default:
return state;
}
}
Search Component
function mapStateToProps ({search}) {
return {search};
}
const mapDispatchToProps = dispatch => ({
searchUsers: () => dispatch(searchUsers())
});
export default connect(mapStateToProps, mapDispatchToProps)(withAuth()(Search));
Your problem is in the Reducer
First you should make an initial state, and then you need to edit this state in order for redux to feel the changes and update
Check the code below and let me know if it worked for you.
import { actions } from '../actions';
const INITIAL_STATE= {search: ""};
export default (state = INITIAL_STATE, action) => {
switch(action.type) {
case actions.FETCH_USERS:
return {...state, search: action.payload};
default:
return state;
}
}