My parent component is:
constructor(){
super();
this.state = {data: []}
}
componentWillMount(){
this.props.getData(); //makes api call
this.setState({data: this.props.data}); //get data stored in the store
}
render(){
const columns ={ //columns here}
return(
<Tables
dataSource = {this.state.data}
columns = {this.columns}
/>
)
}
const mapStateToProps = state => ({
data: state.data,
})
const mapDispatchToProps = dispatch => ({
getData: () => dispatch(getData()),
})
I do get data when I console it, but when I try to set state of data with the data props. It says data.slice is not a function.
//saga
export function* getData(action){
try{
const data = yield call(axios.get, 'http://localhost:4000/data');
console.log(data);
if (data) {
yield put(updateData(data.data));
}catch(e){
console.log(e);
}
}
//actions
export const updateData = data => ({
type: UPDATE_DATA,
data
});
//reducers
export const INITIAL_STATE = Immutable({});
export const updateData = (state, action) => {
console.log('reducers', action.data);
return { ...state, ...action.data };
};
const ACTION_HANDLERS = {
[UPDATE_DATA]: updateData,
};
export default createReducer(INITIAL_STATE, ACTION_HANDLERS);
I do not know what I am doing wrong while setting the state. I cannot seem to find out the errors.
Please help me
this.setState({data: this.props.data}); //get data stored in the store
The data is not yet there. If you really wants to set the state to use the prop data, you need to update your state in your componentWillReceiveProps(nextProps) lifecycle function, not on componentDidMount().
Analyzing this function
componentWillMount(){
this.props.getData(); //makes api call
this.setState({data: this.props.data}); //get data stored in the store
}
when you call getData(), the browser will do its thing and your redux state will be updated eventually, so, when the second line is called, the data is not yet there.
You could use some promises and/or async/await functions, but you don't really need to. You can use the regular component lifecycle functions and, event better, you can use this.props.data and not have an internal state at all.
Related
On the outside it seems not an issue, but when I open the DevTools and then go to network tab. It shows that there are 500 requests made. So how can I refactor the code so this will not happens?
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(
"https://raw.githubusercontent.com/XiteTV/frontend-coding-exercise/main/data/dataset.json"
);
dispatch(getData(response.data));
console.log('input');
} catch (err) {
console.log(err);
}
};
fetchData();
}, [dispatch]);
with redux first create a function which will push your request data into redux state like that outside your react component
export const getMyData = () => {
return async (dispatch) => {
const response = await axios.get(
"https://raw.githubusercontent.com/frontend-coding-exercise/main/data/dataset.json"
);
dispatch(getData(response.data));
}
}
create a function that will extract data from redux. state is redux current state, ownProps is your component props, like <Component customProp={1}/>
const mapStateToProps = (state: any, ownProps: any) => {
return {
...ownProps,
myProps: state.user
};
};
In your connect Function pass the function which will store your data in redux state
export default connect(mapStateToProps, { getMyData })(MyReactComponent);
that way you'll be able to access your data via your component props and also access your getMyData function and also the redux state you mapped to props
props.getMyData();
I'm struggling to get my data from a fetch request into the state of my container
My fetch request is stored in api.js and looks like this - it retrieves the key from a constant which is fine:-
import { openWeatherKey } from './constants';
const getWeather = async() => {
const base = "https://api.openweathermap.org/data/2.5/onecall";
const query = `?lat=52.6&lon=-2.2&exclude=hourly,daily&appid=${openWeatherKey}`;
const response = await fetch(base + query);
const data = await response.json();
return data;
}
export { getWeather };
My container looks like this:-
import React, { Component } from "react";
import './weather.css';
import { getWeather } from './api';
class Spy extends Component {
constructor() {
super()
this.state = {test(){return "this is a test"}}
}
render() {
return (
<div id="spy-weather" className="app-border">
<h3 className="spy-name">Weather at { this.props.location } {this.state.test()}</h3>
</div>
)
}
}
(() => {
getWeather().then(data => {
console.log(data);
})
})();
export { Spy as Weather };
I have an IIFE which makes the request and prints the results to the console. You can see that between the class declaration and the export statement above.
Here are the results from the console - the request works fine
{lat: 52.6, lon: -2.2, timezone: "Europe/London", timezone_offset: 3600, current: {…}}
current: {dt: 1594401262, sunrise: 1594353486, sunset: 1594412995, temp: 289.05, feels_like: 286.49, …}
lat: 52.6
lon: -2.2
timezone: "Europe/London"
timezone_offset: 3600
__proto__: Object
What I can't manage to do is set the state with the data from the resolved promise. I've tried various things, including some solutions I've seen which didn't work.
How do I place and run the function within the container and then update state with the data?
I'm pretty new to React as you can probably tell.
With sincere thanks,
Phil
In class based components, lifecycle method known as componentDidMount is used to do something after component has mounted. In your case, move the code in IIFE in the componentDidMount method.
Make a property in state object which will hold the weather data. Optionally, you can also make a property in state object to hold any error message that might occur during the fetching of data from the API.
this.state = {
weatherData: null,
error: ''
};
and then call getWeather() function from componentDidMount() lifecycle method
componentDidMount() {
getWeather()
.then(data => {
this.setState({ weatherData: data });
})
.catch(error => this.setState({ error: error.message }));
}
In functional components, useEffect hook is used to perform any side-effect like fetching data from an API. State in functional components is saved using useState hook.
If you use a functional component, then your code will look like this:
const [weatherData, setWeatherData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
getWeather()
.then(data => {
setWeatherData(data);
})
.catch(error => setError(error.message));
}, []);
this.state = {test(){return "this is a test"}}
This is invalid structure for state managment, right way
getWeather().then(data => {
console.log(data);
this.setState({ weatherData: data });
})
state structure too
state = {
someProperty: value,
someArray: [],
weatherData: {}
}
I have tried numerous ways to set state but for some reason, the state never gets updated.this is the JSON data that I want my state to change to
export class Provider extends Component {
state = {
posts: [],
profileinfo: {},
dispatch: action => this.setState(state => reducer(state, action))
};
componentDidMount() {
fetch("http://localhost:3001/login").then(response =>
response
.json()
.then(data => this.setState({ profileinfo: data.firstname }))
);
console.log(this.state.profileinfo);
}
render() {
// ...
}
}
setState is asynchronous. Your console log probably triggers before the state got updated. If you want to see the result after the setState call, do it this way:
data => this.setState({ profileinfo: data.firstname }, () => {
console.log(this.state);
});
I am using thunk middleware and I have two asynchronous actions creators like following.
export const fetchObject = () => {
return dispatch => {
let action = fetchObjectRequest();
dispatch(action);
let url = "URL1";
let promise = axios.get(url)
.then(response => dispatch(fetchObjectSuccess(response.data)));
return handlingErrorsPromise(promise,
error => {
console.error(JSON.stringify(error));
dispatch(errorOccurred(action, error))
}
);
}
};
Let's assume I have Object1 and Object 2 endpoints, but the problem is Object1 is required by almost all components and I have to somehow merge all other objects with data from Object1.
Ex: Object2 contains peoples id and I have to attach them names from Object1.
Currently I am mapping my component properties to both objects, and I have if statements in render checking if all object are fetched. Like this:
class Peoples extends Component {
componentWillMount(){
this.props.fetchObject1();
this.props.fetchObject2();
}
render() {
let peoples = this.mergeObjects();
//rendering
}
mergeObjects = () => {
let isFetching = this.props.object1.isFetching ||
this.props.object2.isFetching;
if (isFetching) {
return {
isFetching,
json: []
};
}
let mergedJson = {...};
return {
isFetching,
json: mergedJson
};
};
}
const mapDispatchToProps = dispatch => {
return {
fetchObject1: () => dispatch(fetchObject1()),
fetchObject2: () => dispatch(fetchObject2())
}
};
const mapStateToProps = state => {
return {
object1: state.object1,
object2: state.object2
};
};
export default Peoples = connect(mapStateToProps, mapDispatchToProps)(Peoples);
Is there a more elegant way to merge one asynchronous object with others in my store?
I believe you could use the Promise.all api since axios returns a promise.
Promise.all([this.props.fetchObject1(), this.props.fetchObject2()]).then((data) => {
//do stuff with data
})
Update:
You can trigger an action in your view this.props.action.fetchObjects() that does the Promise.all or yield all[] if you're using redux-saga and then trigger another action that updates your store with both of them at once (merged or not). You can easily merge them in your view or even in the selector function.
The result I want is my component to not render unless all the async function have dispatched. I'm using this as a wrapper to make sure everything has dispatched. I've tried two ways:
call everything in componentWillMount and use setState to set loaded = true. I can then render the component based on my state's loaded key.
ajax = async () => {
try{
const { dispatch } = this.props;
dispatch(loadPack('ars'));
dispatch(loadPack('acr'));
await dispatch(loadProds());
await dispatch(loadRepls());
await dispatch(checkEligibile());
}catch (e) { console.log(e)}
}
componentWillMount() {
this.ajax().then(() => {
this.setState({ loaded: true });
});
}
render() {
const { loaded } = this.state;
return loaded ? <Component/> : null;
}
This gets the desired results, except I see this error:
ExceptionsManager.js:71 Warning: Can only update a mounted or mounting
component. This usually means you called setState, replaceState, or
forceUpdate on an unmounted component. This is a no-op.
I tried dispatching in mapDispatchToProps. Ideally loaded should return true and I should see this.props.loaded = true to load my component. However, I'm receiving a Promise and not the result.
I'm feeling stuck here and not sure what else to try. Any suggestions?
const loadAsync = async dispatch => {
dispatch(loadPack('ars'));
dispatch(loadPack('acr'));
await dispatch(loadProds());
await dispatch(loadRepls());
await dispatch(checkEligibile());
return true
};
export const mapDispatchToProps = dispatch => ({
loaded: loadAsync(dispatch),
});
Since you are using redux, you have a global redux state. So after all dispatch, dispatch one more action that toogle a reducer state to true which indicate that all the actions has been dispatched.
In component, use dispatchStateToProps method to convert reducer state into props and then use that prop to check weather all the actions has been dispatched or not. It should roughly look something like this
ajax = async () => {
try{
const { dispatch } = this.props;
dispatch(loadPack('ars'));
dispatch(loadPack('acr'));
await dispatch(loadProds());
await dispatch(loadRepls());
await dispatch(checkEligibile());
// Add one more action here
await dispatch(everythingDispatched());
}catch (e) { console.log(e)}
}
Then a reducer state that keep track of that
dispatchTrackerReducer.js
switch(action.type){
case "everythingDispatched" :
everythingDispatched: true
break;
}
Then in component use mapStateToProps like this
render() {
const { everythingDispatched } = this.props;
return everythingDispatched ? <Component/> : null;
}
function mapStateToProps(state){
return {
everythingDispatched:state.dispatchTrackerReducer.everythingDispatche
}
}