The task is to create a reducer function to handle multiple authentication actions. Use a JavaScript switch statement in the reducer to respond to different action events. This is a standard pattern in writing Redux reducers. The switch statement should switch over action.type and return the appropriate authentication state.
There are two approaches that seem same to me. In fact, I feel method 2 is better than method 1 as it actually updates the state. However freecodecamp seems to believe otherwise. Can someone tell me what is the difference between the 2.
Method 1 - Create new objects
const defaultState = {
authenticated: false
};
const authReducer = (state = defaultState, action) => {
// change code below this line
switch (action.type) {
case "LOGIN":
return {
authenticated: true
};
case "LOGOUT":
return {
authenticated: false
};
default:
return defaultState;
}
// change code above this line
};
const store = Redux.createStore(authReducer);
const loginUser = () => {
return {
type: "LOGIN"
};
};
const logoutUser = () => {
return {
type: "LOGOUT"
};
};
Method 2 : Update existing object
const defaultState = {
authenticated: false
};
const authReducer = (state = defaultState, action) => {
// Change code below this line
switch(action){
case 'loginUser':
state.authenticated = true;
return state;
case 'logoutUser':
state.authenticated = false;
return state;
default:
return state;
}
// Change code above this line
};
const store = Redux.createStore(authReducer);
const loginUser = () => {
return {
type: 'LOGIN'
}
};
const logoutUser = () => {
return {
type: 'LOGOUT'
}
};
The error message that I get in Method 2 is as follows
Error Log
Related
I get the array of objects coming from backend, I get it with socket.io-client. Here we go!
//App.js
import Tickers from "./Components/TickersBoard";
import { actions as tickerActions } from "./slices/tickersSlice.js";
const socket = io.connect("http://localhost:4000");
function App() {
const dispatch = useDispatch();
useEffect(() => {
socket.on("connect", () => {
socket.emit("start");
socket.on("ticker", (quotes) => {
dispatch(tickerActions.setTickers(quotes));
});
});
}, [dispatch]);
After dispatching this array goes to Action called setTickers in the slice.
//slice.js
const tickersAdapter = createEntityAdapter();
const initialState = tickersAdapter.getInitialState();
const tickersSlice = createSlice({
name: "tickers",
initialState,
reducers: {
setTickers(state, { payload }) {
payload.forEach((ticker) => {
const tickerName = ticker.ticker;
const {
price,
exchange,
change,
change_percent,
dividend,
yeild,
last_trade_time,
} = ticker;
state.ids.push(tickerName);
const setStatus = () => {
if (ticker.yeild > state.entities[tickerName].yeild) {
return "rising";
} else if (ticker.yeild < state.entities[tickerName].yeild) {
return "falling";
} else return "noChange";
};
state.entities[tickerName] = {
status: setStatus(),
price,
exchange,
change,
change_percent,
dividend,
yeild,
last_trade_time,
};
return state;
});
return state;
},
},
});
But the state doesn't change. I tried to log state at the beginning, it's empty. After that I tried to log payload - it's ok, information is coming to action. I tried even to do so:
setTickers(state, { payload }) {
state = "debag";
console.log(state);
and I get such a stack of logs in console:
debug
debug
debug
3 debug
2 debug
and so on.
I am just trying to run a simple redux program when i use command node index it shows me error that action must be plain objects below is my code for that
const redux = require('redux')
const createStore = redux.createStore
const BUY_CAKE = 'BUY_CAKE'
function buyCake () {
return
{
type: BUY_CAKE
}
}
const initialState = {
numOfCakes: 10
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case BUY_CAKE: return {
...state,
numOfCakes: state.numOfCakes - 1
}
default: return state
}
}
const store = createStore(reducer)
console.log("initial state is ", store.getState())
const unsubscribe = store.subscribe(() => console.log("updated", store.getState()))
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
unsubscribe()
when i dispatch(buyCake()) then only it shows error but if i do store.dispatch({type:BUY_CAKE}) then code runs fine why is the error occuring
Because your return statement is wrongly formatted. Be aware of this deadly feature:
JavaScript will automatically insert semicolons. Without the parentheses, JavaScript would ignore the following lines and return without a value.
This is your function with semicolons, which will return undefined:
function buyCake(){
return;
{
type:BUY_CAKE
};
};
Solution: Move your curly brackets to the return line:
function buyCake(){
return {
type: BUY_CAKE
}
}
I have a redux app which enables users to post something(like a blog post which contains only a title and body).The user can edit the post whenever they want to.I am not able to update the reducer,i.e. I just want to update the title and the body for that particular "id". I am trying to update the state in my reducer as shown below:
case SUBMIT_POST:
let id = action.payload.id;
return {
...state,
[id]: {
...state[id],
title: action.payload.title,
body: action.payload.body
}
};
My action-creator looks like this:
//Action creator for submitting edited post
export function submitEditedPost(id, values, callback) {
const request = axios.put(`${API}/posts/${id}`, {values}, {headers});
return dispatch => {
return request.then((res) => {
callback();
console.log(res.data.values)
dispatch({
type:SUBMIT_POST,
payload: res.data.values
})
})
}
}
How to update the reducer with the new edited title and body for that particular post id?
EDIT 1:
I am calling the action creator in my form onSubmit() method as shown below:
onSubmit(values) {
const { id } = this.props.match.params;
const formData = {};
for (const field in this.refs) {
formData[field] = this.refs[field].value;
}
formData.id = id;
console.log('-->', formData);
this.props.submitEditedPost(id, formData, () => {
this.props.history.push('/');
});
}
EDIT 2: Screenshot of the action in reducer is shown below:
EDIT 3: My entire reducer:
import _ from 'lodash';
import { FETCH_POSTS, FETCH_POST, CREATE_POST, EDIT_POST, SUBMIT_POST } from '../actions/posts_action';
export default function(state = {posts: {} }, action) {
switch (action.type) {
case FETCH_POST:
// const post = action.payload.data;
// const newState = { ...state, };
// newState[post.id] = post;
// return newState;
return {...state, [action.payload.id]: action.payload};
case FETCH_POSTS:
return {posts: { ...state.posts, ...(_.mapKeys(action.payload,'id'))}};
case CREATE_POST:
return {...state, [ action.payload.id]: action.payload};
case EDIT_POST:
return { ...state, [action.payload.id]: action.payload};
case SUBMIT_POST:
console.log(action.payload);
let id = action.payload.id;
return {
...state,
[id]: {
...state[id],
title: action.payload.title,
body: action.payload.body
}
};
default:
return state;
}
}
EDIT 4 : Screenshot of redux-dev-tools:
Below are my action and reducer files - In my component state I am only seeing this.props.mainData - but others subdataOneData etc., are not being loaded in to the state - till reducer i see the right actions are being dispatched and I also see the data for sub - calls - but they are not reaching my component - I have mapStatetoprops - where I am doing
New issue: as per the updated code - when i print out payload in reducer - I see maindata with the api data but SubData [{}, {}, {}] ..?
Updated code:
import { GET_DATA_AND_SUBDATA } from '../constants/types';
export function getMainData() {
return async function getMainData(dispatch) {
const { data } = await getMainDataAPI();
const subData = data.map((item) => {
const endpoint = 'build with item.name';
return Request.get(endpoint);
});
console.log('subddd' + subData); prints -> **[object Promise],[object Promise],[object Promise]**
dispatch({
type: GET_DATA_AND_SUBDATA,
payload: { data, subData }
});
};
}
async function getMainDataAPI() {
const endpoint = 'url';
return Request.get(endpoint);
}
The problem lies on the way you dispatch the actions.
You are not providing data for mainData and subdataOneData at the same time.
export function getData() {
return async function getData(dispatch) {
const { data } = await getDataAPI();
// This will cause first re-render
dispatch({ type: GET_DATA, payload: data });
Object.keys(data).map((keyName, keyIndex) => {
const endpoint = 'ENDPOINT';
Request.get(endpoint).then((response) => {
// This will cause second re-render
dispatch({
type: GET_subdata + keyIndex,
payload: response.data });
});
return keyIndex;
});
};
}
At first render your subdataOneData is not availble yet.
You are not even specifying a default value in the reducer, therefore it will be undefined.
You can change your action thunk like this
export function getData() {
return async function getData(dispatch) {
const { data } = await getDataAPI();
const subDataResponse = await Promise.all(
Object.keys(data).map( () => {
const endpoint = 'ENDPOINT';
return Request.get(endpoint)
})
)
const subData = subDataResponse.map( response => response.data )
dispatch({
type: GET_DATA_AND_SUBDATA
payload: { data, subData }
});
};
}
And change your reducer accordingly in order to set all data at once.
export default function myReducer(state = {}, action) {
switch (action.type) {
case GET_DATA_AND_SUBDATA:
return {
...state,
mainData: action.payload.data,
subdataOneData: action.payload.subData[0],
subdataTwoData: action.payload.subData[1]
};
default:
return state;
}
}
Note: it's also a good practice to set your initial state in the reducer.
const initialState = {
mainData: // SET YOUR INITIAL DATA
subdataOneData: // SET YOUR INITIAL DATA
subdataTwoData: // SET YOUR INITIAL DATA
}
export default function myReducer(initialState, action) {
I created a rootSaga in sagas.js as
function* fetchStuff(action) {
try {
yield put({type: 'INCREMENT'})
yield call(delay, 1000)
yield put({type: 'DECREMENT'})
const highlights = yield call(API.getStuff, action.data.myObject);
} catch (e) {
yield put({type: 'FETCH_STUFF_FAILED', message: e});
}
}
export default function* rootSaga() {
yield takeEvery('INIT_LOAD', fetchStuff);
}
I am calling the INIT_LOAD after thirdParty.method:
class myClass extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.load();
}
load = () => {
this.init = () => {
this.myObject = thirdParty.method(event => {
const action = {
type: 'INIT_LOAD',
payload: {
myObject: this.myObject
}
};
store.dispatch(action);
});
};
this.init();
};
render() {
return (
<div id="render-here" />
);
}
Passing the this.myObject in the action that is dispatched does not trigger the saga. If I change the action payload to a string, like the following, the saga is triggered.
const action = {
type: 'INIT_LOAD',
payload: {
myObject: 'this.myObject'
}
};
Why am I unable to pass this.myObject but a string is ok?
UPDATE: It is not a saga issue. I replicated the same issue with just plain redux. The rootReducer as
export default function rootReducer(state = initialState, action) {
switch (action.type) {
case 'INIT_LOAD':
return Object.assign({}, state, { myObject: action.payload.myObject });
default:
return state;
}
}
As I mentioned in the comment below, assigning it to an object Obj does not change the issue
let Obj = {};
...
load = () => {
this.init = () => {
Obj.myObject = thirdParty.method(event => {
const action = {
type: 'INIT_LOAD',
payload: {
myObj: Obj
}
};
store.dispatch(action);
});
};
this.init();
};
UPDATE2
I cleaned the code up & simply dispatched an action in the component that triggers the saga. Inside the saga is where I do the init(). I ran into another issue where the object that I was trying to save in the redux store has active socket sessions (which were given me cross-domain issues). Although I didn't solve my original problem, not storing a socket object made my problem go away.