Redux receiving props delay after dispatch calling from componentDidMount - reactjs

State is not updated immediately after receiving data
Accounts.js like this
class Accounts extends Component {
componentDidMount()
{
this.props.dispatch(fetchAccountsAction())
}
render(){
const accInfo = this.props.accounts // Not getting data immediately
return (
<Details accInfo = {accInfo} />
)
}
}
const mapStateToProps = (state, ownProps) => {
console.log('state',state);
return {
accounts:state.accounts
}
}
export default connect(mapStateToProps)(Accounts)
Action.js like this
const fetchAccountsAction = () => {
return async (dispatch) => {
const res = await fetch(url, {
method: "POST",
headers: {
'Content-type': 'Application/json',
'Authorization': token,
},
body: JSON.stringify(data)
});
const data = await res.json()
if (data) {
dispatch(fetchAccounts(data))
}
}
}
export function fetchAccounts(accounts)
{
console.log('accounts',accounts) // Am getting data here
return {
type: FETCH_ACCOUNTS,
accounts : accounts
}
}
Reducer.js like this
const initialState = {
accounts : [],
error:null
}
export function accountsReducer(state=initialState,action) {
switch(action.type){
case FETCH_ACCOUNTS:
return {
...state,
accounts:action.accounts
}
default:
return state
}
}
When componentDidMount happened props not receiving immediately because there is a delay in API response. Could you please help with the props access after receiving the data from API.
Thank you.

What happens here:
cDM is called, action is dispatched.
If action creator was sync(just a plain action + straight reducer without any async operations) state would be updated
render() happens with previous props(old state)
redux's store.subscribe() makes wrapper(created by connect) to recalculate all that mapStateToProps/mapDispatchToProps
since step #3 returned different values wrapper re-renders your component with new props
render() happens with new props
That fact your action creator is async by its nature switch #2 and #3 with their places. But anyway, your first render will be with old store values.
So you better handle that accordingly(like checking if some object is not undefined anymore or use brand new optional chaining to get safe from "cannot read property ... of null")

Related

How to get data from an API using redux?

I am requesting an API using redux and saga
I the request is working fine I am able to get API returned data in the reducer, but I am not able to get that data in the home.js I am new to redux please help me.
in my home.js this is how I am making a call
const fetchSessionData = () => {
dispatch(
fetchSession({
token: token
})
)
}
action.js
export const fetchSession = data => {
return {
type: Action.SESSION_DATA,
payload: {data},
};
};
this is reducer file
export const SessionData = (state = sessionState, action) => {
console.log('inside session reducer', JSON.stringify(action));
switch (action.type) {
case Action.SESSION_FETCH_SUCCESS:
return {
...state,
isLoading: false,
};
case Action.SESSION_FETCH_ERROR:
return {
...state,
sagaerror: action.error,
isLoading: false,
};
case Action.SESSION_DATA:
return {
...state,
isLoading: true,
};
default:
return state;
}
};
and this is the api
export function fetchSessionData(payload) {
return fetch(`${url}/session/`, {
method: 'GET',
headers: {
Authorization: `Bearer ${payload.token}`,
Accept: 'application/json',
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(res => {
return res;
})
.catch(error => {
throw error;
});
}
how can I get the returning data from api in home.js?
Looks like you are not storing back the response from saga.
Please have an action for storing the response into your reducer.you may use
yield put(storeactionname(payload received from api reponse);
in your saga method and in reducer your action.payload should be stored into a state variable
and in your component you can use useSelector like below
const { yourvariableNameInStateInReducer } =
useSelector((state) => state.yourreducername);
As you say you will able to get data to reducer. It's then fine in that section. In the component you need to select stored data from the store. If you are using functional component you can use useSelector hook in react-redux. If you are using class component then you need to use connect function, you need to pass mapStateToProps and mapDispatchToProps arguments to the connect function. Refer https://react-redux.js.org/api/connect

Access to API using Redux

I have a react-redux app. I need to call API and used it in my component. The app is called with fetch in function in utills.
All functions are group and export like this:
export const sportTeam = {
getBasketballTeam,
getBasketballTeamById,
}
function getBasketballTeam() {
let token = store.getState().UserReducer.token;
fetch(
actions.GET_BASKETBALLTEAM,
{
method: "GET",
headers: { Authorization: `Bearer ${token}` },
}
)
.then((res) => {
if (res.status == 200 ) {
return res.json();
}
})
.then((response) => {
console.log(response);
})
.catch((err) => {
console.log(err);
});
}
getBasketballTeam contains an array of objects.
How can I get getBasketballTeam and used it in the component in the view to returning the list with this data?
You don't want your getBasketballTeam function to access the store directly through store.getState().
What you want is a "thunk" action creator that gets the store instance as an argument when you dispatch it.
The flow that you want is this:
Component continuously listens to the basketball team state with useSelector (or connect).
Component mounts.
Component dispatches a getBasketballTeam action.
Action fetches data from the API.
Reducer saves data from the action to the state.
State updates.
Component re-renders with the new data from state.
The easiest way to do this is with the createAsyncThunk function from Redux Toolkit. This helper handles all errors by dispatching a separate error action. Try something like this:
Action:
export const fetchBasketballTeam = createAsyncThunk(
"team/fetchBasketballTeam",
async (_, thunkAPI) => {
const token = thunkAPI.getState().user.token;
if ( ! token ) {
throw new Error("Missing access token.");
}
const res = await fetch(actions.GET_BASKETBALLTEAM, {
method: "GET",
headers: { Authorization: `Bearer ${token}` }
});
if (res.status !== 200) {
throw new Error("Invalid response");
}
// what you return is the payload of the fulfilled action
return res.json();
}
);
Reducer:
const initialState = {
status: "idle",
data: null
};
export const teamReducer = createReducer(initialState, (builder) =>
builder
.addCase(fetchBasketballTeam.pending, (state) => {
state.status = "pending";
})
.addCase(fetchBasketballTeam.fulfilled, (state, action) => {
state.status = "fulfilled";
delete state.error;
state.data = action.payload;
})
.addCase(fetchBasketballTeam.rejected, (state, action) => {
state.status = "rejected";
state.error = action.error;
})
);
Store:
export const store = configureStore({
reducer: {
team: teamReducer,
user: userReducer,
}
});
Component:
export const BasketballTeam = () => {
const { data, error, status } = useSelector((state) => state.team);
const dispatch = useDispatch();
useEffect(
() => {
dispatch(fetchBasketballTeam());
},
// run once on mount
// or better: take the token as an argument and re-run if token changes
[dispatch]
);
if (status === "pending") {
return <SomeLoadingComponent />;
}
if (!data) {
return <SomeErrorComponent />;
}
// if we are here then we definitely have data
return <div>{/* do something with data */}</div>;
};
After you get response you need to do the following things
call dispatch function to store the data received in REDUX state.
Now when you have data in redux state, you can use useSelector() to get that state and make use of it in your jsx file.

react -redux component does not re-render after state change

I have been trying and trying by my component wont re-render itself . Below is my reducer code and I have tried everything to not mutate the state. In my component code ,inside render method, I have a log statement console.log("Check Here"); I know the component does not re-render because this log works first time the component renders but after reducer changes the state the log statement is not called . In logs I can clearly see that prev state and next state are different by just that one SearchType that I am changing. Please help!!
const initState = {
searchType: ""
};
const techniqueReducer = (state = initState, action) => {
switch (action.type) {
case actionTypeConstants.GET_SEARCH:
{
return { ...state, searchType: "new string" };
}
default: {
return state;
}
}
};
export default myReducer;
My component code is below
import React, { Component } from "react";
import { connect } from "react-redux";
import * as tDispatchers from "../actions/Actions";
const mapStateToProps = state => {
  return {
  
    searchType: state.searchType
  };
};
class SearchCollection extends Component {
  Search= () => {
    this.props.dispatch(tDispatchers.getSearch(document.getElementById("txtSearch").value));
  }
 
  render() {
console.log("Check Here")
    return (
      <div class="container-fluid">
        <div>
          <input
            type="text"
            id="txtSearch"
            class="form-control"
            placeholder="Enter Search Keywords Here..."
          />
        </div>
        <div>
  <button
            className="btn btn-light btn-sm m-1"
            onClick={this.Search}
          >
            Search
          </button>
         
        </div>
  
      </div>
    );
  }
}
export default connect(mapStateToProps)(SearchCollection);
GetSearch looks like below
I plan to pass payload to reducer eventually but currently I am not
import * as actionTypeConstants from "../action_type_constants";
import axios from "axios";
export function getSearch(searchtext) {
return dispatchFunction => {
axios
.get("<api call>"+searchtext)
.then(response => {
dispatchFunction({
type: actionTypeConstants.GET_SEARCH,
payload: response.data.data
});
})
};
}
ActionTypeConstant
export const GET_SEARCH = "GET_SEARCH";
I suppose you are using redux-thunk to work with async actions. But you don't return an async function from getSearch. I believe it should be
export function getSearch(searchtext) {
return dispatchFunction => {
return axios
.get("<api call>"+searchtext)
.then(response => {
dispatchFunction({
type: actionTypeConstants.GET_SEARCH,
payload: response.data.data
});
})
};
}
or
export function getSearch(searchtext) {
return async dispatchFunction => {
const response = await axios
.get("<api call>"+searchtext);
dispatchFunction({
type: actionTypeConstants.GET_SEARCH,
payload: response.data.data
});
};
}
You are not updating searchType value, which is hardcoded to string new string. Try setting the new state from the action, for example:
return { ...state, searchType: action.payload};
Or check this, https://jsfiddle.net/xt3sqoc6/1/ and open your dev tools to see the rerenders.
You can use componentDidUpdate(prevProps, prevState). It is invoked immediately after updating occurs & you can compare the current props to previous props. Using that you can re-render your component by changing state
componentDidUpdate(prevProps) {
if (this.props.SearchType !== prevProps.SearchType) {
//Do whatever needs to happen!
}
}
You may call setState() immediately in componentDidUpdate but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop.
Hope this helps you. Feel free for doubts.

How to load axios data as initial redux state

I try to load some axios json result on my initial state in order to open my app with a prepopulated state but i do not manage to load the axios result in my that initial state, i can see it on my console but the return doesnt work here
this is the code of my reducer
import axios from "axios";
const getBookings = () => {
return new Promise((resolve) => {
axios.get('http://localhost:4000/bookings.json')
.then(res => resolve(res.data))
});
}
const getInitiatState = getBookings().then(
function(data)
{
console.log(data)
const initialState = {
data: data, // ' ' or axios result
};
return initialState;
}
)
function bookings(state = getInitiatState, action)
{
switch(action.type)
{
default:
return state
}
}
export default bookings;
As i said in comments: You should make it as empty array/object and "initialize" state later with proper action. Right now instead of making state with array you fill it with promise.
My sample using React with hooks and setTimeout (this will work the same with your fetch): https://codesandbox.io/s/6wwy4xxwwr?fontsize=14
You can also just do it in your "index.js" using store.dispatch(action) like:
import store from './fileWithConstStore';
fetch()
.then(data => {
store.dispatch({
type: 'INIT_BOOKINGS',
payload: data
})
});
but this rather quick than approved solution.

Redux Thunk Callback after dispatching an action?

So in my React component, I have this:
this.props.updateAlertCallback('error', ERROR_MESSAGE)
My updateAlertCallback action is:
export const updateAlert = (alert, message) => {
return {
type: 'UPDATE_ALERT',
alert,
message
}
}
export const updateAlertCallback = (alert, message) => {
return dispatch => {
return dispatch(updateAlert(alert, message)).then(() => {
console.log('Done!');
});
}
}
I'm getting the following error: Uncaught TypeError: dispatch(...).then is not a function
What's the proper way to log something after updateAlert is done running?
With redux-thunk, you can make action return a promise:
export const updateAlert = (alert, message) => (dispatch, getState) => {
dispatch ({
type: 'UPDATE_ALERT',
alert,
message
});
return Promise.resolve(getState());
// or just Promise.resolve();
now you can call updateAlert(xx, xx).then(newState => {.....});
function showAlert(message) {
return {
type: SHOW_ALERT,
message
};
}
function hideAlert(message) {
return {
type: HIDE_ALERT,
};
}
function flashAlert(message) {
return (dispatch) => {
dispatch(showAlert(message));
setTimeout(() => {
dispatch(hideAlert());
}, 5000);
}
}
You'll need redux-thunk for this to work. You can then use this.props.flashAlert('Oh noes!!!!') with the proper mapStateToProps. Also needed are reducers and react components.
Fading isn't necessarily an easy thing to do in react. I suggest you save that for later.
What the flashAlert function does is it returns a function that takes a dispatch function. This function does all kinds of fun things but not yet. First this function gets passed to redux's dispatch. This dispatch would normally throw because actions must be plain objects. But because you're using redux-thunk it will be fine. Redux-thunk will call this function and pass it the dispatch function from redux. Now the function will run, finally. First thing it does is dispatch an action that it gets by calling showAlert(). This time it's an object with a type property, which makes it a proper redux action. Presumably redux will pass this action on to our reducer which will update the state with the new message, but we don't know that for sure because the reducer was left out of this answer for brevity. Who know what code it contains. After the state was changed to show the message somehow, we do a setTimeout(). When this calls back we dispatch another action we get by calling hideAlert() using the same dispatch function we used previously. We still have it. This presumably will scrub the message from the state.
Redux will tell react to rerender the appropriate components whenever the state changes. Presumably one of those components will display or not display the message as the case may be.
Redux-thunk is your answer. In your store code change
const enhancers = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f
);
to
const enhancers = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f,
applyMiddleware(thunk)
);
and you will be able to use thunks with your redux actions.
Refer to https://github.com/gaearon/redux-thunk#installation
Actions in redux are plain objects. Redux thunk allows to return functions instead of objects. These functions are executed by the thunk middleware, and ultimately the final object that reaches the store for dispatch is a plain object. An example of redux thunked action is below.
export default class AccountActions {
static login(username, password) {
return (dispatch, getStore) => {
dispatch(AccountActions.loginRequest(username));
fetch(apiUrls.signInUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
user: {
email: username,
password: password,
}
})
})
.then(response => {
return response.json().then(responseJson => {
return dispatch(AccountActions.loginResponse(username, responseJson.token, response.status));
});
})
.catch(err => {
console.error(err);
});
};
}
static loginRequest(username) {
return {
type: ActionTypes.loginRequest,
username,
};
}
static loginResponse(username, token, status) {
return {
type: ActionTypes.loginResponse,
username,
token,
status,
};
}
}

Resources