React: How to update one component, when something happens on another component - javascript

I have an application with a table, the table has a checkbox to set a Tenant as Active, this variable is a global variable that affects what the user does in other screens of the application.
On the top right of the application, I have another component called ActiveTenant, which basically shows in which tenant the user is working at the moment.
The code of the table component is like this:
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
class ListTenants extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.id,
TestSiteCollectionUrl: row.TestSiteCollectionUrl,
TenantName: row.TenantName,
Email: row.Email
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
const columns = [
{
title: 'TenantName',
dataIndex: 'TenantName',
key: 'TenantName',
},
{
title: 'TestSiteCollectionUrl',
dataIndex: 'TestSiteCollectionUrl',
key: 'TestSiteCollectionUrl',
},
{
title: 'Email',
dataIndex: 'Email',
key: 'Email',
}
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
if(selectedRows[0].TenantName != undefined){
console.log(selectedRows[0].TenantName);
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+selectedRows[0].TenantName.toString(), options)
.then(response =>{
if(response.status === 200){
Notification(
'success',
'Tenant set to active',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Tenant not activated',
error
);
console.error(error);
});
}
},
getCheckboxProps: record => ({
type: Radio
}),
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListTenants;
And the code of the ActiveTenant component its also very simple
import React, { Component } from 'react';
import authAction from '../../redux/auth/actions';
import { adalApiFetch } from '../../adalConfig';
class ActiveTenant extends Component {
constructor(props) {
super(props);
this.state = {
tenant: ''
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant/GetActiveTenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
this.setState({ tenant: responseJson.TenantName });
}
})
.catch(error => {
this.setState({ tenant: '' });
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
return (
<div>You are using tenant: {this.state.tenant }</div>
);
}
}
export default ActiveTenant;
The problem is, if I have multiple tenants on my database registered and I set them to active, the server side action occurs, and the state is changed, however on the top right its still showing the old tenant as active, UNTIL I press F5 to refresh the browser.
How can I achieve this?
For the sake of complete understandment of my code I will need to paste below other components:
TopBar which contains the active tenant
import React, { Component } from "react";
import { connect } from "react-redux";import { Layout } from "antd";
import appActions from "../../redux/app/actions";
import TopbarUser from "./topbarUser";
import TopbarWrapper from "./topbar.style";
import ActiveTenant from "./activetenant";
import TopbarNotification from './topbarNotification';
const { Header } = Layout;
const { toggleCollapsed } = appActions;
class Topbar extends Component {
render() {
const { toggleCollapsed, url, customizedTheme, locale } = this.props;
const collapsed = this.props.collapsed && !this.props.openDrawer;
const styling = {
background: customizedTheme.backgroundColor,
position: 'fixed',
width: '100%',
height: 70
};
return (
<TopbarWrapper>
<Header
style={styling}
className={
collapsed ? "isomorphicTopbar collapsed" : "isomorphicTopbar"
}
>
<div className="isoLeft">
<button
className={
collapsed ? "triggerBtn menuCollapsed" : "triggerBtn menuOpen"
}
style={{ color: customizedTheme.textColor }}
onClick={toggleCollapsed}
/>
</div>
<ul className="isoRight">
<li
onClick={() => this.setState({ selectedItem: 'notification' })}
className="isoNotify"
>
<TopbarNotification locale={locale} />
</li>
<li>
<ActiveTenant />
</li>
<li
onClick={() => this.setState({ selectedItem: "user" })}
className="isoUser"
>
<TopbarUser />
<div>{ process.env.uiversion}</div>
</li>
</ul>
</Header>
</TopbarWrapper>
);
}
}
export default connect(
state => ({
...state.App.toJS(),
locale: state.LanguageSwitcher.toJS().language.locale,
customizedTheme: state.ThemeSwitcher.toJS().topbarTheme
}),
{ toggleCollapsed }
)(Topbar);
App.js which contains the top bar
import React, { Component } from "react";
import { connect } from "react-redux";
import { Layout, LocaleProvider } from "antd";
import { IntlProvider } from "react-intl";
import { Debounce } from "react-throttle";
import WindowResizeListener from "react-window-size-listener";
import { ThemeProvider } from "styled-components";
import authAction from "../../redux/auth/actions";
import appActions from "../../redux/app/actions";
import Sidebar from "../Sidebar/Sidebar";
import Topbar from "../Topbar/Topbar";
import AppRouter from "./AppRouter";
import { siteConfig } from "../../settings";
import themes from "../../settings/themes";
import { themeConfig } from "../../settings";
import AppHolder from "./commonStyle";
import "./global.css";
import { AppLocale } from "../../dashApp";
import ThemeSwitcher from "../../containers/ThemeSwitcher";
const { Content, Footer } = Layout;
const { logout } = authAction;
const { toggleAll } = appActions;
export class App extends Component {
render() {
const { url } = this.props.match;
const { locale, selectedTheme, height } = this.props;
const currentAppLocale = AppLocale[locale];
return (
<LocaleProvider locale={currentAppLocale.antd}>
<IntlProvider
locale={currentAppLocale.locale}
messages={currentAppLocale.messages}
>
<ThemeProvider theme={themes[themeConfig.theme]}>
<AppHolder>
<Layout style={{ height: "100vh" }}>
<Debounce time="1000" handler="onResize">
<WindowResizeListener
onResize={windowSize =>
this.props.toggleAll(
windowSize.windowWidth,
windowSize.windowHeight
)
}
/>
</Debounce>
<Topbar url={url} />
<Layout style={{ flexDirection: "row", overflowX: "hidden" }}>
<Sidebar url={url} />
<Layout
className="isoContentMainLayout"
style={{
height: height
}}
>
<Content
className="isomorphicContent"
style={{
padding: "70px 0 0",
flexShrink: "0",
background: "#f1f3f6",
position: "relative"
}}
>
<AppRouter url={url} />
</Content>
<Footer
style={{
background: "#ffffff",
textAlign: "center",
borderTop: "1px solid #ededed"
}}
>
{siteConfig.footerText}
</Footer>
</Layout>
</Layout>
<ThemeSwitcher />
</Layout>
</AppHolder>
</ThemeProvider>
</IntlProvider>
</LocaleProvider>
);
}
}
export default connect(
state => ({
auth: state.Auth,
locale: state.LanguageSwitcher.toJS().language.locale,
selectedTheme: state.ThemeSwitcher.toJS().changeThemes.themeName,
height: state.App.toJS().height
}),
{ logout, toggleAll }
)(App);
I think this should be enough to illustrate my question.

You're not using redux correctly there. You need to keep somewhere in your redux state your active tenant; That information will be your single source of truth and will be shared among your components throughout the app. Every component that will need that information will be connected to that part of the state and won't need to hold an internal state for that information.
Actions, are where you would call your API to get your active tenant information or set a new tenant as active. These actions wil call reducers that will change your redux state (That includes your active tenant information) and those redux state changes will update your app components.
You should probably take some time to read or re-read the redux doc, it's short and well explained !

Related

Sending API data in Gatsby to be used in Chart.js

I am trying to send API data being called from my index.js to my ChartData.js. index.js is a page and ChartData.js is a component in Gatsby, so to begin with I could not figure out if Gatsby's Link to="" function only work from one page to another or if it can send data from a page to a component.
The issue is when I try to access the sent data from index.js to ChartData.js in the line {props.location.state.companyName} I am getting the error: TypeError: props.location is undefined
I plan to switch out labels: ['x', 'y'] for something like labels: [{props.location.state.companyName}, {props.location.state.symbol} etc. I am not sure if this would be the correct syntax either.
A more detailed explanation here: https://www.youtube.com/watch?v=No9cqzqlKS0&feature=youtu.be
index.js:
import React from "react"
import { Link } from "gatsby"
import axios from "axios"
import "../css/style.css"
import Layout from "../components/layout"
import { symbol } from "prop-types"
import ChartData from "../components/ChartData"
export default class index extends React.Component {
state = {
companyName: "",
previousClose: "",
marketCap: "",
change: "",
symbol: "",
topStocks: [],
Yearweekhigh: "",
Yearweeklow: "",
avgTotalVolume: "",
peRatio: ""
}
componentDidMount() {
const API_KEY = '*******************';
axios.get(`https://cloud.iexapis.com/stable/stock/market/previous?token=${API_KEY}`)
.then(res => {
console.log(res)
const topStocks = res.slice(1);
this.setState({ topStocks })
})
}
clickHandler = (event) => {
if (event.keyCode === 13) {
const query = event.target.value;
const API_KEY = '*******************';
axios.get(`https://cloud.iexapis.com/stable/stock/${query}/quote?token=${API_KEY}`)
.then(res => {
const companyName = res.data['companyName'];
this.setState({ companyName })
const previousClose = res.data['previousClose'];
this.setState({ previousClose })
const marketCap = res.data['marketCap'];
this.setState({ marketCap })
const change = res.data['change'];
this.setState({ change })
const symbol = res.data['symbol'];
this.setState({ symbol })
const Yearweekhigh = res.data['week52High'];
this.setState({ Yearweekhigh })
const Yearweeklow = res.data['week52Low'];
this.setState({ Yearweeklow })
const avgTotalVolume = res.data['avgTotalVolume'];
this.setState({ avgTotalVolume })
const peRatio = res.data['peRatio'];
this.setState({ peRatio })
})
}
}
render() {
return (
<Layout>
<div class = "main-div">
<input type="search" class="main-search" onKeyDown={event => this.clickHandler(event)}/>
<table>
<tr>
<th>Ticker-Symbol</th>
<th>Market Cap</th>
<th>Previous Close</th>
</tr>
<tr>
<td>
<Link to='/details/' state={{
setState: this.state.symbol,
companyName: this.state.companyName,
previousClose: this.state.previousClose,
marketCap: this.state.marketCap,
change: this.state.change,
Yearweekhigh: this.state.Yearweekhigh,
Yearweeklow: this.state.Yearweeklow,
avgTotalVolume: this.state.avgTotalVolume,
peRatio: this.state.peRatio
}}>
{this.state.symbol}</Link>
<Link to='/ChartData/' state={{
setState: this.state.symbol,
companyName: this.state.companyName,
previousClose: this.state.previousClose,
marketCap: this.state.marketCap,
change: this.state.change,
Yearweekhigh: this.state.Yearweekhigh,
Yearweeklow: this.state.Yearweeklow,
avgTotalVolume: this.state.avgTotalVolume,
peRatio: this.state.peRatio
}}></Link>
</td>
<td>{this.state.marketCap}</td>
<td>{this.state.previousClose}</td>
</tr>
</table>
</div>
<div>
{
this.state.topStocks.length && this.state.topStocks.map(stock => (
<h1>{stock.symbol}</h1>
))
}
</div>
<ChartData />
</Layout>
)
}
}
details.js
//import { Link } from "gatsby"
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import Layout from '../components/layout';
import "../css/style.css"
const Details = props => {
const [yourState, setYourState] = useState('');
useEffect(() => {
}, []);
return <Layout>
<div>
<h1 class="details-company-name">{props.location.state.companyName}</h1>
<div class = "details-div">
<div class="details-div-1">
<p>Open {} </p>
<p>High {} </p>
<p>Low {} </p>
<p>52 WK HIGH <h2>{props.location.state.Yearweekhigh}</h2> </p>
<p>52 WK LOW <h2>{props.location.state.Yearweeklow}</h2> </p>
</div>
<div class="details-div-2">
<p>VOLUME</p>
<p>AVG VOL <h2>{props.location.state.avgTotalVolume}</h2> </p>
<p>MKT CAP <h2>{props.location.state.marketCap}</h2></p>
<p>P/E RATIO <h2>{props.location.state.peRatio}</h2></p>
<p>DIV/YIELD</p>
</div>
</div>
</div>
</Layout>;
};
export default Details;
ChartData.js
import React, {useState, useEffect } from "react";
import { Line } from "react-chartjs-2";
const ChartData = props => {
const [yourState, setYourState] = useState('');
const chart = () => {
setYourState({
labels: ['x', 'y'],
datasets: [
{
level: 'level of xyz',
data: [22, 55]
}
]
})
}
useEffect(() => {
chart()
}, [])
return(
<div>
<h1>Hello</h1>
{props.location.state.companyName}
<div>
<Line data={yourState}/>
</div>
</div>
)
}
export default ChartData;
There's a quite a bit going on here that needs clarification. You mention graphql in the title, but there's no graphql in your code.
You are using axios to fetch data at runtime in the componentDidMount lifecycle method, and then setting the result to state.
I assume that once you have that data, all you want to do is pass it to your chart component so that it can render itself on the index page.
Consider the following example which does the same thing; Fetches some data from the Rick & Morty api, sets the results to state, and passes the relevant part of that state via props directly to the <Characters /> component.
From there, the <Characters /> component has everything it needs in order to render. (It has no state, and is not concerned about where the data actually came from).
// index.js
import React from 'react';
import './App.css';
import Characters from './Characters'
const api = "https://rickandmortyapi.com/api/character/";
class IndexPage extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch(api)
.then(res => res.json())
.then(
json => {
console.log(json)
this.setState({
isLoaded: true,
data: json.results
});
},
// 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, data } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<Characters data={data} />
);
}
}
}
export default IndexPage;
// Characters.js
import React from 'react';
class Characters extends React.Component {
render() {
return (
<ul>
{this.props.data.map(item => (
<li key={item.id}>
<dl>
<dt>Name:</dt>
<dd>{item.name}</dd>
<dt>Species:</dt>
<dd>{item.species}</dd>
<dt>Status:</dt>
<dd>{item.status}</dd>
</dl>
</li>
))}
</ul>
);
}
}
export default Characters;
Codesandbox Example using functional components and hooks
Gatsby’s <Link> component allows you to link between pages (and does some other stuff like prefetching resources, and can share data between pages). As you are rendering the <ChartData /> component on the index page, this is not required to solve your problem.
Using <Link> with state works because details is a gatsby page. As <ChartData> is not a page, you can't *link* to it.

How can i push data into react-admin store?

I'm trying to build a web app with react-admin and need to push data to the redux store. I read the React-admin docs (https://marmelab.com/react-admin/Actions.html) and when I try to do that, I get Failures.
Here is my code
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withStyles, MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import {
Menu,
Notification,
Sidebar,
setSidebarVisibility,
} from 'react-admin';
import {kidsLoad} from './customActions/KidsActions'
import AppBar from './MyAppBar';
const styles = theme => ({
root: {
display: 'flex',
flexDirection: 'column',
zIndex: 1,
minHeight: '100vh',
backgroundColor: theme.palette.background.default,
position: 'relative',
},
appFrame: {
display: 'flex',
flexDirection: 'column',
overflowX: 'auto',
},
contentWithSidebar: {
display: 'flex',
flexGrow: 1,
},
content: {
display: 'flex',
flexDirection: 'column',
flexGrow: 2,
padding: theme.spacing.unit * 3,
marginTop: '1em',
paddingLeft: 5,
},
});
class MyLayout extends Component {
componentWillMount() {
this.props.setSidebarVisibility(true);
}
componentDidMount(){
const { kidsLoad, record } = this.props;
kidsLoad({data: "HELLOOOOOOOOOOOO!!!!!"})
}
render() {
const {
children,
classes,
dashboard,
isLoading,
logout,
open,
title,
} = this.props;
return (
<div className={classes.root}>
<div className={classes.appFrame}>
<AppBar title={title} open={open} logout={logout} />
<main className={classes.contentWithSidebar}>
<div className={classes.content}>
{children}
</div>
</main>
<Notification />
</div>
</div>
);
}
}
MyLayout.propTypes = {
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
dashboard: PropTypes.oneOfType([
PropTypes.func,
PropTypes.string,
]),
isLoading: PropTypes.bool.isRequired,
// logout: componentPropType,
setSidebarVisibility: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
kidsLoad: PropTypes.func,
};
const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 });
export default connect(mapStateToProps, { setSidebarVisibility, kidsLoad })(withStyles(styles)(MyLayout));
I did everything like in the documentation (https://marmelab.com/react-admin/Actions.html).
What did I do wrong?
How do you add data to the store in this framework?
This is going to be a quick answer that I leave for further improvement.
If I understand your question correctly you got some of your resources (entities) updated and you want react-admin to know about it and update its store accordingly triggering updates in app views if necessary.
The first thing we have to get is the dispatch function of the react-admin store. In my case, the source of resource update was a React component, so I used withDataProvider decorator to receive a reference to the dispatch function.
Once you have the dispatch function you dispatch, for example, CRUD_UPDATE_SUCCESS action for a particular resource update in the react-admin store.
import { CRUD_UPDATE_SUCCESS, FETCH_END, UPDATE } from 'react-admin';
dispatch({
type: CRUD_UPDATE_SUCCESS,
payload: { data },
meta: {
resource,
notification: {
body: 'ra.notification.dataSaved',
level: 'info'
},
fetchResponse: UPDATE,
fetchStatus: FETCH_END
}
});
You can also use action creators from react-admin. Like showNotification, for example.
import { showNotification } from 'react-admin';
dispatch(showNotification(errorMessage, 'warning', { autoHideDuration: 10000 }));
A bit more consistent piece of code here to show how all this can work together. The Updater component here renders its child components passing them a resource record and subscribes for their onSubmit callback to perform entity saving and updating react-admin store.
import React from 'react';
import {
CRUD_UPDATE_SUCCESS, FETCH_END, UPDATE, withDataProvider, showNotification
} from 'react-admin';
class Updater extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleUpdate(record) {
const { resource, dataProvider, dispatch } = this.props;
const payload = {
id: record.id,
};
payload.data = {...record};
return new Promise((resolve, reject) => {
dataProvider(UPDATE, resource, payload)
.then(({data}) => {
dispatch({
type: CRUD_UPDATE_SUCCESS,
payload: { data },
meta: {
resource,
notification: {
body: 'ra.notification.dataSaved',
level: 'info'
},
fetchResponse: UPDATE,
fetchStatus: FETCH_END
}
});
resolve(data);
})
.catch(e => {
const errorMessage = e.message || e;
this.setState({ errorMessage });
dispatch(
showNotification(errorMessage, 'warning', { autoHideDuration: 10000 })
);
reject(errorMessage);
});
});
}
render() {
const { record, children } = this.props;
const { errorMessage } = this.state;
return React.Children.only(React.cloneElement(children, {
record,
errorMessage,
onUpdate: this.handleUpdate
}));
}
}
export default withDataProvider(Updater);
HTH,
Ed

this.props.dispatch is not a function in react with redux

I am calling api with redux action, so i created the reducers, action and store. But when i calling the redux action from the view part. I am getting the following error.
this.props.dispatch is not a function
Below is my reducer, actions and view part(React) Code which can help you to understand the code.
Note: when i console.log(this.props), I am not getting dispatch() function.
actions.js
import {
FETCH_WORKS_SUCCESS,
FETCH_WORKS_FAILURE
} from './actionTypes'
export function fetchFeaturedWork(){
return dispatch => {
return fetch("https://api.com/api/wp-json/customapi/all_posts?category=work&featured=true")
.then(res => res.json())
.then(json => {
dispatch(fetchFeaturedWorkSuccess(json.works));
return json.works;
})
};
}
export const fetchFeaturedWorkSuccess = works => ({
type: FETCH_WORKS_SUCCESS,
payload: { works }
});
reducer.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
FETCH_WORKS_SUCCESS,
FETCH_WORKS_FAILURE
} from '../actions/actionTypes'
const initialState = {
isMenuOpen: null,
isMobile: false,
error:null,
works:[]
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case OPEN_MENU:
return {...state, isMenuOpen: true}
case CLOSE_MENU:
return {...state, isMenuOpen: false}
case SET_DEVICE_TYPE:
return {...state, isMobile: action.isMobile}
case FETCH_WORKS_SUCCESS:
return {...state,works:action.payload.works}
case FETCH_WORKS_FAILURE:
return {...state,error:action.payload.error,works:[]}
default:
return state
}
}
export default reducer;
store.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers/reducer';
import thunk from "redux-thunk";
let store = createStore(rootReducer,applyMiddleware(thunk))
export default store
App.js
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import Slider from "react-slick";
import Footer from '../footer'
import './index.css';
import { connect } from 'react-redux'
import { openMenu,fetchFeaturedWork } from '../../actions/actionCreators'
import Header from '../header'
import arrowRight from '../../images/arrow-right.svg'
import arrowLeft from '../../images/arrow-left.svg'
const mapStateToProps = (state) => ({
isMobile: state && state.isMobile
})
const mapDispatchToProps = (dispatch, ownProps) => ({
openMenu: (isMobile) => dispatch(openMenu(ownProps.isMobile))
})
class Work extends Component {
getImgSlider = (slider) => {
this.imgSlider = slider
}
getTextSlider = (slider) => {
this.textSlider = slider
}
componentDidMount(){
this.props.dispatch(fetchFeaturedWork());
}
render() {
let className = 'idz_work'
if (this.props.isMobile) {
className = 'idle'
}
return (
<div>
{
this.props.isMobile ?
null
:
<Link to='/home'>
<div className='idz_sidebar' style={{ left: 0 }}>
<div>
<label>FEATURED</label>
</div>
</div>
</Link>
}
<div className={className}>
{!this.props.isMobile &&
<Header logo="dark"/>
}
<div className="content">
<label className="light-gray" style={this.props.isMobile ? { fontSize: '1.3rem' } : { fontSize: '2rem' }}>Featured Works</label>
<hr style={this.props.isMobile ? { marginBottom: '0.5rem', marginTop: '0.5rem' } : {}} />
<hr style={this.props.isMobile ? { marginBottom: '0.5rem', marginTop: '0.5rem' } : {}} />
<label className="light-gray" style={this.props.isMobile ? { fontSize: '1.3rem' } : { fontSize: '2rem' }}>All Works</label>
<hr style={this.props.isMobile ? { marginBottom: '0.5rem', marginTop: '0.5rem' } : {}} />
<hr />
</div>
{this.props.isMobile ? null : <Footer />}
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Work);
When you use mapDispatchToProps, dispatch is not longer available in props. So, either get rid of mapDispatchToProps and use this.props.dispatch. Or move all dispatch functions to mapDispatchToProps.

React Native: Component rerender but props has not changed

I'm encountering this strange issue that I can figure out why is happing.
This should not be happening since the prop passed down to the History component has not been updated.
./components/History.js
...
const History = ({ previousLevels }) => {
return (
<ScrollView style={styles.container}>
{previousLevels.reverse().map(({ date, stressValue, tirednessValue }) => {
return (
<CardKBT
key={date}
date={date}
stressValue={stressValue}
tirednessValue={tirednessValue}
/>
)
})}
</ScrollView>
)
}
...
export default History
As can be seen in this code (below), the prop to the History is only updated once the user press Save.
App.js
import React from 'react'
import { View, ScrollView, StyleSheet } from 'react-native'
import { AppLoading, Font } from 'expo'
import Store from 'react-native-simple-store'
import { debounce } from 'lodash'
import CurrentLevels from './components/CurrentLevels'
import History from './components/History'
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoadingComplete: false,
currentLevels: {
stressValue: 1,
tirednessValue: 1,
},
previousLevels: [],
}
this.debounceUpdateStressValue = debounce(this.onChangeStressValue, 50)
this.debounceUpdateTirednessValue = debounce(
this.onChangeTirednessValue,
50
)
}
async componentDidMount() {
const previousLevels = await Store.get('previousLevels')
if (previousLevels) {
this.setState({ previousLevels })
}
}
render() {
const { stressValue, tirednessValue } = this.state.currentLevels
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading
...
/>
)
} else {
return (
<View style={{ flex: 1 }}>
<CurrentLevels
stressValue={stressValue}
onChangeStressValue={this.debounceUpdateStressValue}
tirednessValue={tirednessValue}
onChangeTirednessValue={this.debounceUpdateTirednessValue}
onSave={this.onSave}
/>
<History previousLevels={this.state.previousLevels} />
</View>
)
}
}
...
onChangeStressValue = stressValue => {
const { tirednessValue } = this.state.currentLevels
this.setState({ currentLevels: { stressValue, tirednessValue } })
}
onChangeTirednessValue = tirednessValue => {
const { stressValue } = this.state.currentLevels
this.setState({ currentLevels: { stressValue, tirednessValue } })
}
onSave = () => {
Store.push('previousLevels', {
date: `${new Date()}`,
...this.state.currentLevels,
}).then(() => {
Store.get('previousLevels').then(previousLevels => {
this.setState({
currentLevels: { stressValue: 1, tirednessValue: 1 },
previousLevels,
})
})
})
}
}
The component will re-render when one of the props or state changes, try using PureComponent or implement shouldComponentUpdate() and handle decide when to re-render.
Keep in mind, PureComponent does shallow object comparison, which means, if your props have nested object structure. It won't work as expected. So your component will re-render if the nested property changes.
In that case, you can have a normal Component and implement the shouldComponentUpdate() where you can tell React to re-render based on comparing the nested properties changes.

MUI - How to animate Card depth on hover?

I want to animate the depth of the whole Card when the mouse is over it.
I try this (so-so I'm new in React) but I have no idea how to do it:
<Card
linkButton={true}
href="/servicios/"
onClick={Link.handleClick} zDepth={3}
onMouseEnter={this.setState({zDepth={1}})}>
</Card>
Thanks in advance.
5 years later and there is still no correct answer, you do not have to set the component state when it hovers, just use the pseudo-class :hover:
<Card
sx={{
':hover': {
boxShadow: 20, // theme.shadows[20]
},
}}
>
If you want to use styled():
const options = {
shouldForwardProp: (prop) => prop !== 'hoverShadow',
};
const StyledCard = styled(
Card,
options,
)(({ theme, hoverShadow = 1 }) => ({
':hover': {
boxShadow: theme.shadows[hoverShadow],
},
}));
<StyledCard hoverShadow={10}>
<Content />
</StyledCard>
Live Demo
constructor(props) {
super(props);
this.state = { shadow: 1 }
}
onMouseOver = () => this.setState({ shadow: 3 });
onMouseOut = () => this.setState({ shadow: 1 });
<Card
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
zDepth={this.state.shadow}
>
Updated #1
Full example
// StyledCard.js
import React, { Component } from 'react';
import { Card } from 'material-ui/Card';
class StyledCard extends Component {
state: {
shadow: 1
}
onMouseOver = () => this.setState({ shadow: 3 });
onMouseOut = () => this.setState({ shadow: 1 });
render() {
return (
<Card
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
zDepth={this.state.shadow}
>
{this.props.children}
</Card>
);
}
export default StyledCard;
.
// Container.js
import React from 'react';
import StyledCard from './StyledCard';
const Container = () => [
<StyledCard>Card 1</StyledCard>,
<StyledCard>Card 2</StyledCard>,
<StyledCard>Card 3</StyledCard>,
];
export default Container;
UPDATED #2
With HOC
// withShadow.js
import React from 'react';
const withShadow = (Component, { init = 1, hovered = 3 }) => {
return class extends React.Component {
state: {
shadow: init
};
onMouseOver = () => this.setState({ shadow: hovered });
onMouseOut = () => this.setState({ shadow: init });
render() {
return (
<Component
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
zDepth={this.state.shadow}
{...this.props}
/>
);
}
};
};
export default withShadow;
.
// Container.js
import React from 'react';
import { Card } from 'material-ui/Card';
import withShadow from './withShadow';
const CardWithShadow = withShadow(Card, { init: 2, hovered: 4 });
const Container = () => [
<CardWithShadow>Card 1</CardWithShadow>,
<CardWithShadow>Card 2</CardWithShadow>,
<CardWithShadow>Card 3</CardWithShadow>,
];
export default Container;
#Alex Sandiiarov answer didnt work for me. The docs show to use the raised property.
https://material-ui.com/api/card/
class Component extends React.Component{
state = {
raised:false
}
toggleRaised = () => this.setState({raised:!this.state.raised});
render(){
return <Card onMouseOver={this.toggleRaised}
onMouseOut={this.toggleRaised}
raised={this.state.raised}>
...
</Card>
}
}

Categories

Resources