How to load axios data as initial redux state - reactjs

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.

Related

redux useSelector hook returns array from state that is set by redux-sagas but i cant .filter() over it

I am just writing up a a small site that fetches list of repositories for a given user and displays them in a grid.
To achieve it i am using probably an overkill combination of redux, redux-sagas, axios and redux-hook.
First thing i do is i have a httpClient that does the async call to fetch the repos which returns array of objects [{},{},{}]
import axios from "axios";
export const getProjects = async () => {
return await axios.get("https://api.github.com/users/xxx/repos", {
headers: {
"Content-type": "application/json",
},
});
};
inside my Container component which is loaded when the app mounts i dispatch a action to trigger the redux cycle:
const dispatch = useDispatch();
useEffect(() => {
dispatch(getProjectsRequest());
}, []);
the Action:
export const getProjectsRequest = () => ({
type: ActionTypes.GET_PROJECTS_REQUEST,
})
This is then captured by my saga where i yield my httpCLient that return the array of objects and passes the payload onto getProjectsSuccess(result.data) which is :
export const getProjectsSuccess = (projects) => ({
type: ActionTypes.GET_PROJECTS_SUCCESS,
payload: {
projects
}
})
SAGA:
import { call, put, takeEvery, fork } from "redux-saga/effects";
import { ActionTypes } from "../actionTypes";
import * as actionProjects from "../actions/projectsAction";
import * as http from "../../api/httpClient";
// Worker Saga
function* fetchProjects() {
try {
const result = yield call(http.getProjects);
yield put(actionProjects.getProjectsSuccess(result.data));
} catch (error) {
console.log(error);
yield put({ type: "GET_PROJECTS_FAILED", message: error.message });
}
}
function* watchGetProjectsRequest() {
yield takeEvery(ActionTypes.GET_PROJECTS_REQUEST, fetchProjects);
}
const projectsSagaResult = [fork(watchGetProjectsRequest)];
export default projectsSagaResult;
This is the captured in my reducer and updates the state accordingly with array of objects:
case ActionTypes.GET_PROJECTS_SUCCESS: {
return {
isLoading: false,
...action.payload,
};
}
FINALLY:
In my projects.js component where i am trying do loop and display all the projects from GITHUB user i use const { projects } = useSelector((state) => state.gitHubPortfolio)
so that i can access the state slice and filter over it like so:
const test = projects.filter(x => {return x.name === "m" })
This instantly throws a error:
Uncaught TypeError: Cannot read properties of undefined (reading 'filter')
But when i step through the code in the browser i can do this without the error so the useSelector fetches array of objects from the state.
Now i the console i can simply filter projects array whilst inn debugger mode like so:
AT LAST
I have no idea why i cant filter through the projects array inn my code, but it seems to me like its some PROMISE issue it might be that the projects are not set before i am trying to filter them i really have no idea.

React Native, problem while updating State inside useEffect from async function

I'm trying the fetch data from a json API and setting it to a state. Currently using visual studio code with a pixel 4 emulator.
When I try to update my state inside of a useEffect method on the emulator's first launch or on reload, it doesn't change. If I save in vs code, the data in state updates as intended.
...
import React, {useState, useEffect} from 'react';
import {getJsonData} from './getJsonData';
const myApp = () => {
const [state, setState] = useState({
isLoading: true,
data: null,
});
const updateState = data => {
console.log(data); //Logs the correct Json data everytime
setState(state => ({...state, isLoading: false, data: data}));
console.log(state.isLoading); //Doesn't update on reload (updates only on save)
console.log(state.data); //Same as above
};
useEffect(() => {
getJsonData().then(data => updateState(data));
}, []);
return (
<View>
<Text>{state.data.title}</Text>
<Text>{data.data.completed}</Text>
</View>
);
}
And this is the getJsonData( ) function:
export async function getJsonData() {
try {
let response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
let responseJson = await response.json();
return responseJson;
} catch (error) {
console.error(error);
}
}
I ultimately want the state to update on the application's first run, on reload, as well as each time I call a certain reloadApp( ) function.
If the above code is not the best practice, please don't hold back to correct me as I'm just learning about states.
setState function is asynchronous. So console.log immediately after setState will give old value but not the new value.
Also why don't you seperate the states like
const [isLoading,setIsLoading]=useState(true);
const [data,setData] = useState(null);
and set them separately so your code looks better.
in updateState(jsonData) you can do then
setIsloading(false);
setData(jsonData);

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.

Redux receiving props delay after dispatch calling from componentDidMount

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")

React, Redux and Axios - trying to make API call

It's my first experience with React, Redux and I am totally lost. The problem is my action :
import axios from 'axios';
import { FETCH_MOVIE } from '../constants/actionTypes';
const API_KEY = <API_KEY>;
const ROOT_URL = `<API_URL>`;
export function fetchMovies(pop){
const url = `${ROOT_URL}?api_key=${API_KEY}&sort_by=${pop}`;
axios.get(url)
.then(function (response) {
console.log("response is",response)
})
.catch(function (error) {
console.log(error);
});
return{
type: FETCH_MOVIE,
payload: response.data
};
}
On Console.log it seems just fine - I can see the response has the data I need. But when I am trying to send response.data to payload it returns the error - response is not defined. What am I doing wrong?
P.s. I also tried to create const result = [] and than result = [...response.data]. The error was - SyntaxError: "result" is read-only
The const error is because, result being a variable that changes over the course of the execution, you must use 'let' and not 'const'.
Now, for the fix, response is not defined comes from the last return. A good approach would be to, instead of returning the action on this function fetchMovies, you should dispatch a new action, e.g dispatch(fetchMoviesSuccess(payload)) instead of "console.log("response is",response)", which will dispatch an action that will trigger the reducer, and , in turn, update the state of the app.
You are performing async request using axios. You should dispatch your action using redux-thunk. Installation is easy, read more about thunk here.
Then your action should look like this:
export function fetchMovies(pop) {
return dispatch => {
const url = `${ROOT_URL}?api_key=${API_KEY}&sort_by=${pop}`;
axios.get(url)
.then(function (response) {
console.log("response is",response);
dispatch({
type: FETCH_MOVIE,
payload: response.data
});
})
.catch(function (error) {
console.log(error);
// You can dispatch here error
// Example
dispatch({
type: FETCH_MOVIE_FAILED,
payload: error
});
});
}
}
The issue with your code is that by the time you return, response is still undefined because this code run synchronously till the return statement.
As you can see response is defined in console.log("response is",response)
So this is where you need to do your actual magic return but in another way.
You can use redux-thunk to do these thing because this is redux async. but as I feel you are a beginner from the code I have seen, Just use the simpler way and read redux-thunk or redux-promise. if you feel your project needs this then go one.
//try to make the caller pass this.props.dispatch as param
export function fetchMovies(dispatch, pop){
const url = `${ROOT_URL}?api_key=${API_KEY}&sort_by=${pop}`;
axios.get(url)
.then(function (response) {
// only here is response define so use dispatch to triger another action (fetched data with response)
dispatch({
type: FETCH_MOVIE,
payload: response.data
})
})
.catch(function (error) {
//if you had a loader state, you call also toggle that here with erro status
console.log(error);
});
}
//now on the caller (onClick for instance) do this instead
fetchMovies(this.props.dispatch, pop)
As you can see from #loelsonk answer down. if you use redux-thunk then you won't need to pass dispatch from the caller redux-thunk for you. But also notice how you would return and anonymous arrow function which accept dispatch as a parameter.
You can use redux promise middleware. I have used this in my new project. It is very simple and keeps our code and state manageable.
For every async action dispatch, it dispatches
$action_type_PENDING immediately after our action dispatch , $action_type_FULFILLED if api call success, $action_type_REJECTED if api call failure
See documentation- https://github.com/pburtchaell/redux-promise-middleware
Example from my project-
your action is
export function getQuestions() {
return {
type: types.GET_QUESTIONS,
payload: axios.get('http://localhost:3001/questions')
};
}
reducer is
const initialState = {
isLoading: false,
questions: []
};
const questions = (state = initialState.questions, action) => {
switch(action.type) {
case types.GET_QUESTIONS_FULFILLED:
return [...action.payload.data];
default: return state;
}
};
For displaying loader while api call we can use following reducer
const isLoading = (state = initialState.isLoading, action) => {
switch(action.type) {
case (action.type.match(/_PENDING/) || {}).input:
return true;
case (action.type.match(/_FULFILLED/) || {}).input:
return false;
default: return state;
}
};
Comment me if you need any more details on above stuff.

Resources