I have these Reducers:
const initialState = {
categories: [],
programms: {}
}
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: programms
}
default:
return state
}
}
The last one (FETCH_PROGRAMM_COMPONENTS) adds an array to an object in the programm object. This works but somehow it won't fire componentDidUpdate in my component. It works for FETCH_PROGRAMM though.
class ProgrammPage extends Component {
static async getInitialProps({ store, query: {id} }) {
let programm;
if (!store.getState().programm.programms[id]) {
console.log('Programm not! found');
programm = await store.dispatch(loadProgramm(id));
await store.dispatch(loadProgrammComponents(id));
} else {
programm = store.getState().programm.programms[id];
console.log('Programm found')
}
return {
// programm: programm
programmId: id
}
}
componentDidUpdate(prevProps) {
console.log('UPDATE', this.props, this.props.programm.components.length)
if (!prevProps.user && this.props.user) {
this.props.loadProgrammComponents(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>
<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) {
return {
programm: state.programm.programms['ProgrammLevel1'],
programms: state.programm.programms,
user: state.auth.user
}
}
const mapDispatchToProps = dispatch => bindActionCreators({
loadProgrammComponents,
loadProgramm
}, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProgrammPage)
You returning the same reference.
Try returning a copy of programms array: [...programms] ( or Object.assign() if it's an Object).
case actionTypes.FETCH_PROGRAMM_COMPONENTS:
programms[action.programmId].components = action.payload;
console.log('Added Components')
return {
...state,
programms: [...programms] // <-- Return new state
}
Related
I am trying to use Redux in my app, but something went wrong and I am currently get a mistake as props.fetchData is not a function. I tried to do it with useEffect but it did not work too.
My provider is set in App.js where I create store and give to other components
My App Code
export function Header(props) {
console.log(props)
props.fetchData('https://api.coincap.io/v2/assets');
return (
<ul>
{props.items.map((item) => (
<li key={item.id}>
{item.name}
</li>
))}
</ul>
);
}
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (url) => dispatch(itemsFetchData(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Header);
my store
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
}
my actions
export function itemsHasErrored(bool) {
return {
type: 'ITEMS_HAS_ERRORED',
hasErrored: bool
};
}
export function itemsIsLoading(bool) {
return {
type: 'ITEMS_IS_LOADING',
isLoading: bool
};
}
export function itemsFetchDataSuccess(items) {
return {
type: 'ITEMS_FETCH_DATA_SUCCESS',
items
};
}
export function itemsFetchData(url) {
return (dispatch) => {
dispatch(itemsIsLoading(true));
fetch(url)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(itemsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(itemsFetchDataSuccess(items.data)))
.catch(() => dispatch(itemsHasErrored(true)));
};
}
reducers
export default combineReducers({
items,
itemsHasErrored,
itemsIsLoading
});
export function itemsHasErrored(state = false, action) {
switch (action.type) {
case 'ITEMS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
export function itemsIsLoading(state = false, action) {
switch (action.type) {
case 'ITEMS_IS_LOADING':
return action.isLoading;
default:
return state;
}
}
export function items(state = [], action) {
switch (action.type) {
case 'ITEMS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
I wonder what I am doing wrong here. The dispatch methods are dispatching correct values but the state object is showing wrong values.
{ name: "name", room: "room" } is what I am dispatching separately. But the state is showing { name: "room": room: "" }
Google chrome logs :
NOTE: please checkout the code here from the github repo incase needed.
Reducer:
export const initialState = {
name: '',
room: ''
}
export const reducer = (state, action) => {
console.log("Calling action", action);
switch (action.type) {
case types.SET_NAME:
return { ...state, name: action.name }
case types.SET_ROOM:
return { ...state, name: action.room }
default:
return state;
}
}
_app component:
import DataProvider from "../context/DataProvider";
import { initialState } from '../reducers/index';
import { reducer } from '../reducers';
const AppComponent = ({ Component, pageProps }) => {
return (
<DataProvider intialState={initialState} reducer={reducer}>
<Component {...pageProps} />
</DataProvider>
)
}
AppComponent.getInitialProps = async (appContext) => {
let pageProps = {};
if (appContext.Component.getInitialProps) {
pageProps = await appContext.Component.getInitialProps(appContext.ctx);
}
return { pageProps }
}
export default AppComponent;
Component:
const Join = () => {
const [name, setName] = input('');
const [room, setRoom] = input('');
const [state, dispatch] = useContext(DataContext);
const submit = (e) => {
if (name === '' || room === '') {
e.preventDefault();
return;
}
dispatch({
type: types.SET_NAME,
name
});
dispatch({
type: types.SET_ROOM,
room
});
}
return (
<div>
<h1>Join</h1>
<input onChange={(e) => setName(e)} placeholder="name" />
<input onChange={(e) => setRoom(e)} placeholder="room" />
<Link href="/chat">
<button type="submit" onClick={(e) => submit(e)}>Submit</button>
</Link>
</div>
)
}
Chat component (where I am consuming state):
const Chat = () => {
// const backendEndpoint = 'http://localhost:5000';
const [state, dispatch] = useContext(DataContext);
console.log('STATE', state)
return <h1>Chat</h1>
}
Chat.getInitialProps = async (ctx) => {
return {}
}
export default Chat;
I think the problem is in your reducer
case types.SET_ROOM:
return { ...state, name: action.room }
Here you change the name in rooms action
maybe you need to update like this
return { ...state, room: action.room }
actually u make a mistake in your Reducer.js
export const reducer = (state, action) => {
console.log("Calling action", action);
switch (action.type) {
case types.SET_NAME:
// equals state.name = action.name
// state = { name: 'name', room: '' }
return { ...state, name: action.name }
case types.SET_ROOM:
// equal state.name = action.room
// state = { name: 'room', room: '' }
return { ...state, name: action.room }
default:
return state;
}
}
// u can change your code style to reduce mistakes
export const reducer = (state, action) => {
const {name, room} = action
switch (action.type) {
case types.SET_NAME:
return { ...state, name }
case types.SET_ROOM:
return { ...state, room }
default:
return state;
}
}
Not able to call function inside a component, provided by Redux.
This is my Container component
export class HomeContainer extends React.Component {
render() {
return(
<Home
getData={ this.props.getData }
/>
)
}
}
const mapStateToProps = state => {
return state;
}
const mapDispatchToProps = dispatch => {
return bindActionCreators({
getData: getCountriesList.getCountriesByISO
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(HomeContainer);
I am calling that redux function inside the component using
componentDidMount() {
this.props.getData();
}
This is my Action
export function getCountriesByISOData() {
return (dispatch) => {
dispatch(toggleLoading(true));
return fetch('/api/countries', {
method: 'GET',
}).then(res => {
if(res.status === 200) {
return res.json().then(res=> {
console.log(res)
dispatch(toggleLoading(false));
dispatch(isSuccess(res));
})
} else {
dispatch(toggleLoading(false));
dispatch(isError(res.statusText));
}
}).catch(error => {
dispatch(toggleLoading(false));
dispatch(isError(error));
})
}
}
and this is my reducer
let defaultState = {
isLoading: false,
data: [],
error: ''
}
const getCountriesByISOReducer = (state=defaultState, action) => {
if(action.type === 'COUNTRIES_ISO_TOGGLE_LOADING') {
return {
...state,
isLoading: action.isLoading
}
}
if(action.type === 'COUNTRIES_ISO_IS_ERROR') {
return {
...state,
error: action.error
}
}
if(action.type === 'COUNTRIES_ISO_IS_SUCCESS') {
return {
...state,
data: [...state.data, action.success]
}
}
return state;
}
export default getCountriesByISOReducer;
This is my root reducer
export default combineReducers({
globalConfirmedCases: globalConfirmedCasesReducer,
dailyStatsByDate: getDailyStatsByDateReducer,
dailyStats: getDailyStatsReducer,
getCountriesByISO: getCountriesByISOReducer,
getCountriesStats: getCountriesStatsReducer
});
What I am doing wrong here? Is there any naming conventions while writing reducers and actions and using it in the react context?
i have this code from https://jsbin.com/zirugiteju/edit?js,console,output
but i have a question, what is {store.getState().todos} its in the bottom of the code, see please this part
const render = () => {
ReactDOM.render(
this is the code complete
const todo = (state, action) => {
switch(action.type) {
case 'ADD_TODO':
console.log("ADD");
return {
id : action.id,
text: action.text,
completed: false
};
break;
case 'TOGGLE_TODO':
if (state.id !== action.id ) {
return state;
}
return {
...state,
completed: !state.completed
};
break;
default:
return state;
}
};
const todos = (state=[], action) => {
switch(action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
];
break;
case 'TOGGLE_TODO':
return state.map(t =>todo(t, action));
break;
default:
return state;
}
};
const visibilityFilter = (state='SHOW_ALL', action) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
break;
default:
return state;
}
};
const { combineReducers } = Redux;
const todoApp = combineReducers({
todos,
visibilityFilter
});
const { createStore } = Redux;
const store = createStore(todoApp);
const { Component } = React;
let nextTodoId = 0;
class TodoApp extends Component {
render() {
return (
<div>
<input ref={node => {
this.input = node
}} />
<button onClick={()=>{
store.dispatch({
type: 'ADD_TODO',
text: this.input.value,
id : nextTodoId++
});
this.input.value = '';
}}>
Add Todo</button>
<ul>
{this.props.todos.map(todo =>
<li key={todo.id}>
{todo.text}
</li>
)}
</ul>
</div>
);
}
}
const render = () => {
ReactDOM.render(
<TodoApp
todos={store.getState().todos}
/>,
document.getElementById('root')
);
};
store.subscribe(render);
render();
The prop todos = {store.getState().todos} that you are passing to TodoApp component is a reducer, called todos, from your redux state that you combined in this part of the code:
const todoApp = combineReducers({
todos,
visibilityFilter
});
The getState() function returns the current state tree of your application. It is equal to the last value returned by the store's reducer.
You can learn more about redux States in this link: https://redux.js.org/api/store
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.