my state get filled and get empty again ? React-Redux - reactjs

Hello I am having a little problem here, I am trying to render data in my component, here is my component :
// Store data in the global state
const mapStateToProps = ({ moduleState }) => ({
operationsContrat: moduleState.contrat.operationsContrat
});
// execute the action
const mapDispatchToProps = dispatch => {
return {
getOperationContrat: (...args) =>
dispatch(getOperationContrat(...args))
};
};
class Operations extends Component {
state = {
data: []
};
// Nod used
componentDidMount() {
if (this.props.operationsContrat.data.length === 0) {
this.props.getOperationContrat(4000003);
}
}
componentWillReceiveProps(nextProps) {
if (this.props.operationsContrat.loading && !nextProps.operationsContrat.loading) {
this.setState({ data: nextProps.operationsContrat.data });
}
}
render() {
console.log("operations");
console.log(this.state.data);
return (
<Fragment />
);
}
}
So I get the data and then my state.data is empty again, whch caused me a problem, because No data is displayed, here is what I see in my console :
So what happens is that my componentWillReceiveComponent is not executed when my render method is executed the last time, I know what happened I guess but I dont know how to solve the problem..
I can provide any needed code if you need it to understand more the problem.
Any help would be much appreciated.

Related

UseEffect not triggering when used with MaterialTopTabNavigator

This is my first time using this platform to ask questions so please pardon me if my question does not seem well developed.
brief introduction
what I am trying to achieve is a dynamic Tab navigator, whereby the number of tabs changes depending on the number of elements in an array where this array changes in the number of elements over time, i.e :
{
userIds : [1,2,3,4,5,6]
}
will render a tab navigator with 6 tabs
I am using react-redux for managing state and I have been following this tutorial on youtube just for your information: https://www.youtube.com/watch?v=9boMnm5X9ak&list=PLC3y8-rFHvwheJHvseC3I0HuYI2f46oAK
context
in the main code snippet the action FetchMonthlyTransIdAct() is being dispatched, this consist of 2 actions being dispatched in order :
RequestMonthlyTransaction → FetchSuccess or FetchFail
(as per mentioned in FetchMonthlyTransIdAct.js, ) the initial state is as follows and the changes each action does :
{
loading : false
Id : []
error : ''
}
{
loading : true //when RequestMonthlyTransaction is dispatched
Id : []
error : ''
}
{
loading : false // When FetchSuccess is dispatched after RequestMonthlyTransaction
Id : [1,2,3,4,5,6]// When FetchSuccess is dispacthed after RequestMonthlyTransaction
error : ''
}
{
loading : false //when FetchFail is dispacthed after RequestMonthlyTransaction
Id : []
error : 'some error message here' //when FetchFail is dispatched after RequestMonthlyTransaction
}
problem
so the problem that I am currently facing is that useEffect does not seem to trigger when I am rendering components with navigationContainer/ tab.navigator
here is the snippet of my code, I have narrowed down the source of the problem between asterisks
const Tab = createMaterialTopTabNavigator();
const mapStateToProps = state => {
return {
userData: state.MonthlyEntry
}
}
const mapDispatchToProps = dispatch => {
return {
FetchMonthlyTransId: () => dispatch(FetchMonthlyTransIdAct())
}
}
const EntryTabNavigator = ({userData, FetchMonthlyTransId}) => {
useEffect (() => {
FetchMonthlyTransId()
}, [])
console.log(userData.Id)
if (userData.loading || userData.error != '') {
return <View/>
} else {
return(
**************************************************************************************
<NavigationContainer independent = {true}>
<Tab.Navigator swipeEnabled = {true} tabBarOptions = {{scrollEnabled:true, tabStyle:{width:120}}}>
{userData.Id.map((data) => {return (<Tab.Screen key = {data.toString()} name = {data.toString()} component = {MonthlyTransactions} initialParams={{id:data.toString()}}/>)})}
</Tab.Navigator>
</NavigationContainer>
**************************************************************************************
)
}
};
export default connect(mapStateToProps, mapDispatchToProps)(EntryTabNaviga
the error message simply that there was no screen for tab navigator to render (due to userData.Id being an empty array when it should not)
based on the console.log(userData.Id)
the expected output should be Array [1,2,3,4,5,6]
but the actual output was Array [] which indicates that the useEffect was not triggered
I have tried replacing the snippet of code between the astericks with
<View><Text>{userData.Id}</Text><View> and it was able to render as expected (returning a screen with the string representation of the array as the text), hence leading me to identify that the code snippet between the astericks is the problematic portion. I have also tried adding a console.log statement within useEffect and it does not output anything into the console when I have the code snippet in asterisks, however it does output into the console when I replaced the snippet of code between the astericks with <View><Text>{userData.Id}</Text><View>
should there be a similar problem to this that has already been asnwered, it would be much apppreciated if you could direct me to it, it would also be great if you could point me to resources to improve my knowledge with redux (prefreably beginner friendly) ! additional reference code (reducer and action) is below
Thank you in advance
FetchMonthlyTransIdAct.js
const requestMonthlyTransaction = () => {
return {
type: "REQUEST_MONTHLY_TRANSACTION",
}
}
const fetchSucess = (ids) => {
return {
type: "FETCH_SUCCESS",
payload: ids,
}
}
const fetchFail = (error) => {
return {
type: "FETCH_FAILURE",
payload: error,
}
}
export const FetchMonthlyTransIdAct = () => {
return (dispatch) => {
dispatch(requestMonthlyTransaction())
async function getId() {
return require('../../data/DummyId.js').id //returns [1,2,3,4,5,6]
}
getId().then(
id => dispatch(fetchSucess(id))
).catch(
error => dispatch(fetchFail(error))
)
}
}
FetchMonthlyTransIdRed.js
const initialState = {
loading:false,
Id : [],
error:''
}
const FetchMonthlyTransactionIdRed = (state = initialState, action) => {
switch (action.type){
case "REQUEST_MONTHLY_TRANSACTION":
return {
...state,
loading: true,
}
case "FETCH_SUCCESS":
return {
...state,
loading: false,
Id: action.payload
}
case "FETCH_FAILURE":
return {
...state,
loading: false,
error: action.payload
}
default: return state;
}
}
export default FetchMonthlyTransactionIdRed;
after much tinkering, I manage to find a solution (or a workaround rather) to the problem. which is to add an initial element in the array of the Id attribute in the initial state in FetchMonthlyTransIdRed.js, that will allow the first render of the navigation component to occur without issues, and subsequently in the next re-render when FetchMonthlyTransId is dispatched Id is then updated with the array that I have imported
Use React Navigation's useFocusEffect, e.g.:
import { useFocusEffect } from '#react-navigation/native';
function Profile({ userId }) {
const [user, setUser] = React.useState(null);
useFocusEffect(
React.useCallback(() => {
const unsubscribe = API.subscribe(userId, user => setUser(user));
return () => unsubscribe();
}, [userId])
);
return <ProfileContent user={user} />;
}

React - Render happening before data is returned and not updating component

I can't get this to work correctly after several hours.
When creating a component that needs data from Firebase to display, the data is returning after all actions have taken place so my component isn't showing until pressing the button again which renders again and shows correctly.
Currently my function is finishing before setState, and setState is happening before the data returns.
I can get setState to happen when the data is returned by using the callback on setState but the component would have already rendered.
How do i get the component to render after the data has returned?
Or what would the correct approach be?
class CoffeeList extends Component {
constructor(props) {
super(props);
this.state = {
coffeeList: [],
}
}
componentDidMount() {
this.GetCoffeeList()
}
GetCoffeeList() {
var cups = []
coffeeCollection.get().then((querySnapshot) => {
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
})
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
}
render() {
const coffeeCups = this.state.coffeeList;
console.log("Rendering component")
return (
<div className="coffee">
<p> This is the Coffee Component</p>
{coffeeCups.map((c) => {
return (
<CoffeeBox name={c.name} />
)
})}
</div >
)
}
}
Thanks
The problem is that you set the state before the promise is resolved. Change the code in the following way:
GetCoffeeList() {
coffeeCollection.get().then((querySnapshot) => {
const cups = []
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
})
}

component mounting before fetch is complete, resulting in failed propTypes

I have a tour website. The basic structure here is that I have the tour page, and on the tour page is a component that handles booking times. When mounted, TourPage fetches the available start times from the db and ships that data down as props.
I am able to receive this data and send it down, but the deeper component gives back a propTypes error, saying the data is undefined. When I console log out (from parent) the value of this data, it logs 3 times: first, it is an empty array. The second and third time, the data is there.
Odd thing is that the propType error appears chronologically AFTER my console.log showing the actual data. I have also added logic to the TourPage to return a Spinner if the availableStartTimes array is empty. I fully expect that no children components will be mounted until all data has arrived. This does not seem to be the case.
Here is some relevant code (edited for brevity):
const ExperienceDetailContainer = () => {
//a function that cleans the fetched data. returns an array
const availableStartTimes = getTimesForCurrentDate(experienceDate, detail.availabilities)
//fetch request to get available start times
const getExperienceAvailabilities = () => {
api.post('/GetExperienceAvailabilities', { experienceid: experienceId })
.then(result => {
const availabilities = result.d
updateExperience(availabilities)
})
.catch(err => {
console.error(err)
})
}
useEffect(() => {
getExperienceAvailabilities()
}, [])
if (availableStartTimes.length < 1) {
return <Spinner />
}
return <ExperienceDetail {...{ availableStartTimes, experienceDate }} />
const mapStateToProps = state => ({
detail: fromResource.getDetail(state, 'experience'),
experienceDate: state.userSelections.experienceDate,
})
const mapDispatchToProps = (dispatch) => ({
readDetail: (apiRequestParams) => dispatch(resourceDetailReadRequest('experience', { apiRequestParams })),
updateExperience: (availabilities) => dispatch(updateExperience('experience', availabilities))
})
here is getTimesForCurrentDate helper function:
export const getTimesForCurrentDate = (date, availabilities) => {
if (!availabilities) {
return []
}
const rawTimes = availabilities[formattedDateWithoutTime(date)]
const properTimes = rawTimes.map(rawTime => {
return convertTimeFromRawToProper(rawTime)
})
return properTimes
}
here is the propTypes for BookCard component
BookCard.propTypes = {
availableStartTimes: T.arrayOf(T.string).isRequired,
}
and the reducer for updateExperience
case UPDATE_EXPERIENCE:
return {
...state,
experience: {
...getResourceState(state, resource),
detail: {
...getDetail(state, resource),
availabilities: payload
}
}
}
here is a screenshot of my console to demonstrate the 3 outputs of availableStartTimes, as well as a console.log(availableStartTimes.toString()) placed after the Spinner guard if statement

ReactJS, componentWillReceiveProps can get data, but render cannot

I am working on a ReactJS 15 project using sagas and reselect as middle ware to fetch data. I can successfully get data in componentWillReceiveProps and set the data in the state, however there still not data in the render function first run when I take the data from the state. Anyone knows what's going on here? BTW, I used json-server as mock data server.Below is part of my code:
Component:
constructor(props) {
super(props);
this.state = {
timelineData: [],
};
}
componentDidMount() {
// use react redux to make the api call here
this.props.fetchTimelineData({
id: parse(this.props.location.search.substr(1)).id,
});
}
componentWillReceiveProps(nextProps) {
console.log('nextProps', nextProps);
// Successfully take the data from the nextProps (timelineData is not [])
const { timelineData } = nextProps;
this.setState({
timelineData,
});
}
render() {
// the first render called timelineData is an empty array, which will not able to populate the UI
// which RaiseTimeline is empty
const { timelineData } = this.state;
return (
<RaiseTimelineStyled>
<RaiseDetailsGrid>
<Accordion
title={
<RaiseAccordionHeader image={image} title={'Timeline'} />
}>
<Timeline.wrapper>
<RaiseTimelineStyled.divider>
<RaiseTimelineStyled.container>
<RaiseTimeline timelineEvents={timelineData} />
action.js (works fine):
export const setTimelineData = timelineData => console.log('actions.js', timelineData) || ({
type: ACTIONS.SET_TIMELINE_DATA,
timelineData,
});
Api.js (works fine):
class TimelineAPI {
// payload will be used after backend done
static fetchTimelineData(payload) {
return http.get(`${baseURI}/timeline`).then(result => console.log('api', result.data) || result.data);
}
}
Reducers: (works fine)
function TimelineDataReducer(state = initialState, action) {
switch (action.type) {
case ACTIONS.SET_TIMELINE_DATA:
console.log('reducer', action.timelineData);
return state.set('numbers', action.timelineData);
default:
return state;
}
}
Sagas: (works fine)
export function* fetchTimelineData(action) {
yield put(togglePendingScreen(true));
const { result, error } = yield call(TimelineAPI.fetchTimelineData, action.payload);
if (error) {
yield put(
toggleErrorModal({
isOpen: true,
text: error.code,
source: 'Fetch Timeline Data',
}),
);
} else {
console.log('Sagas', result.timeline);
yield put(ACTIONS.setTimelineData(result.timeline));
}
yield put(togglePendingScreen(false));
}
Selectors(works fine):
import { createSelector } from 'reselect';
const selectTimelineData = state => state.get('TimelinePageData').toJS();
const selectTimeline = () =>
createSelector(selectTimelineData, TimelineDataState => TimelineDataState.numbers);
export { selectTimeline };
To me it seems logical that you have no data on the first run.
The reason is there render() function is called once before the componentDidMount() in the react life cycle. (V15)
Look here for the react 15 life cycle : https://gist.github.com/bvaughn/923dffb2cd9504ee440791fade8db5f9
I got the answer, everything is correct, but another person name but another person name same component and set its state in side the constructor, so it's not able to render in the first time

Lifecycle hooks - Where to set state?

I am trying to add sorting to my movie app, I had a code that was working fine but there was too much code repetition, I would like to take a different approach and keep my code DRY. Anyways, I am confused as on which method should I set the state when I make my AJAX call and update it with a click event.
This is a module to get the data that I need for my app.
export const moviesData = {
popular_movies: [],
top_movies: [],
theaters_movies: []
};
export const queries = {
popular:
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=",
top_rated:
"https://api.themoviedb.org/3/movie/top_rated?api_key=###&page=",
theaters:
"https://api.themoviedb.org/3/movie/now_playing?api_key=###&page="
};
export const key = "68f7e49d39fd0c0a1dd9bd094d9a8c75";
export function getData(arr, str) {
for (let i = 1; i < 11; i++) {
moviesData[arr].push(str + i);
}
}
The stateful component:
class App extends Component {
state = {
movies = [],
sortMovies: "popular_movies",
query: queries.popular,
sortValue: "Popularity"
}
}
// Here I am making the http request, documentation says
// this is a good place to load data from an end point
async componentDidMount() {
const { sortMovies, query } = this.state;
getData(sortMovies, query);
const data = await Promise.all(
moviesData[sortMovies].map(async movie => await axios.get(movie))
);
const movies = [].concat.apply([], data.map(movie => movie.data.results));
this.setState({ movies });
}
In my app I have a dropdown menu where you can sort movies by popularity, rating, etc. I have a method that when I select one of the options from the dropwdown, I update some of the states properties:
handleSortValue = value => {
let { sortMovies, query } = this.state;
if (value === "Top Rated") {
sortMovies = "top_movies";
query = queries.top_rated;
} else if (value === "Now Playing") {
sortMovies = "theaters_movies";
query = queries.theaters;
} else {
sortMovies = "popular_movies";
query = queries.popular;
}
this.setState({ sortMovies, query, sortValue: value });
};
Now, this method works and it is changing the properties in the state, but my components are not re-rendering. I still see the movies sorted by popularity since that is the original setup in the state (sortMovies), nothing is updating.
I know this is happening because I set the state of movies in the componentDidMount method, but I need data to be Initialized by default, so I don't know where else I should do this if not in this method.
I hope that I made myself clear of what I am trying to do here, if not please ask, I'm stuck here and any help is greatly appreciated. Thanks in advance.
The best lifecycle method for fetching data is componentDidMount(). According to React docs:
Where in the component lifecycle should I make an AJAX call?
You should populate data with AJAX calls in the componentDidMount() lifecycle method. This is so you can use setState() to update your component when the data is retrieved.
Example code from the docs:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
Bonus: setState() inside componentDidMount() is considered an anti-pattern. Only use this pattern when fetching data/measuring DOM nodes.
Further reading:
HashNode discussion
StackOverflow question

Resources