I want to fetch data based on the name of the name of the doctor. On the 1st click on the Sales Overview tab, the component gets mounted and the data is fetched smoothly and I see all the charts get updated. However, when I use the Search button to fetch the data for a new doctor and then click on the Sales Overview tab, I don't see any fetch happening and I continue seeing the data of the previous MD.
Here is a screenshot of the search field and the tabs:
I'm passing the name of the doctor from the parent Search component to the SalesOverview1 child component like this:
{
this.state.tab_button_clicked === 'sales_overview' &&
<SalesOverview1
searchName = {this.state.searchName}
/>
}
Inside SalesOverview1, I'm fetching the data in componentDidMount and then updating the various charts. I'm also setting the state of it based on the props received from parent Search component. I'm keeping a default value of ["[101]Anna"] if there is nothing to pass from search component to child SalesOverview1 comp.
class SalesOverview1 extends Component {
state = {
page_id: 4,
hcp_id: 101,
sales_overview_data: [],
searchName: this.props.searchName.length === 0 ? ["[101]Anna"] : this.props.searchName
}
componentDidMount() {
console.log('Clicked on sales Overview!');
let page_id = this.state.page_id;
let hcp_id = parseInt(this.state.searchName[0].replace(/(^.*\[|\].*$)/g, ''));
console.log('state: ', this.state);
axios.post('/test-json', {
page_id: page_id,
hcp_id: hcp_id,
})
.then((res) => {
const dataRequest = res.data;
this.setState({ sales_overview_data: res.data });
}, (error) => {
console.log(error);
});
}
render() {
console.log('props data inside sales overview comp: ', this.props)
console.log('state inside sales overview comp: ', this.state)
return (
<>
<div class='row'>
{
this.state.sales_overview_data.length !== 0 &&
<ChartBox
data={this.state.sales_overview_data[401]}
/>
}
</div>
</>
Basically, I want to run the api request after I receive a new props in this component. I want to re-mount so that I can make the api request.
In the api fetch, I'm using a replace function to extract the number between the square brackets which is then used for fetching data:
Adding a key while passing props to child component did the trick.
{
this.state.tab_button_clicked === 'sales_overview' &&
<SalesOverview1
key={this.state.finalsearchName}
searchName = {this.state.searchName}
/>
}
I read that adding a key automatically re-mounts the child component if the props has changed.
Related
My problem is in my subcomponent files where every state update in the parent component keeps re-rendering the subcomponents infinitely by making infinite api calls with the default or updated props value passed to the child components. I have a User directory page which contains multiple components in a single page.
class Users extends Component {
constructor(props) {
super(props);
this.state = {
user: "",
listOfUsers: [],
socialData:[],
contactData:[],
videoData:[],
detailsData:[]
}
}
componentDidMount(){
//api call that gets list of users here
//set response data to this.state.listOfUsers
}
userHandler = async (event) => {
this.setState({
user: event.target.value,
});
};
render() {
return (
<div>
<div>
<select
value={this.state.user}
onChange={this.userHandler}
>
// list of users returned from api
</select>
</div>
<div>
<Social Media user={this.state.user} />
<Contact user={this.state.user}/>
<Video user={this.state.user}/>
<Details user={this.state.user}/>
</div>
</div>
);
}
}
I have 1 API call for the parent component Users, and 4 for each of the subcomponents: Social Media, Contact, Video, and Details. The Users api will return a list of users in a dropdown and the value of the user selected is then fed to the other four API's. i.e. https://localhost:3000/social_media?user=${this.state.user}. Thus, the four subcomponents' API is dependent on the Users API. I currently have the parent api call in a componentDidMount() and the other 4 api calls in their respective subcomponents and use props to pass down the value of the user selected in the parent to the subcomponents. Each of the api calls is in a componentDidUpdate(prevProps). All the subcomponents follow this structure:
class Social Media extends Component {
constructor(props) {
super(props);
this.state = {
user: "",
socialData:[],
}
}
componentWillReceiveProps(props) {
this.setState({ user: this.props.user })
}
componentDidUpdate(prevProps){
if (this.props.user !== prevProps.user) {
// make api call here
fetch (`https://localhost:3000/social_media?user=${this.state.user}`)
.then((response) => response.json())
.catch((error) => console.error("Error: ", error))
.then((data) => {
this.setState({ socialData: Array.from(data) });
}
}
render() {
return (
{this.socialData.length > 0 ? (
<div>
<Social Media data={this.state.socialData}/>
</div>
)
:
(<div> Loading ... </div>)
);
}
}
Abortive attempt to answer your question
It's hard to say exactly what's going on here; based on the state shown in the Users component, user should be a string, which should be straightforward to compare, but clearly something is going wrong in the if (this.props.user !== prevProps.user) { comparison.
If we could see the results of the console.log(typeof this.props.user, this.props.user, typeof prevProps.user, typeof prevProps.user) call I suggested in my comment, we'd probably have a better idea what's going on here.
Suggestions that go beyond the scope of your question
Given the moderate complexity of your state, you may want to use some sort of shared state like React's Context API, Redux, or MobX.. I'm partial toward the Context API, as it's built into React and requires relatively less setup.
(Then again, I also prefer functional components and hooks to classes and componentDidUpdate, so my suggestion may not apply to your codebase without a rewrite.)
If this.props.user is an object, then this.props.user !== prevProps.user will evaluate to true because they are not the same object. If you want to compare if they have the same properties and values (i.e. they are shallowly equal) you can use an npm package like shallow-equal and do something like:
import { shallowEqualObjects } from "shallow-equal";
//...
componentDidUpdate(prevProps){
if (!shallowEqualObjects(prevProps.user, this.props.user)) {
// make api call here
fetch (`https://localhost:3000/social_media?user=${this.state.user}`)
.then((response) => response.json())
.catch((error) => console.error("Error: ", error))
.then((data) => {
this.setState({ socialData: Array.from(data) });
}
}
I have a search screen, contain Input And TopTabs "Songs, Artists",
When I get data from API after a search I make two things
1- I setState to appear the TopTab Component "true/false"
2- dispatch an action to save Songs & Artists Data in redux store.
that works fine.
But in topTab component, as I say before I have tow tabs "songs, artists"
For example, In the Songs component, I want to manipulate the data to achieve my case so in componentDidMount I Map the songs array from redux and push the new data into the component state.
But it's not working fine!
At the first time, I got songs from redux as empty [] although it's saved successfully in redux store when I get data from API
So how can I handle this case to not mutate the data?
Search.js "Main screen"
onSearch = async () => {
const {searchText} = this.state;
if (searchText.length > 0) {
this.setState({onBoarding: false}); // to appear the TopTab Component
try {
let response = await API.post('/search', {
name: searchText,
});
let {
data: {data},
} = response;
let artists = data.artists.data;
let songs = data.traks.data;
this.props.getResult(songs, artists);
}
catch (err) {
console.log(err);
}
}
render(){
<View style={styles.searchHeader}>
<Input
onChangeText={text => this.search(text)}
value={this.state.searchText}
onSubmitEditing={this.onSearch}
returnKeyType="search"
/>
</View>
{this.state.onBoarding ? (
<SearchBoard />
) : (
<SearchTabNavigator /> // TopTabs component
)}
}
SongsTab
...
componentDidMount() {
console.log('props.songs', this.props.songs); // Empty []
let All_tunes = [];
if (this.props.songs?.length > 0) {
console.log('mapping...');
this.props.songs.map(track =>
All_tunes.push({
id: track.id,
name: track.name,
url: URL + track.sounds,
img: URL + track.avatar,
}),
);
this.setState({All_tunes});
}
}
...
const mapStateToProps = state => {
return {
songs: state.searchResult.songs,
};
};
Edit
I fix the issue by using componentDidUpdate() life cycle
If you have any other ways tell me, please!
SongsTab
manipulateSongs = arr => {
let All_tunes = [];
arr.map(track =>
All_tunes.push({
id: track.id,
name: track.name,
url: URL + track.sounds,
img: URL + track.avatar,
}),
);
this.setState({All_tunes});
};
componentDidMount() {
if (this.props.songs?.length > 0) {
this.manipulateSongs(this.props.songs);
console.log('mapping...');
}
}
componentDidUpdate(prevProps) {
if (prevProps.songs !== this.props.songs) {
this.manipulateSongs(this.props.songs);
}
}
The problem you're referring to has to do with the way asynchronous code is handled in JavaScript (and in turn react-redux). When your component initially mounts, your redux store passes its initial state to your SongsTab.js component. That seems to be an empty array.
Any API call is an asynchronous action, and won't update the redux store until the promise has resolved/rejected and data has been successfully fetched. Any HTTP request takes much longer to complete than painting elements to the DOM. So your component loads with default data before being updated with the response from your API call a number of milliseconds later.
The way you've handled it with class-based components is fine. There are probably some optimizations you could add, but it should work as expected. You might even choose to render a Spinner component while you're fetching data from the API as well.
If you want a different approach using more modern React patterns, you can try and use the equivalent version with React hooks.
const Songs = ({ fetchSongs, songs, ...props }) => {
React.useEffect(() => {
// dispatch any redux actions upon mounting
// handle any component did update logic here as well
}, [songs])
// ...the rest of your component
}
Here are the docs for the useEffect hook.
I am new to react, I am getting data from redux, first, I get an object from accounts from redux, then I pass this to the function in redux and set a value in numReg in the reducer.
When I call a function by this.props.fetchAccountDetail(data) in actions its send a request to API and fetch the data from API and save it in reducer or store. When i call function in render by
this.getDataFromAccount(accountDetail.num), it goes in infinite loop.
I want data in a return, it should only run one time.
import React, { Component } from 'react'
import { fetchAccountDetail, } from '../../../actions'
class myclass extends Component {
state = {
num : ''
};
getAccounts = (data) => {
if (!data) { return; }
return data.find(item => item.id == this.props.match.params.id);
}
getDataFromAccount = (data) => {
this.props.fetchAccountDetail(data);
// This is a api , which provide the result agaisnt
// a num and set value in numReg in reducer
}
render() {
const { accounts, numReg } = this.props;
const accountDetail = this.getAccounts(accounts);
// Here i will get a match object like {id :1 , num :12345}
const test=this.getDataFromAccount(accountDetail.num)
// When i call this , it stucks in infinite loop , how can i get data only once when it render
console.log(test)
return (
<div />
);
}
}
const mapStateToProps = state => {
return { accounts : state.accounts.accounts | [{id :1 , num :12345} , {id :2 , num :535234}],
numReg : state.accounts.numReg
//Is a object containg the information of num from accounts
}
}
export default (compose(
withStyles(styles),
connect(mapStateToProps, { fetchAccountDetail,}))(myclass));
It should return data in variable test after fetching data from redux.
You should never call data fetching functions or functions which alter the state within render.
Render may be called multiple times if a parent rerenders or just its internal state changes.
Calling fetchAccountDetails in render updates the redux store. Redux will pass the new but equal data as props into your component.
That Component will rerender because its props changed and will call fetchAccountDetails again => loop. Render should only display data!!
For data fetching, 2 functions exist. componentDidMount which will be called after the component is visible. That would be a good place to call your fetch.
If you need a prop to fetch the data for e.g. an Id of some sort (fetch data for that Id), you would use componentDidUpdate in which you compare the new id and the old id to see if you need to fetch the data again.
You should read the docs and look at some tutorials.
Hope this helps.
Happy coding.
As Domino987 answered, you need to make use of lifecycle methods. Here's an example of how it might look:
componentDidMount() {
const { accounts } = this.props;
const accountDetail = this.getAccounts(accounts);
const accountData = this.getDataFromAccount(accountDetail.num)
this.setState({
account: {
accountDetail: accountDetail,
accountData: accountData
}
})
}
componentDidUpdate() {
const { accounts } = this.props;
const accountDetail = this.getAccounts(accounts);
const accountData = this.getDataFromAccount(accountDetail.num)
if (this.state.account.accountData !== this.getDataFromAccount(accountDetail.num)) {
this.setState({
account: {
accountDetail: accountDetail,
accountData: accountData
}
})
}
}
Let's say we got two different React components. One contains reports with dates, the other should show employees that worked that particular month.
So depending on what reports month was clicked, I need to be able to show those employees, but in a second component.
I'm able to get the date that was clicked in the first one but in order to know which employees to show I need to compare that data (from the 1st component) with employees data (second component).
The big question here is - HOW CAN I TRANSFER THAT NEWLY CONSTRUCTED (onClick - Custom function)EVENTS DATA TO THAT SECOND COMPONENT SO I CAN COMPARE THEM ??
You can create a "Parent" component which will render your two components.
The Parent component will have the selected date in the state.
class Parent extends Component {
constructor() {
this.handleDateChange = this.handleDateChange.bind(this);
this.state = { date: null };
}
handleDataChange(date) {
this.setState({ date });
}
render() {
return (
<div>
<Component1 onDateChange={this.handeDataChange} />
<Component2 date={this.state.date} />
</div>
);
}
}
You have to update your Component1 to receive onDateChange, and you have to call that function when the date is updated:
// where the date is updated
this.props.onDateChange(newDate);
Also you have to update your Component2 to receive date (the selected date) which you can use to filter your employees:
// maybe in the render function... you will know the selected date with this.props.date. For example you could do something like this:
const filtered = this.employees.filter(employee => employee.date === this.props.date);
How does this work?
when you select your date in your first component, it will call handleDateChange
... It will update Parent's state
... then Parent's render function will be called (because the state changed)
... then it will pass the new date (stored in the state) to the second component.
Contain the component within a common parent component that's then able to act as a broker for the relevant data.
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor.
[From: lifting state up]
This is simplified but something like:
class Employees extends Component {
state = {
employees: []
}
async componentDidMount() {
const { clickedDate } = this.props
const employees = await fetchEmployees(clickedDate) // or whatever
this.setState({ employees })
}
render() {
const { employees } = this.state
if (employees.length === 0) {
return
<p>Loading...</p>
}
return (
<div className='employees'>
{
employees.map(employee => (
<p>{employee}</p>
))
}
</div>
)
}
}
const Reports = ({ dates, setClickedDate }) => (
<div className='reports'>
{
dates.map(date => (
<p onClick={() => setClickedDate(date)}>{date}</p>
))
}
</div>
)
class Parent extends Component {
state = {
clickedDate: undefined,
dates: ['dates', 'from', 'somewhere']
}
setClickedDate = clickedDate => this.setState({ clickedDate })
render() {
const { clickedDate } = this.state
return [
<Reports dates={dates} setClickedDate={this.setClickedDate} />,
<Employees clickedDate={clickedDate} />
]
}
}
I have a React component that contains an input. The component dispatches a redux action that calls an API. I want to display a "SUCCESS" message per component but I can't work out how to get the message, other than through the reducer? But if I was to do it through the reducer it would just be a general message that would update all the form components?
//COMPONENT
export default class Stock extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
id: this.props.hit['Name of Item'],
}
}
handleChange(e){
e.preventDefault();
this.setState({value: e.target.value});
}
handleClick(e){
e.preventDefault();
this.props.dispatch(updatePosStock(this.state.value, this.state.id));
}
render() {
return (
<div className="item">
<p>{this.props.hit['Name of Item']}: {this.props.hit.Quantity}</p>
<p>Stock ID: {this.props.hit.objectID}</p>
<div className="control">
<input className="input" value={this.state.value} type="text" onChange={this.handleChange.bind(this)} />
<a href="" onClick={this.handleClick.bind(this)} >save</a>
//MESSAGE TO DISPLAY HERE DEPENDING ON handleClick
</div>
</div>
)
}
}
//ACTION
export function updatePosStock(product, stock){
return function(dispatch) {
axios.post('/api/v1/product/', {
product,
stock
})
.then((response) => {
//console.log(response.data);
return response.data;
//dispatch({type: 'FETCH_PRODUCT_FULFILLED', payload: response.data})
})
.catch((error) => {
console.log(error);
})
}
}
If I understand, this component is rendered multiple times on the page and when you trigger action on one, message gets distributed to all the components (because it is not distinguishable between components.
So, I'd set message to be something like:
[{
message: 'Success',
item: itemId
},
...]
and when sending a data to api I'd send for which item it is being sent and would just push this message to message array, then in place where you want to display message you put
{this.displayMessage(messagesVar, index) || ''}
So when display message is not falsy it displays message.
messageVar is what your reducer updates.
Display message function is smt like
(messages, id) => {
currentComponentMsgs = messages.filter((item) => item.id === id);
return currentComponentMsgs.length > 0
? currentComponentMsgs.mat((item) => item.message).reduce((a,b) => a + ', ' + b}
: '');
}
I did not have time to check if it all works, but if you propagate id of each elemnt properly, using this pattern you can even have multiple messages recorder in messagesVar, so it serves as kind of a mailbox for components. You can use it on all of your components.
There are two ways you can achieve this
1. save your response message (e.g. 'success') in the form of an array
In this case you can define a state such as feedback: [], for example. Then you will set the message this way
...
//dispatch({type: 'FETCH_PRODUCT_FULFILLED', payload: [ product : response.data]})
..
Then in your component you do the following
...
<input className="input" value={this.state.value} type="text" onChange={this.handleChange.bind(this)} />
<a href="" onClick={this.handleClick.bind(this)} >save</a>
//MESSAGE TO DISPLAY HERE DEPENDING ON handleClick
<p className="feedback">{this.props.feedback[this.state.value] || ''}</p>
Depending on how you import your states in to your components.
2. Create another property in your redux state or inside this.state={}
for example inputKey. Then set inputKey value through handleClick(e, inputKeyValue)
You could pass inputKey as a third parameter to the function dispatched and continue with display as should above in you component.
I strongly recommend that you handle all your states with redux and avoid using this.state and this.setState() as much as you can.
I hope it helps.
Use callback for your action:
handleClick(e){
e.preventDefault();
this.props.dispatch(updatePosStock(this.state.value, this.state.id,
(code, message)=>{if (code==='OK')this.setState({displayMessage:true, message:"Successfully saved"})}));
}
///ACTION
export function updatePosStock(product, stock, callback){
return function(dispatch) {
axios.post('/api/v1/product/', {
product,
stock
})
.then((response) => {
callback('OK')
return response.data;
//dispatch({type: 'FETCH_PRODUCT_FULFILLED', payload: response.data})
}) .catch((error) => { callback(error.code, error.message)
console.log(error);
})
}
}