I am trying to update the state of an item in the store.
This is working but it is not returning the state in the format I am looking for.
The state looks like this:
state : {
watchList: [
{
movie: {
'name' : 'Batman'
}
}
]
}
however, I have been attempting to make my state look like this (i.e. not have another object inside the first object, but just an array of objects).
state : {
watchList: [{'name' : 'Batman'}. {'name': 'Superman'}]
}
My reducer looks like this:
export default (state = [], action) => {
switch(action.type) {
case 'MOVIE_ADDED_TO_LIST':
return [
...state,
{
movie: movie.event
}
];
default:
return state;
}
};
and my action looks like this:
export const addMovieToList = (movie) => {
return {
type: 'MOVIE_ADDED_TO_LIST',
movie
};
};
And here is how I am mapping stateToProps.
function mapStateToProps(state, props) {
return {
WatchListEvents: state.watchList
}
}
export default connect(mapStateToProps)(WatchList);
export default (state = [], action) => {
switch(action.type) {
case 'MOVIE_ADDED_TO_LIST':
return [
...state,
movie.event
];
default:
return state;
}
};
Related
I am like in a strange problem. The problem is that I am trying to make an API hit (in service file) which in turn provides some data (it is working), this data is to be updated in my reducer1.js and then returned. Now, my issue is though the value is coming in reducer file, but is not returned, so in turn, state is not changed, and in turn my end component is not rerendered.
Now, when my service file is successfully hitting and then returning data to my reducer1.js, why in the world the updated-state is not returned by "GET_List" action type? Can someone see any problem?
index.js (service file)
const global = {
getActressList: async function(){
const response = await fetch("http://localhost:2000/api/actressList");
const data = await response.json();
return data;
}
}
export default global;
reducer1.js
import global from '../../services/index';
const initialState = {
data: [
{
id: 1,
name: "Aishwarya Rai",
src: "/assets/img/aishwarya.png"
}
]
};
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
const data = global.getActressList();
data.then((res)=> {
return {
...state,
data: res
}
})
}
default:
return state;
}
}
export default reducer1;
Result:
You are returning from a promise not from a reducer function:
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
const data = global.getActressList();
data.then((res) => {
// here you are returning from a promise not from a reducer function
return {
...state,
data: res,
};
});
}
default:
return state;
}
}
The code in reducer should be sync like this:
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
return {
...state,
data: action.payload,
};
}
default:
return state;
}
}
And your data fetching should be moved to component effect like this:
function YourComponent() {
const dispatch = useDispatch();
const data = useSelector(state => state.data)
useEffect(() => {
const data = global.getActressList();
data.then((res) => {
dispatch({type: 'GET_LIST', payload: res});
});
}, [])
...
}
EDIT
If you use class components the fetching logic should be placed in componentDidMount lifecycle hook like this:
class YourComponent extends Component {
state = { data: [] };
componentDidMount() {
const data = global.getActressList();
data.then((res) => {
dispatchYourAction({type: 'GET_LIST', payload: res});
});
}
...
}
If I fetch this array of restos with redux:
[{
res_id: Int,
res_name: String,
res_category: String,
res_category_id: Int,
city_id: Int
}]
My action looks something like this:
export const getrestos = () => {
const resData = await response.json();
dispatch({
type: GET_RESTOS,
payload: resData
});
};
};
export const setFilters = filterSettings => {
console.log(filterSettings);
return { type: SET_FILTERS, filters: filterSettings };
};
And this is my reducer:
import { GET_RESTOS, SET_FILTERS } from '../actions/restos';
const initialState = {
restoList: [],
filteredRestos: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_RESTOS:
return {
restoList: action.payload
}
case SET_FILTERS:
const appliedFilters = action.filters;
const updatedFilteredRestos = state.restoList.filter(resto => {
if (appliedFilters.cityID || resto.city_id) {
resto => resto.city_id.indexOf(cityID) >= 0
return { ...state, filteredRestos: updatedFilteredRestos };
}
});
return { ...state, filteredRestos: updatedFilteredRestos };
default:
return state;
}
};
I have touchable categorys in a page, and when i touch one i want to fetch the corresponding restos for that category and show them in a flatlist. Apart from that i want to have a search bar that when I type I want to show restos by res_name and/or by res_category.
Ive tried to create selectors, but I dont understand how, i dont need an specific approach, but the most clean or efficient as possible.
Thanks in advance if anyone can give me a hint or solution!
EDIT
The problem is im getting undefined in updatedFilteredRestos.
Your reducers should be clean, dumb and all they do should be returning objects. This makes your components more testable and errors easier to catch. In my opinion, this is a perfect use-case for reselect. Here's a medium article: https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c But the true beauty of reselect is that it will memoize for you, i.e. if your states don't change, it uses a cached version of the data.
Anyway, you should clean up your restoReducer to something to this effect.
import { GET_RESTOS, SET_FILTERS } = "../actions/restos";
const initialState = {
restoList: [],
filteredRestos: []
};
const restoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_RESTOS:
return { ...state, restoList: action.payload };
case SET_FILTERS:
return { ...state, filteredRestos: action.payload };
default:
return state;
}
}
Then write your filtered resto selector:
// ../selectors/restos
import { createSelector } from "reselect";
// First, get your redux states
const getRestos = (state) => state.restos.restoList;
const getFilteredRestos = (state) => state.restos.filteredRestos;
// Next, create selectors
export const getFilteredRestoList = createSelector(
[getRestos, getFilteredRestos],
(restoList, filteredRestos) => {
// need to check for non-empty filters
// if it is, simply return the unfiltered `restoList`
if(!Array.isArray(filteredRestos) || !filteredRestos.length)
return restoList || [];
// If you do have valid filters, return filtered logic
return restoList.filter(r => filteredRestos.some(f => f.cityID === r.city_id));
);
Then, use this selector in your components:
// ../components/my-app
import { getFilteredRestoList } from "../selectors/restos";
// hook it up to your `mapStateToProps` as you would a normal state
// except this time, it's a special selector
const mapStateToProps = (state, ownProps) => {
restoList: state.restos.restoList,
filteredRestos: state.restos.filteredRestos,
filteredRestoList: getFilteredRestoList(state) //<-- this is your selector
}
Then inside your component, just reference it: this.props.filteredRestoList.
I'm setting up actions and reducers in my react-redux app. I need a function to update a property in the state and add objects to its list, if possible with the spread syntax. Here's what I have so far:
const defaultState = {
genres: {}
}
export default function(state = defaultState, action) {
switch(action.type) {
case 'ADD_GENRE':
return {
...state,
genres[action.name]: action.list //new code here
}
default:
return state;
}
}
I need the genres property to be dynamically accessible like an array using its property name like so:
const getMusicFromGenre = (genre) => {
return state.genres[genre];
}
The reducer should accept the following action, then modify the state accordingly:
// action
{
type: 'ADD_GENRE,
name: 'Rock',
list: ['Bohemian Rhapsody', 'Stairway to Heaven', 'Hotel California']
}
// old state
{
genres: {
"Pop": ['Billie Jean', 'Uptown Funk, 'Hey Jude']
}
}
// new state
{
genres: {
"Pop": ['Billie Jean', 'Uptown Funk, 'Hey Jude'],
"Rock": ['Bohemian Rhapsody', 'Stairway to Heaven', 'Hotel California']
}
}
I'm willing to use a different approach if necessary.
You're on the right track, but need to handle each level of nesting separately. Here's an example I wrote for http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html :
function updateVeryNestedField(state, action) {
return {
....state,
first : {
...state.first,
second : {
...state.first.second,
[action.someId] : {
...state.first.second[action.someId],
fourth : action.someValue
}
}
}
}
}
You may also want to read some of the articles on immutable data handling that I have linked at http://redux.js.org/docs/recipes/reducers/PrerequisiteConcepts.html#immutable-data-management and https://github.com/markerikson/react-redux-links/blob/master/immutable-data.md .
immutability-helper is a very useful library for doing state updates. In your situation it would be used like this, which will create a new array if there are no existing items, or concat the existing items with the action's list if there are pre-existing items:
import update from 'immutability-helper';
const defaultState = {
genres: {}
}
const createOrUpdateList = (prev, list) => {
if (!Array.isArray(prev)) {
return list;
}
return prev.concat(list);
// or return [...prev, ...list] if you prefer
}
export default function(state = defaultState, action) {
switch(action.type) {
case 'ADD_GENRE':
return update(state, {
genres: {
[action.name]: {
$apply: prev => createOrUpdate(prev, action.list)
}
}
});
default:
return state;
}
}
I want to incorporate ImmutableJS to my React app. So now my reducer looks like this:
import { Map } from 'immutable'
const initialState = Map({
data: [],
sections: []
});
const AppState = (state = initialState, action) => {
switch (action.type) {
case 'ADD_CONTENT':
return state.set('data', action.response);
case 'ADD_SECTION':
return state.set('sections', [ ...state.get('sections'), action.payload ]);
default:
return state;
}
};
export default AppState;
And this is all fine and good. But in my component I have mapStateToProps:
const mapStateToProps = (state) => {
const d = state.app.get('data');
return {
works: d.works,
pages: d.pages,
sections: state.app.get('sections')
}
};
and then I want to display the data:
{
this.props.works.map(d => {
return <div>d</div>
})
}
And I am getting
TypeError: this.props.works.map is not a function
When I console.log instead, I am getting that this.props.works is undefined. What can I do?
Since you're using Immutable JS
In your mapStateToProps
const mapStateToProps = (state) => {
const d = state.app.get('data');
return {
works: d.works,
...
To access data from d, change the way you access data, From d.works to d.get('works')
return {
works: d.get('works'),
pages: d.get('pages'),
sections: state.app.get('sections')
}
You are calling d.works but it is not defined. I think in your action when you pass in your initial state you need to also defined works
const initialState = Map({
data: [
works: [],
pages: []
],
sections: []
});
I'm having surprisingly difficult time figuring this out, essentially I'm trying to set state to initial state, so far I tried:
// -- Initial state ------------------------------------------------------------
const INITIAL_STATE = {
search: {
listings: []
},
listings: []
}
// -- Story structure for story editor -----------------------------------------
export default function(state = INITIAL_STATE, action) {
switch(action.type) {
case ACTIONS.RESET_STATE:
return { ...state, INITIAL_STATE }
default:
return state;
}
}
this just adds initial state to existing one
case ACTIONS.RESET_STATE:
return { ...state, state = INITIAL_STATE }
this returns error
case ACTIONS.RESET_STATE:
return { ...state, state: INITIAL_STATE }
this is adding initial state to existing one gain
case ACTIONS.RESET_STATE:
return { ...state, search: { listings:[] }, listings: [] }
This works, but I start getting weird mutation errors.
The proposed solution of Anders is right, but has potential problem with immutables. This generates always new object.
case ACTIONS.RESET_STATE:
return { ...INITIAL_STATE };
Look at Jiri Fornous solution instead, as this will mutate your data.
An even easier way is to just return INITIAL_STATE.
case ACTIONS.RESET_STATE:
return INITIAL_STATE;
If you simply want to reset state completely, just return the value of INITIAL_STATE:
export default function(state = INITIAL_STATE, action) {
switch(action.type) {
case ACTIONS.RESET_STATE:
return {
search: {
listings: []
},
listings: []
};
default:
return state;
}
}
If you want to keep the INITIAL_STATE in a single place. Change the initial state creator to a function:
function get_INITIAL_STATE => {
return { search: {
listings: []
},
listings: []
}
}
export default function(state = get_INITIAL_STATE(), action) {
switch(action.type) {
case ACTIONS.RESET_STATE:
return get_INITIAL_STATE();
default:
return state;
}
}