React MOBX Component Render - javascript

I just switched out this.setState to use mobx observable, because I have multiple GET requests that fetch data. This prevents the PieChart from being re-rendered every time this.setState is called.
However, now the child component does not ever get re-rendered and stays with the initial placeholder mobxState. How can I get the PieChart child component to re-render when the data for it comes in from the API.
class Device extends React.Component {
mobxState = {
customOptions: [],
rowData: []
};
//mount data
componentDidMount() {
//call the data loader
this.fetchData();
}
fetchData = () => {
axios
.get("/custom_options.json")
.then(response => {
this.mobxState.customOptions = response.data.custom_options;
})
.then(
//data for PieChart, need this portion to render the PieChart
axios.get("/devices.json").then(response => {
this.mobxState.rowData = response;
})
);
};
render() {
return <PieChart data={this.mobxState.rowData} />;
}
}
decorate(Device, {
mobxState: observable
});
export default Device;

You need to make sure your Device component is an observer, and if you are using a MobX version below 5 you have to slice() or peek() the array in the render method.
import { observer } from "mobx-react";
class Device extends React.Component {
// ...
render() {
return <PieChart data={this.mobxState.rowData.slice()} />;
}
}
decorate(Device, {
mobxState: observable
});
export default observer(Device);

Related

How to get state in react after render

I am trying to fetch data from firebase. I am able to get the data and update the state, but state returns undefined after render in my React context Provider. I have tried to use some of the Life cycle method like componentWillMount or calling my fetchData function my the constructor function , since it get called before render, but none is working. Below is my code.
import React, { Component } from 'react';
import { dataDB, productDetail } from './data';
import { db } from './config/fbConfig'
import { TimerSharp } from '#material-ui/icons';
const ProductContext = React.createContext();
class ProductProvider extends Component {
constructor(props) {
super(props)
this.state = {
products: []
}
this.fetchData()
}
fetchData = () => {
db.collection("projects")
.get()
.then(querySnapshot => {
const data = querySnapshot.docs.map(doc => doc.data());
console.log(data); //successfully returns the data
// this.setState({ projects: data });
this.setState(() => {
return {
projects: data
}
})
console.log(this.state.products) // successfully returns the data and update the state
});
}
render() {
console.log(this.state.products) // returns empty arr and I need it to return the updated state with data
return (
<ProductContext.Provider value={{
...this.state
}}>
{this.props.children}
</ProductContext.Provider>
)
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
The issue is this.state.products get called before calling data in firebase. Please how can I be able to get data after render.
In fetchData() you set the attribute this.state.projects but in render you log this.state.products

basic reactjs, how to get REST data and render it

I have a basic rect component and I already figured out how to get data from a protected rest api, however I am not sure how to render it in the component and how to call that function, or in which lifecycle I should call the function.
import React, { Component } from 'react';
import LayoutContentWrapper from '../components/utility/layoutWrapper';
import LayoutContent from '../components/utility/layoutContent';
var q = require('q');
var Adal = require('../adal-webapi/adal-request');
function getValues() {
var deferred = q.defer();
Adal.adalRequest({
url: 'https://abc.azurewebsites.net/api/values'
}).then(function(data) {
console.log(data);
}, function(err) {
deferred.reject(err);
});
return deferred.promise;
}
export default class extends Component {
render() {
return (
<LayoutContentWrapper style={{ height: '100vh' }}>
<LayoutContent>
<h1>Test Page</h1>
</LayoutContent>
</LayoutContentWrapper>
);
}
}
The lifecycle method you choose to fetch the data in will largely depend on whether or not you need to update the data at any point and re-render, or whether that data depends on any props passed to the component.
Your example looks as though it is a one time API call that doesn't depend on any props, so placing it in the constructor would be valid.
I would move the getValues code to within the class, and do something like this. Note: I've used async/await, but you could use promise callbacks if you prefer.
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
this.fetchData();
}
async fetchData() {
try {
const data = await this.getValues();
!this.isCancelled && this.setState({ data });
} catch(error) {
// Handle accordingly
}
}
getValues() {
// Your API calling code
}
componentWillUnmount() {
this.isCancelled = true;
}
render() {
const { data } = this.state;
return (
<ul>
{data && data.map(item => (
<li>{item.name}</li>
))}
</ul>
);
}
}
If you needed to fetch the data again at any point, you might use one of the other lifecycle hooks to listen for prop changes, and call the fetchData method again.
Note the inclusion of a failsafe for the component un-mounting before the async call has finished, preventing React from throwing an error about setting state in an unmounted component.
something like this...
export default class extends React.Component {
constructor(props) {
super(props);
// initialize myData to prevent render from running map on undefined
this.state = {myData: []};
}
// use componentDidMount lifecycle method to call function
componentDidMount() {
// call your function here, and on promise execute `setState` callback
getValues()
.then(data => {
this.setState({myData: data})
}
}
render() {
// create a list
const items = this.state.myData.map((datum) => {
return <LayoutContent>
<h1>{datum}</h1>
</LayoutContent>
});
// return with the list
return (
<LayoutContentWrapper style={{ height: '100vh' }}>
{items}
</LayoutContentWrapper>
);
}
}

Update variable in React in class not extending component

I am trying to wrap my head around ReactJS and I am stumped with an issue where I want to be able to update the value of a local variable and return the updated value.
I've read about state and I've used that when working with React Components, however, this class is just defined as const and it doesn't extend React.Component.
Is there a different way I should be defining setting the variable?
Here is a simplified version of my code:
import React from 'react';
const WelcomeForm = ({welcome}) => {
var welcomeMsg = 'Test';
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
// response.text has content
welcomeMsg = response.text;
}).catch(() => {
welcomeMsg = '';
});
return (
<p>{welcomeMsg}</p> // Returns 'Test'
);
};
export default WelcomeForm;
The easiest option here is to change your stateless component to a stateful component.
Stateless components are just JavaScript functions. They take in an
optional input, called prop.
Stateful components offer more features, and with more features comes more baggage. The primary reason to choose class components (stateful) over functional components (stateless) is that they can have state, that is what you want to update to re-render.
Here is what you can do:
class WelcomeForm extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
return (
<p>{welcomeMsg}</p>
);
}
};
If you want, for any reason, to keep your component stateless, you will have to put the loadDynamicContent() function on the Parent and pass the text to WelcomeForm as a prop. For example:
// Your WelcomeForm Component
const WelcomeForm = ({welcomeMsg}) => (
<p>{welcomeMsg}</p>
);
// Whatever it's Parent Component is
class Parent extends React.Component {
state = {
welcomeMsg: ''
}
fetchFromApi() {
DynamicContentApi.loadDynamicContent("welcome_test")
.then(response => {
// response.text has content
this.setState({welcomeMsg: response.text});
})
.catch((e) => console.log(e));
}
componentDidMount() {
fetchFromApi();
}
render() {
<WelcomeForm welcomeMsg={this.state.welcomeMsg} />
}
}
As suggested in the comments, you can pass the DynamicContentApi logic to outside:
import ReactDOM from 'react-dom'
DynamicContentApi.loadDynamicContent('welcome_test').then((response) => {
ReactDOM.render(<WelcomeForm data={response.text} />, document.getElementById('where you wanna render this'));
}).catch(() => {
console.log('error while fetching...');
});
And where you have your component:
import React from 'react';
export default class WelcomeForm extends React.Component {
render() {
return (
<p>{this.props.data}</p>
);
}
}

MapStateToProps not working

I am trying to use redux with react. The mapStateToProps is not called.
The dispatch calls are working properly, the action reaches the reducer and the newState is returned and the state of the store is modified. But mapStateToProps is not called and the component is not rerendering.
class Wrapper extends React.Component {
updateChartData (data) => {
...logic...
this.props.actions.chartDataAction(modifiedData);
}
}
function mapStateToProps(state, ownProps) {
return {
chartData: state.chartData
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(chartDataAction, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Wrapper);
The other classes that use updated state is as follows
class Chart extends React.Component {
render(){
/*console.log(this.props.chartData);*/
}
}
function mapStateToProps(state, ownProps) {
console.log(state);
return {
chartData: state.chartData
};
}
let ChartComponent = withStyles(styles)(Chart);
export default connect(mapStateToProps)(ChartComponent);
The action is as follows
export default function (state = [], action) {
if (action.type === actionTypes.CHANGE_CHARTDATA) {
return action.chartData;
} else {
return state;
}
}
The reducer is
export default function (state = [], action) {
if (action.type === actionTypes.CHANGE_CHARTDATA) {
return action.chartData;
} else {
return state;
}
}
I've even checked using redux dev tools. The chartData is being updated in the redux store. The chartData is properly passed to the Chart class and it renders on the first update. On consecutive dispatching, the chartData is updated in the redux store, but the mapStateToProps of Chart class is not called and the Chart class is not rerendering.
UPDATE:
I'm trying to live update the chart and it does not render if the few entries in the chartData array are changed.
I noticed that the chart component rerenders when the whole chartData is changed. But I want it to rerender if any part of the chartData is changed.
Edit : Be sure to follow the immutable update patterns even outside of your reducer (in updateChartData() method in your case).
You are probably mutating data in your "...logic...", you can use immutable-js to make it easier for you.

React/Redux Loading application state in component constructor

I'm rendering high-order component, say Application and I need to fetch some data from server, before it's rendered. What I do, in constructor of Application I issue loadApplicationState() action, that performs server call and prepares initial state.
Some simplified code,
class Application extends Component {
constructor(props) {
super(props);
const { dispatch } = this.props;
dispatch(loadApplicationState());
}
render() {
const { stateLoaded } = this.props.state;
render (
<div>
{ stateLoaded ? renderApp() : renderProgress() }
</div>
)
}
}
function loadApplicationState() {
return (dispatch) => {
// fetch data and once ready,
applicationStateLoaded(data);
}
}
I've tried that on practice, it works fine. But not sure is this a right approach? Especially using a constructor for such purposes.
We run this in componentDidMount, and then test for an $isLoading flag in our Redux state, rendering either a loading indicator or the actual UI. Something like so:
import React, { Component } from 'react';
const mapStateToProps = (state) => ({
$isLoading: state.initialState.$isLoading
})
const mapDispatchToProps = (dispatch) => ({
loadApplicationState(){ dispatch(loadApplicationState()); }
})
export class Application extends Component {
componentDidMount(){
this.props.loadApplicationState();
}
render(){
const {
$isLoading
} = this.props;
{$isLoading ? (<Loader />) : <ActualApplication />}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Application)

Categories