Why my reducer is not changing synchronous? - reactjs

I have a reducer that seems me async by default. I had read in alot o places that the dispatch is synchronous by default, but in this case the console.log always print an empty array in the first call of my function, and in the second call it prints with one element instead of two. I don't know if I'm doing any mistake.
const answerQuestion = (correctAnswer, answerText) => {
const {
interview: { questionGroups, responses },
} = state;
dispatch({
type: ANSWER_CURRENT_QUESTION,
payload: {
correctAnswer,
answerText,
},
});
console.log(responses);
};
And I have this reducer
export default (state, action) => {
switch (action.type) {
case ANSWER_CURRENT_QUESTION:
return {
...state,
interview: {
...state.interview,
responses: [...state.interview.responses, action.payload],
},
};
default:
return state;
}
};

Related

redux dispatch action on every element of an array

I have an array of x element
Arr (2) ['16', '149']
I need to dispatch an action on every Id in this array in order to find Object by Id.
My reducer for single Id is working and it is returning me value. But im lost.
This is my reducer
const initialState = {
product: {},
};
const productReducer = (state = initialState, action) => {
switch (action.type) {
case GET_PRODUCT_BY_ID:
return {
...state,
product: action.payload,
};
default:
return state;
}
};
my action:
export const getProductById = (Id)=>async(dispatch)=>{
try {
const {data}= await api.getProductById(Id)
dispatch({type:GET_PRODUCT_BY_ID, payload: data})
} catch (error) {
console.log(error)
}
}
I would like to return something like array of objects with values for that id. Im stuck with this.

React/Redux - list not updating after a list item is editted

I Have a list of objects being displayed in a table.
When an item in the list is edited, the updated item is not displayed unless I refresh the page.
REDUCER FOR LIST
export const eaCodesReducer = (state = initialState.eaCodes, action) => {
switch (action.type) {
case "GET_EA_CODES": {
return {
...state,
eaCodes: action.payload
}
}
default: {
return state
}
}
}
REDUCER FOR SINGLE ITEM
export const eaSingleEaCodeReducer = (state = initialState.eaCode, action) => {
switch (action.type) {
case "UPDATE_EA_CODE": {
return {
...state,
eaCode: action.payload
}
}
default:
return state
}
}
How do I then tell it to update the eaCodes list to incorporate the single item change?
export const eaSingleEaCodeReducer = (state = initialState.eaCode, action) => {
switch (action.type) {
case "UPDATE_EA_CODE": {
return {
...state,
eaCode: action.payload,
eaCodes: [...state.eaCodes, action.payload]
}
}
default:
return state
}
}
Your eaCodesReducer should return an array instead of an object. So the new state is always the previous items in the array along with the new one and you should map over this piece of state in your component which is bound to your store. The update in the table should reflect automatically when this particular piece of state is updated in your store.
// this should be an array also
export const eaCodesReducer = (state = initialState.eaCodes, action) => {
switch (action.type) {
case "GET_EA_CODES": {
// should be an array
return [
...state,
action.payload
]
}
default: {
return state
}
}
}
Hope this helps !
#D10S had the right idea, but resulted in the app crashing. Instead of adding the extra reducer - i updated the action to call the reducer if the initial put request is successful:
export const updateEnforcementActionCode = (id, updateTypeObj) => {
return dispatch => {
return axios.put(`/api/enforcementactions/codes/${id}`, updateTypeObj)
.then(res => {
dispatch({ type: "UPDATE_EA_CODE", payload: res.data })
if (res.status === 200) {
return axios.get('/api/enforcementactions/codes')
.then(res => {
dispatch({ type: "GET_EA_CODES", payload: res.data })
})
}
}).catch(err => {
dispatch({ type: "GET_ERRORS", payload: err.response.message })
})
}
}
This does seem a little redundant. If anyone has a suggestion to make this slicker I'm open.

How to work with API call action in redux?

I am new to redux and I am trying to make it work with my application, but I have problems with understanding how to work with async actions in it. I have action that is api call. This action should be called as soon as my other state is not empty. I do not get any mistakes but do not think that my action is called since the data is empty. Can anybody help to understand what I am doing wrong?
Here is my actions.js. The wordsFetchData is the action I need to call:
export function wordsFetchDataSuccess(items){
return{
type: 'WORDS_FETCH_DATA_SUCCESS',
items
};
}
export function wordsAreFetching(bool){
return{
type: 'WORDS_ARE_FETCHING',
areFetching: bool
}
}
export function wordsHasErrored(bool) {
return {
type: 'WORDS_HAS_ERRORED',
hasErrored: bool
};
}
export function wordsFetchData(parsed) {
return (dispatch) => {
dispatch(wordsAreFetching(true));
fetch('URL', {
method: "POST",
headers: {
"Content-type": "application/json"
},body: JSON.stringify({
words: parsed
})
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(wordsAreFetching(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(wordsFetchDataSuccess(items)))
.catch(() => dispatch(wordsHasErrored(true)));
};
}
Here are my reducers:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
export function wordsAreFetching(state = false, action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return action.areFetching;
default:
return state;
}
}
export function wordsFetchHasErrored(state = false, action) {
switch (action.type) {
case 'WORDS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
This is my componentDidMount function:
componentDidMount = (state) => {
this.props.fetchData(state);
};
This is the function after terminating which the action should be called:
parseInput = async () => {
console.log(this.state.textInput);
let tempArray = this.state.textInput.split(" "); // `convert
string into array`
let newArray = tempArray.filter(word => word.endsWith("*"));
let filterArray = newArray.map(word => word.replace('*', ''));
await this.setState({filterArray: filterArray});
await this.props.updateData(this.state.filterArray);
if (this.state.projectID === "" && this.state.entity === "")
this.dialog.current.handleClickOpen();
else
if (this.state.filterArray.length !== 0)
this.componentDidMount(this.state.filterArray);
};
These are the mapStateToProps and mapDispatchToProps functions.
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.wordsFetchHasErrored,
areFetching: state.wordsAreFetching
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: wordsFetchData
};
};
You only need one action for executing fetching (i.e WORDS_ARE_FETCHING), the rest of the cases (i.e WORDS_HAS_ERRORED & WORDS_FETCH_DATA_SUCCESS) can be handled inside your reducer.
Your action:
export function wordsAreFetching(){
return{
type: 'WORDS_ARE_FETCHING',
}
}
Your new reducer:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return {...state, error: false, areFetching: true};
case 'WORDS_FETCH_DATA_SUCCESS':
return {...state, items: action.payload , areFetching: false};
case 'WORDS_HAS_ERRORED':
return {...state, error: true, areFetching: false};
default:
return state;
}
Then you can trigger WORDS_FETCH_DATA_SUCCESS after you get the data from here:
export function wordsFetchData() {
try {
const response = await axios.get(YOUR_URL);
return dispatch({ type: WORDS_FETCH_DATA_SUCCESS, payload: response.data });
} catch (err) {
return dispatch({ type: WORDS_HAS_ERRORED });
}
}
Take a look at this example, it uses axios that can help you with async calls.
A couple of things:
No need to pass state into your componentDidMount, your mapDispatchToProps is not using it.
Here is a suggestion to structure those functions. They are a bit more concise and readable.
const mapStateToProps = ({items, wordsAreFetching, wordsFetchHasError}) => ({
items,
hasErrored: wordsFetchHasErrored,
areFetching: wordsAreFetching,
});
const mapDispatchToProps = () => ({
fetchData: wordsFetchData(),
});
Other notes and helpful things:
If you're using thunk, you'll have access to your entire redux store in here as a second argument. For example:
return (dispatch, getState) => {
dispatch(wordsAreFetching(true));
console.log('getState', getState());
const { words } = getState().items;
// This is a great place to do some checks to see if you _need_ to fetch any data!
// Maybe you already have it in your state?
if (!words.length) {
fetch('URL', {
method: "POST",
headers: {
......
}
})
I hope this helps, if you need anything else feel free to ask.

react redux don't show getStore

I'm new to React Redux.
It's fantasic so that
I want to see about tasks state of Store.
but don't see it.
result
tasks: [undefined]
Ask
so how can I?
here code:
import { createStore } from 'redux'
const initalState = {
tasks: []
}
function tasksReducer(state = initalState, action) {
switch (action.type) {
case 'ADD_TASK':
return {
...state,
tasks: state.tasks.concat([action.task])
};
default:
return state;
}
}
const addTask = (task) => ({
type: 'ADD_TASK',
payload: {
task
}
})
const store = createStore(tasksReducer)
store.dispatch(addTask('like it'))
console.log(store.getState()) <-- here
The problem is your action. It's adding a task with a key of payload, but you're trying to concat it with action.task (Which doesn't exist).
The object that would be sent into your reducer would look like this:
{
type: 'ADD_TASK',
payload: {
task: 'like it'
}
}
You can see clearly here, action.task doesn't exist, but action.payload.task does. Either change the object around, or modify it so you can access it at action.task:
const addTask = (task) => ({
type: 'ADD_TASK',
task
})
Or, modify your reducer:
function tasksReducer(state = initalState, action) {
switch (action.type) {
case 'ADD_TASK':
return {
...state,
tasks: state.tasks.concat([action.payload.task])
};
default:
return state;
}
}
In the future: a bit of debugging would have gone a long way here (And avoided this question altogether). A simple console.log(action) at the top of your reducer, would log the above object, and you could infer based on why it's trying to add undefined why it wouldn't work.

Redux: Distinguish objects in reducers

I'm quite new to Redux and from what I understand, a reducer should be created for each type of object. E.g. for user interaction a user reducer should be created. My question is: How do you handle cases where you require the object for different purposes?
Scenario: Imagine having a user reducer which returns the current user. This user would be required in the entire application and needed for general controls on every page.
Now what happens when you need to load another user which is used for different purposes. E.g. profile page: loading a user to display information.
In this case there would be a conflict if the user reducer would be used. What would be the correct way to handle this in Redux? In case a different reducer would have to be created, what would be the naming convention for the new reducer?
First, you've mentioned:
a user reducer which loads the current user
I don't know if I got you correctly, but if this means you want to fetch (from an API, for example) the current user inside the reducer, this is a wrong approach.
Reducers are intended to be pure functions. You can call them with the same arguments multiple times and they will always return the same expected state.
Side effects like that should be handled by action creators, for example:
actions/user.js
export const FETCH_ME = 'FETCH_ME'
export const FETCH_ME_SUCCESS = 'FETCH_ME_SUCCESS'
// it's using redux-thunk (withExtraArgument: api) module to make an async action creator
export const fetchMe = () => (dispatch, getState, api) => {
dispatch({ type: FETCH_ME })
return api.get('/users/me').then(({ data }) => {
dispatch({ type: FETCH_ME_SUCCESS, data })
return data
})
}
Inside your reducer you can simple get the data and set a new state (note that if you send the action with the same data multiple times, the state will always be the same).
reducers/user.js
import { FETCH_ME, FETCH_ME_SUCCESS } from '../actions/user'
const initialState = {
item: null,
loading: false
}
export const userReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ME:
return {
...state,
loading: true
}
case FETCH_ME_SUCCESS:
return {
...state,
loading: false,
item: action.data
}
default:
return state
}
}
Now, for your scenario:
Now what happens when you need to load another user which is used for different purposes. E.g. profile page: loading a user to display information.
You will just write another action creator for that:
actions/user.js
export const FETCH_ME = 'FETCH_ME'
export const FETCH_ME_SUCCESS = 'FETCH_ME_SUCCESS'
export const FETCH_USER = 'FETCH_USER'
export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS'
export const fetchMe = () => (dispatch, getState, api) => {
dispatch({ type: FETCH_ME })
return api.get('/users/me').then(({ data }) => {
dispatch({ type: FETCH_ME_SUCCESS, data })
return data
})
}
export const fetchUser = (id) => (dispatch, getState, api) => {
dispatch({ type: FETCH_USER })
return api.get(`/users/${id}`).then(({ data }) => {
dispatch({ type: FETCH_USER_SUCCESS, data })
return data
})
}
Then you adapt your reducer to manage more sets:
reducers/user.js
import { combineReducers } from 'redux'
import { FETCH_ME, FETCH_ME_SUCCESS, FETCH_USER, FETCH_USER_SUCCESS } from '../actions/user'
const initialState = {
item: null,
loading: false
}
const meReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_ME:
case FETCH_ME_SUCCESS:
return userReducer(state, action)
default:
return state
}
}
const activeReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USER:
case FETCH_USER_SUCCESS:
return userReducer(state, action)
default:
return state
}
}
const userReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_USER:
case FETCH_ME:
return {
...state,
loading: true
}
case FETCH_USER_SUCCESS:
case FETCH_ME_SUCCESS:
return {
...state,
loading: false,
item: action.data
}
default:
return state
}
}
export default combineReducers({
activeUser: activeReducer,
me: meReducer
})
Your final user state should be something like:
{
me: {
item: null,
loading: false
},
active: {
item: null,
loading: false
}
}

Resources