Fix Spread syntax error in redux - redux

I am a novice in Redux and I am creating a react book app with redux for state management. As far as I read about redux, it is recommended to use spread syntax to prevent state mutation. In my case, I have 2 default books and I want to output them when the program gets starting. Something goes wrong with the return in case 'GET_BOOK' because I got a syntax error. Any recommendation to reduce my code in bookReducers, I am highly appreciated
bookReducers.js:
let defaultBooks=[{
id:1,
title:'First Book',
description:'This is first book description',
price:33,
currency:'Euro'
},
{
id:2,
title:'Second Book',
description:'This is second book description',
price:24.50,
currency:'Euro'
}];
// Book reducer
export function bookReducer(state={books:defaultBooks} , action){
switch (action.type) {
case 'GET_BOOK':
return {...state, books:[...state.books]};
break;
case 'POST_BOOK':
// return state= action.load;
return {books:[...state.books,...action.load]};
break;
case 'DELETE_BOOK':
const indexToDelete=[...state.books].findIndex((book)=>{return book.id===action.load.id;})
return {books:[...state.books.slice(0,indexToDelete),...state.books.slice(indexToDelete+1)]};
break;
case 'UPDATE_BOOK':
const indexToUpdate=[...state.books].findIndex((book)=>{return book.id===action.load.id});
const newBook={
id:state.books[indexToUpdate],
title:action.load.title,
description:action.load.description,
price:action.load.price,
currency:action.load.currency
};
console.log(newBook);
return {books:[...state.books.slice(0,indexToUpdate),newBook,...state.books.slice(indexToUpdate+1)]};
break
}
return state
}
action.js
export function getBooks(){
return{
type:'GET_BOOK'
}
}
// add books
export function postBooks(book){
return{
type:'POST_BOOK',
load:book
}
}
// delete a book
export function deleteBook(id){
return{
type:'DELETE_BOOK',
load:id
}
}
// Update a book
export function updateBook(book){
return{
type:'UPDATE_BOOK',
load:book
}
}
The error occurs in return {...state, books:[...state.books]}; in case GET_BOOK in bookReducers. And here is my error:

You don't need a Redux action to get books, just have your component in react that connects to redux get the books from the state.
For example:
const library = (props) => {
// You can access your books here with this.props.books
}
const mapStateToProps = (state) => {
return {books: this.state.books}
}
const mapDispatchToProps = () => {}
export default connect(mapStateToProps, mapDispatchToProps)(library)
Edit:
Your interaction with Redux seems like you are trying to mock a RESTful api, you really don't need to do this, you can just have update, add, and delete book actions. If you want initial books in the state then add them when you create the store with the preloaded state parameter.

Related

Why my Redux App return that [ Immer ] error?

I don't know. Why even I added my push function on my object to return my new result, The app is printing error on my console.log.
slice.js
import { createSlice } from '#reduxjs/toolkit';
import { pushProduct } from '../commons/push';
export const slice = createSlice({
name: 'initial',
initialState : {
product: [],
},
reducers: {
ADDS(state, actions) {
return {
...state,
product: pushProduct(state.product, actions.payload),
console1: console.log('State: ', state.product),
console2: console.log('Actions: ', actions.payload),
}
}
}
});
export const { ADDS } = slice.actions;
export default slice.reducer;
push.js
// Push new prpduct to the cart
export const pushProduct = (initial, productSelect) => { return initial.push(productSelect) };
console.log error
errors.ts:49 Uncaught Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.
Thank You
Per the error message: Immer lets you update the state in two ways. One is "mutating" the existing state, and the other is returning a new value. But, you can only do one of those at a time.
You're trying to do both. You have return {...state}, but you also have pushProduct() which sounds like it's mutating.
The best answer here is to not try to do return {...state} at all, and just "mutate" the existing state.
See https://redux-toolkit.js.org/usage/immer-reducers#mutating-and-returning-state for more details.

how do I migrate from redux to redux toolkit

I managed to write reducer using createSlice but the action seems to be confusing.
My old reducer :
function listPeopleReducer(state = {
getPeople:{}
}, action){
switch (action.type) {
case D.LIST_PEOPLE: {
return {
...state
, getPeople:action.payload
}
}
default:{}
}
return state
}
By using createSlice from the redux toolkit, I migrated the reducer to this,
const listPeopleReducer = createSlice({
initialState:{getPeople:{}},
name:"listPeople",
reducers:{
listPeople(state,action){
return {
...state,
getPeople : action.payload
}
}
}
})
My old action, makes an api call inside it, with the help of a helper function makeApiRequest (which takes in parameters and returns the response of the api),
export function listPeople(config: any) {
return function (dispatch: any) {
makeApiRequest(config)
.then((resp) => {
dispatch({
type : D.LIST_PEOPLE,
payload : resp.data
})
})
.catch((error) => {
dispatch({
type : D.LIST_PEOPLE,
payload : error
})
})
}
}
With reduxtool kit, we could do something like,
const listPeople = listPeopleReducer.actions.listPeople;
But, how will I write my custom action that contains the helper function makeApiRequest ?
i.e The old Action should be migrated to reduxtoolkit type.
It's definitely tricky when migrating, since there are some major conceptual changes that you must eventually wrap your head around. I had to do it a couple of times before it clicked.
First, when you are creating const listPeopleReducer with createSlice(), that is not actually what you are creating. A slice is a higher level object that can generate action creators and action types for you, and allows you to export reducers and actions FROM it.
Here are the changes I would make to your code:
const peopleSlice = createSlice({
initialState:{getPeople:{}},
name:"people",
reducers:{
listPeople(state,action){
// uses immer under the hood so you can
// safely mutate state here
state.getPeople = action.payload
}
},
extraReducers:
// each thunk you create with `createAsyncThunk()` will
// automatically have: pending/fulfilled/rejected action types
// and you can listen for them here
builder =>
builder.addCase(listPeople.pending, (state,action) => {
// e.g. state.isFetching = true
})
builder.addCase(listPeople.fulfilled, (state,action) => {
// e.g. state.isFetching = false
// result will be in action.payload
})
builder.addCase(listPeople.rejected, (state,action) => {
// e.g. state.isFetching = false
// error will be in action.payload
})
}
})
Then, outside of your slice definition, you can create actions by using createAsyncThunk(), and do like:
export const listPeople = createAsyncThunk(
`people/list`,
async (config, thunkAPI) => {
try {
return makeApiRequest(config)
} catch(error) {
return thunkAPI.rejectWithError(error)
// thunkAPI has access to state and includes
// helper functions like this one
}
}
}
The "Modern Redux with Redux Toolkit" page in the Redux Fundamentals docs tutorial shows how to migrate from hand-written Redux logic to Redux Toolkit.
Your makeApiRequest function would likely be used with Redux Toolkit's createAsyncThunk, except that you should return the result and let createAsyncThunk dispatch the right actions instead of dispatching actions yourself.

Redux - Update store with same function from different files

being rather new to react.js + redux, I'm facing the following conundrum:
I have multiple files, which need to update the store in exactly the same way, based on the stores current state. Currently I simply copy-paste the same code (along with the needed mapStateToProps), which goes again DRY.
Similar to something like the below, where getData is an Ajax call living in the actions file and props.timeAttribute is coming from mapStateToProps:
props.getData(props.timeAttribute).then((newState) => {
console.log(newState)
})
Would a function like that go in the actions file? Can the current state be read from within that actions file? Or does one normally create some sort of helperFile.js in which a function like that lives and is being called from other files?
Thanks!
If your file is executing the same action, then yes, you would put the action creator in a separate file and export it. In theory, you can put state in an action by passing the state as a parameter, but the philosophy behind an action is that it announces to your application that SOMETHING HAPPENED (as denoted by the type property on the return value of the action function). The reducer function responsible for handling that type subsequently updates the state.
You can access the current state of the store inside of an action creator like this:
export const testAction = (someParam) => {
return (dispatch, getState) => {
const {
someState,
} = getState(); //getState gets the entire state of your application
//do something with someState and then run the dispatch function like this:
dispatch(() => {type: ACTION_TYPE, payload: updatedState})
}
I like this approach because it encapsulates all the logic for accessing state inside of the one function that will need to access it.
DO NOT modify the state inside of the action creator though! This should be read only. The state of your application should only be updated through your reducer functions.
Yes, it is recommended to maintain a separate file for your actions.
Below is an example of how i use an action to fetch information and dispatch an action.
export const fetchComments = () => (dispatch) => {
console.log("Fetch Comment invoked");
/*you can use your Ajax getData call instead of fetch.
Can also add parameters if you need */
return fetch(baseUrl + 'comments')
.then(response => {
if (response.ok){
return response;
}
else {
var error = new Error('Error ' + response.status + ': ' + response.statusText);
error.response = response;
throw error;
}
},
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then(comments => dispatch(addComments(comments)))
.catch(error => dispatch(commentsFailed(error.message)));
}
/* Maintain a separate file called ActionTypes.js where you can store all the ActionTypes as Strings. */
export const addComments = (comments) => ({
type : ActionTypes.ADD_COMMENTS,
payload : comments
});
export const comments = (errMess) => ({
type : ActionTypes.COMMENTS_FAILED,
payload : errMess
});
Once, you receive dispatch an action, you need an reducer to capture the action and make changes to your store.
Note that this reducer must be a pure function.
export const comments = (state = { errMess: null, comments:[]}, action) => {
console.log("inside comments");
switch (action.type) {
case ActionTypes.ADD_COMMENTS:
return {...state, errMess: null, comments: action.payload};
case ActionTypes.COMMENTS_FAILED:
return {...state, errMess: action.payload};
default:
return state;
}
};
Don't forget to combine the reducers in the configureStore().
const store = createStore(
combineReducers({
comments
}),
applyMiddleware(thunk,logger)
);
In your components where you use the Actions, use
const mapDispatchToProps = dispatch => ({
fetchComments : () => dispatch(fetchComments()),
})
Note to export the component as
export default connect(mapStateToProps,mapDispatchToProps)(Component);

Fetch data from API wtih Redux

Recently I've been looking into react and redux. I read up the official documentation and tried some ToDo List tutorials. Part 1 is just about react and this is part 2 about redux:
http://www.theodo.fr/blog/2016/03/getting-started-with-react-redux-and-immutable-a-test-driven-tutorial-part-2/
So basically he just sets up a store and initially adds an array of a few todos. Now I don't want my data to be local and I want to fetch it from an API. I'm having a hard time understanding how this actually works. So the code I would use in my action_creators.js is:
export function fetchData() {
return dispatch => {
fetch('http://127.0.0.1:8000/example')
.then(res => res.json())
.then(res => dispatch({
type: FETCH_DATA,
data: res
}))
}
}
Now in the example code for example adding a 'todo':
export function addItem(text) {
return {
type: 'ADD_ITEM',
text
}
}
You aren't dispatching anything, the tutorial does this in the reducer? But when you return dispatch your fetch, does this automatically get dispatched to your store?
If so I have no clue what I should write in my reducer ..
This is the code I have for adding a 'todo':
import {Map} from 'immutable';
function setState(state, newState) {
return state.merge(newState);
}
function fetchData(state) {
return state;
}
function addItem(state, text) {
const itemId = state.get('hostnames').reduce((maxId, item) => Math.max(maxId,item.get('id')), 0) + 1;
const newItem = Map({id: itemId, text: text, status: 'active'});
return state.update('hostnames', (hostnames) => hostnames.push(newItem));
}
export default function(state = Map(), action) {
switch (action.type) {
case 'SET_STATE':
return setState(state, action.state);
case 'ADD_ITEM':
return addItem(state, action.text);
case 'FETCH_DATA':
return fetchData(state);
}
return state;
}
So basically my question is, how do I fetch the data ( if the fetch is wrong now ) and how do I add the fetched data from my api to the store in my reducer.
I just find react and redux pretty complicated so sorry if I'm asking a really noob question or just making big mistakes in the way I want to do something.
Thanks in advance for any help.
imagine your json
{
"data": {
"apple": 1,
"banana": 3,
},
"status": 200,
}
your actions
export function fetchData() {
return dispatch => {
fetch('http://127.0.0.1:8000/example')
.then(res => res.json())
.then((responseData) => {
if(responseData.status === 200){
dispatch(setData(responseData));
}
})
}
}
export function setData(responseData) {
return {type: SET_DATA, data: responseData.data }
}
your reducer
const initialState = { data: null };
export default function(state = initialState, action) {
switch (action.type) {
case 'SET_DATA':
return Object.assign({}, state, {
data: action.data,
})
default:
return state;
}
}
then your state will become
{ data: {
apple: 1,
banana: 3,
}
}
Actually, all your reducers should be pretty dumb and pure (without any side effects). So their only concern is to modify the state and nothing else. Fetching data from the server or any kind of orchestration should be implemented in redux middleware. Look at redux-thunk or redux-saga if you need something more complicated. Hope that helps.

Simplest Redux-React app in ES5: why aren't props being passed down?

I'm trying to build the most trivial possible Redux app. I have an initial state, I make a Redux store, I pass the store to ReactRedux.Provider, and I have my app as a child of the Provider.
However, my APP view, written as a stateless functional component, is not receiving any props. (The same is true if I write my APP view using React.createClass and checking for this.props in the render method.)
What am I doing wrong?
var initialState = {
counter: 0
};
var rootReducer = function(state, action) {
if (!state) state = initialState;
switch (action.type) {
default: // don't do anything yet
return state;
}
};
var store = Redux.createStore(rootReducer, initialState);
var APP = function(props) {
return React.createElement(
'div',
{},
props.counter // props is not defined
);
};
ReactDOM.render(
React.createElement(
ReactRedux.Provider,
{store: store},
React.createElement(APP, null)
),
document.getElementById('app')
);
You need to use the connect() function provided by React-Redux to create a wrapped version of your "APP" component that is actually hooked up to the store. See http://redux.js.org/docs/basics/UsageWithReact.html .
You can write the equivalent logic yourself for subscribing to the store and passing updated props to a component, but generally there's not a good reason to do so.
For future reference, I am going to add here an example of a working case in codepen not using babel neither the integrated version of jsx.
https://codepen.io/kanekotic/pen/LxbJNJ
Solution ;TL;DR
As commented before there is missing the redux connect
var mapstateToProps = function(state) {
return{
counter: state.counter
}
}
function mapDispatchToProps(dispatch){
return Redux.bindActionCreators(ActionCreators, dispatch);
}
var connectedApp = ReactRedux.connect(mapstateToProps,mapDispatchToProps)(APP)
and use then in the component connectedApp and no APP

Resources