I'm currently learning React/hooks/redux. To do so, I'm building a react app that takes in data from a climate API.
The problem I'm having is correctly setting state for a couple of items in useEffect. One state relies on the other, so I'm trying to figure out how to properly call useEffect so I don't get infinite loops and follow best-practices.
A little background before the code included below:
-The user creates a project, and selects a city. This produces a cityId that I'm storing in my "project" state.
-On the user's dashboard, they can click a project that sends the project ID in a queryString to my ClimateData component.
-ClimateData passes the project ID queryString to the "getProjectByID" redux action to get the project state, including it's cityId.
-ClimateData includes the IndicatorList component, which brings in a list of all the climate data breakouts. I want the user to click one of these list items and have ClimateData's "indicatorByCityData" state set. So I passed ClimateData's setState function to IndicatorList and have the list call with onClicks. Is there a better way I should do this?
-On ClimateData, once I have the project's cityId, and the selected item from IndicatorList, I need to call "getIndicatorByCity" and pass both the cityId and indicator to have the result saved in the "indicatorByCityData" state
I keep trying to change how my ClimateData's useEffect is written, but I'm either getting infinite loops or errors. How can I best change this to set both states and follow best practices?
The redux actions and reducers have been tested elsewhere and work fine, so for brevity, I'll exclude them here and just focus on my ClimateData and IndicatorList components:
import React, { Fragment, useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import IndicatorList from './IndicatorList';
import Spinner from '../layout/Spinner';
import { getProjectById } from '../../actions/projects';
import { getIndicatorByCity } from '../../actions/climate';
const ClimateData = ({
getProjectById,
getIndicatorByCity,
project: { project, loading },
auth,
match
}) => {
const [indicatorByCityData, setIndicatorByCityData] = useState({});
const nullProject = !project;
useEffect(() => {
if (!project) getProjectById(match.params.id);
// Once we have the cityID, set the indicatorByCityData state, with a default selected Indicator
if (!loading) setIndicatorByCityData(getIndicatorByCity(project.cityId));
}, []);
// Get the selected indicator from IndicatorList and update the indicatorByCityData state
const setIndicator = indicator => {
setIndicatorByCityData(getIndicatorByCity(project.cityId, null, indicator));
};
return (
<Fragment>
{project === null || loading || !indicatorByCityData ? (
<Spinner />
) : (
<Fragment>
<Link to='/dashboard' className='btn btn-light'>
Back To Dashboard
</Link>
<h1 className='large text-primary'>{`Climate Data for ${project.city}`}</h1>
<IndicatorList setIndicator={setIndicator} />
</Fragment>
)}
</Fragment>
);
};
ClimateData.propTypes = {
getProjectById: PropTypes.func.isRequired,
getIndicatorByCity: PropTypes.func.isRequired,
project: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
project: state.projects,
auth: state.auth
});
export default connect(mapStateToProps, { getProjectById, getIndicatorByCity })(
ClimateData
);
/******************************************************************/
import React, { useEffect, Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Spinner from '../layout/Spinner';
import { getIndicatorList } from '../../actions/climate';
const IndicatorList = ({
getIndicatorList,
auth: { user },
climateList: { indicatorList, loading },
setIndicator
}) => {
useEffect(() => {
getIndicatorList();
}, [getIndicatorList]);
return loading ? (
<Spinner />
) : (
<Fragment>
{indicatorList.length > 0 ? (
<Fragment>
<ul>
{indicatorList.map(indicator => (
<li key={indicator.name}>
<a href='#!' onClick={() => setIndicator(indicator.name)}>
{indicator.label}
</a>
<br />- {indicator.description}
</li>
))}
</ul>
</Fragment>
) : (
<h4>No climate indicators loaded</h4>
)}
</Fragment>
);
};
IndicatorList.propTypes = {
auth: PropTypes.object.isRequired,
climateList: PropTypes.object.isRequired,
setIndicator: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
climateList: state.climate
});
export default connect(mapStateToProps, { getIndicatorList })(IndicatorList);
Related
The version of react-router-dom is v6 and I'm having trouble with passing values to another component using Navigate.
I want to pass selected rows to another page called Report. But, I'm not sure I'm using the right syntax for navigate method and I don't know how to get that state in the Report component.
Material-ui Table: I'm trying to use redirectToReport(rowData) in onClick parameter.
function TableRows(props){
return (
<MaterialTable
title="Leads"
columns={[
...
]}
data = {props.leads}
options={{
selection: true,
filtering: true,
sorting: true
}}
actions = {[{
position: "toolbarOnSelect",
tooltip: 'Generate a report based on selected leads.',
icon: 'addchart',
onClick: (event, rowData) => {
console.log("Row Data: " , rowData)
props.redirect(rowData)
}
}]}
/>
)}
LeadTable component
export default function LeadTable(props) {
let navigate = useNavigate();
const [leads, setLeads] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl(url) {
const response = await fetch(url);
const json = await response.json();
setLeads(json[0]);
setLoading(false);
}
useEffect(() => {
fetchUrl("http://localhost:5000/api/leads");
}, []);
function redirectToReport(rowData) {
navigate('/app/report', { state: rowData }); // ??? I'm not sure if this is the right way
}
return(
<div>
<TableRows leads={leads} redirect={redirectToReport}></TableRows>
</div>
)}
Report component
export default function ReportPage(state) {
return (
<div>
{ console.log(state) // This doesn't show anything. How to use the state that were passed from Table component here?}
<div className = "Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);}
version 6 react-router-dom
I know the question got answered but I feel this might be helpful example for those who want to use functional components and they are in search of passing data between components using react-router-dom v6.
Let's suppose we have two functional components, first component A, second component B. The component A wants to share data to component B.
usage of hooks: (useLocation,useNavigate)
import {Link, useNavigate} from 'react-router-dom';
function ComponentA(props) {
const navigate = useNavigate();
const toComponentB=()=>{
navigate('/componentB',{state:{id:1,name:'sabaoon'}});
}
return (
<>
<div> <a onClick={()=>{toComponentB()}}>Component B<a/></div>
</>
);
}
export default ComponentA;
Now we will get the data in Component B.
import {useLocation} from 'react-router-dom';
function ComponentB() {
const location = useLocation();
return (
<>
<div>{location.state.name}</div>
</>
)
}
export default ComponentB;
Note: you can use HOC if you are using class components as hooks won't work in class components.
Your navigate('/app/report', { state: rowData }); looks correct to me.
react-router-v6
If you need state, use navigate('success', { state }).
navigate
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: any }
): void;
(delta: number): void;
}
Your ReportPage needs to be rendered under the same Router that the component doing the push is under.
Route props are no longer passed to rendered components, as they are now passed as JSX literals. To access route state it must be done so via the useLocation hook.
function ReportPage(props) {
const { state } = useLocation();
console.log(state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
If the component isn't able to use React hooks then you still access the route state via a custom withRouter Higher Order Component. Here's an example simple withRouter HOC to pass the location as a prop.
import { useLocation, /* other hooks */ } from 'react-router-dom';
const withRouter = WrappedComponent => props => {
const location = useLocation();
// other hooks
return (
<WrappedComponent
{...props}
{...{ location, /* other hooks */ }}
/>
);
};
Then access via props as was done in pre-RRDv6.
class ReportPage extends Component {
...
render() {
console.log(this.props.location.state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
}
2 things (just a suggestion):
Rather than a ternary use &&
{location && <div>{location.state.name}</div>}
Why are you checking location and rendering location.state.name? I would use the check on the data you are fetching or make sure the data returns null or your value.
On Sabaoon Bedar's Answer, you can check if there is any data or not before showing it :
Instead of this <div>{location.state.name}</div>
Do this { location != null ? <div>{location.state.name}</div> : ""}
if you want to send data with usenavigate in functional component you can use like that
navigate(`/take-quiz/${id}`, { state: { quiz } });
and you can get it with uselocation hook like this
const location = useLocation();
location.state.quiz there is your data
But you cannot get this data in props it;s tricky part ;)!!
on SABAOON BEDAR answer,
from component A: navigate('/', {state:"whatever"}
in component B: console.log(location.state) //output = whatever
This question already has answers here:
How do I access store state in React Redux?
(8 answers)
Closed 1 year ago.
I've created the reducer and using it to change the state of my store. but as you can see in App.js whenever I click on button and update the state. it updates. I can see it in console. but component does not update. as you can see I have list of tracks there it is not updating. and if I make any changes to code because of that the component re-render I can see the new state after that. why is it not rendering automatically whenever the state updates.
Action
import * as actions from './actionTypes'
export const trackAdded = (title, artist, audioSrc, img) => {
return {
type: actions.TRACK_ADDED,
payload: {
title,
artist,
audioSrc,
img
}
}
}
Reducer
import * as actions from './actionTypes'
export default function reducer(state = [], action) {
switch (action.type) {
case actions.TRACK_ADDED:
return [
...state,
{
title: action.payload.title,
artist: action.payload.artist,
audioSrc: action.payload.audioSrc,
img: action.payload.img
}
]
default:
return state
}
}
App.js
import './App.css';
import store from './store'
import { trackAdded } from './actions'
function App() {
const add = (title) => {
store.dispatch(trackAdded(title, "Taylor Swift", "src", "image"))
console.log(store.getState())
}
return (
<div className="App">
{store.getState().map((track, track_id) => {
return (
<li key={track_id}>{track.title}</li>
)
})}
<button onClick={() => { add("Shake It Off") }}>Add Track</button>
</div>
);
}
export default App;
The component will not update because the store.getState() inside of <div className="App"> will only run once when the component is called. There is no logic that exists that tells the component to rerun the store.getState(). If you want the component to receive updates when the store's state changes, you need to connect it to the store using react-redux's connect function or a useSelector hook.
As an example, if using the connect function, you can map the redux state to a react component's props. So if the component's props change, then the component will "react" to it's props changing. The mapping of redux's state to the components props happens in the mapStateToProps function, returning a prop tracks that is mapped to the component. Otherwise there is no reason for the component to update. Also note: in the example below, the store is connected to React through a Provider component, providing the store to child components that wish to connect to it.
import { Provider, connect } from 'react-redux'
import './App.css';
import store from './store'
import { trackAdded } from './actions'
function App(props) {
const add = (title) => {
store.dispatch(trackAdded(title, "Taylor Swift", "src", "image"))
console.log(store.getState())
}
return (
<Provider store={store}>
<div className="App">
{props.tracks.map((track, track_id) => {
return (
<li key={track_id}>{track.title}</li>
)
})}
<button onClick={() => { add("Shake It Off") }}>Add Track</button>
</div>
</Provider>
);
}
const mapStateToProps = (state) => {
const tracks = state
return { tracks }
}
export default connect(mapStateToProps)(App);
Having said that, if you go the connect route, you might want to add a mapDispatchToProps function to the connect so you dont pass around the store everywhere. You can find that info in the redux docs, but that would be an answer to a different question.
This is happening because your component does not know that store has been updated, you can use something like this
useEffect(() => {
this.artistList = store.getState();
}, [store.getState()]); // kind of watcher of store.getState()
but this is definitely not recommended. You would want to use useSelector() from react-redux, which is much concise and recommended way to doing it.
I'm not understanding some ReactJs behavior and would need some help.
I have a Root Functional Component ("Index"), that contains another functional Component ("Preview").
That Preview component contains several other Functional Components ("InlineField").
The app is a simple form, where InlineField is component that renders an input and also contains a state to know if the field is "opened" or "closed" (when close it is displayed as a text, when open it is displayed as an input).
The global state is defined using hooks ad the "Index" level and moved down to the field through props (I've tried the same using Context). This state contains all form values.
The InlineField Component uses hook to maintain its local state only (is open / is closed).
When a an input is changed it updates the state (Index level) which triggers a re-render of the Index as well as its children.
This translate into the currently edited field (InlineField Component with local state = open) to refresh and lose its state value.
My question:
How can I make sure these InlineField Components retain their state even after updating global state?
I could simply move that InlineField Component state to the global state too, but I don't think it makes much sense.
I must be getting something wrong...
Thanks!
Edit: added code sample
Index Component:
import React, { useState, useEffect } from "react"
import Layout from "../components/layout"
const IndexPage = () => {
const [formValues, setFormValues] = useState({
name: 'Myname',
email: 'myemail#mail.com',
})
const onFormValueChange = (key, value) => {
setFormValues({...formValues, [key]: value})
}
return (
<Layout>
<Preview
key="previewyaknow"
formValues={formValues}
onFieldChange={setFormValues}
/>
</Layout>
)
}
export default IndexPage
Preview Component:
import React from 'react'
import { Box, TextField } from "#material-ui/core"
import { InlineField } from './inlineField'
export const Preview = ({formValues, onFieldChange}) => {
return (
<>
<Box display="flex" alignItems="center">
<InlineField
value={formValues.email}
onChange={onFormValueChange}
id="email"
field={<TextField value={formValues.email}/>>>}
/>
</>
)
}
InlineEdit Component
import React, { useState, useEffect } from "react"
export const InlineField = ({onChange, value, id, field}) => {
const [isEdit, setIsEdit] = useState(false)
const onBlur = (e) => {
setIsEdit(false)
}
let view = (<div>{value}</div>);
if (isEdit) {
view = (
<FieldContainer className={classes.fieldContainer}>
{React.cloneElement(field, {
'onBlur': onBlur,
'autoFocus': true,
'onChange': (e) => {
onChange(id, e.target.value)
}
})
}
</FieldContainer>
)
}
return (
<div onClick={()=>setIsEdit(!isEdit)}>
{view}
</div>
)
}
After a post I would like to reload my table to be able to display the data after the post. Now the question arises how to get my "DataProvider" to render again?
I would do this as a function call in "FormOPCConnect". But I don't know how to start. I already tried to use the "props" of the "DataProvider", but I can't figure out how to render the new table.
Enclosed my source code.
TableOPCConnections.js
import React from "react";
import PropTypes from "prop-types";
import key from "weak-key";
import Table from 'react-bootstrap/Table'
const OPCVarTable = ({ data }) =>
!data.length ? (
<p>Nothing to show</p>
) : (
<div>
<h2 className="subtitle">
Showing <strong>{data.length}</strong> OPC Variables
</h2>
<Table striped bordered hover>
<thead>
<tr>
{Object.entries(data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
</tr>
</thead>
<tbody>
{data.map(el => (
<tr key={el.id}>
{Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
</tr>
))}
</tbody>
</Table>
</div>
);
OPCVarTable.propTypes = {
data: PropTypes.array.isRequired
};
export default OPCVarTable;
DataProvider.js
import React, { Component } from "react";
import PropTypes from "prop-types";
class DataProvider extends Component {
static propTypes = {
endpoint: PropTypes.string.isRequired,
render: PropTypes.func.isRequired
};
state = {
data: [],
loaded: false,
placeholder: "Loading..."
};
componentDidMount() {
fetch(this.props.endpoint)
.then(response => {
if (response.status !== 200) {
return this.setState({ placeholder: "Something went wrong" });
}
return response.json();
})
.then(data => this.setState({ data: data, loaded: true }));
}
render() {
const { data, loaded, placeholder } = this.state;
return loaded ? this.props.render(data) : <p>{placeholder}</p>;
}
}
export default DataProvider;
FormOPCConnect.js
(Here I'd like to refresh the state of the DataProvider)
After the fetch method I would like to render the table again as long as the post to the database was successful.
import React, { Component, useState } from "react";
import PropTypes from "prop-types";
import Button from "react-bootstrap/Button";
import Form from 'react-bootstrap/Form'
import DataProvider from "./DataProvider";
import csrftoken from './csrftoken';
class FormOPCConnect extends Component {
constructor(props) {
super(props);
this.state = {
validated: false
};
}
static propTypes = {
endpoint: PropTypes.string.isRequired
};
state = {
ip_address: "",
port: "",
namespace_name: "",
root_name: "",
owner: ""
};
handleChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
handleSubmit = event => {
const form = event.currentTarget;
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
event.preventDefault();
const { ip_address, port, namespace_name, root_name, owner } = this.state;
const opcConn= { ip_address, port, namespace_name, root_name, owner };
const conf = {
method: "post",
body: JSON.stringify(opcConn),
headers: new Headers({ "Content-Type": "application/json", "X-CSRFTOKEN": csrftoken })
}
fetch(this.props.endpoint, conf).then(response => console.log(response));
//>>
//if response is valid -> refresh the Dataprovider and the table...
//<<
this.setState({ validated: this.state.validated = true })
};
App.js
const App = () => (
<React.Fragment>
<Container>
<Row>
<Col> <NavBarTop fixed="top" /> </Col>
</Row>
<Row>
<Col> <DataProvider endpoint="opcconnection/"
render={data => <OPCVarTable data={data} />} /></Col>
<Col><FormOPCConnect endpoint="opcconnection/" /></Col>
</Row>
</Container>
</React.Fragment>
);
const wrapper = document.getElementById("app");
wrapper ? ReactDOM.render(<App />, wrapper) : null;
I'm new to React, so please forgive my mistakes. :D
Finally it looks like this.
OPCConnection_Image
Your code currently contains 2 problems that need to be fixed in order to update your table when you post data.
1)
Your DataProvider does actually rerender on props change. The problem here is that your logic to fetch your data is in componentDidMount. componentDidMount only triggers the first time the component mounts and doesn't trigger on rerender.
If you want your data to fetch everytime the component rerenders you could place your fetch functionality in your render method of DataProvider.
To rerender a component all you have to do is update its props or it's state.
2) You want your DataProvider to update when your FormOPCConnect has finished some logic.
The thing with React is. You can only pass variables from parents to children. You can't directly communicate from sibling to sibling or from child to parent.
In your App your DataProvider is a siblign of FormOPCConnect, they are next to eachother.
<App> // App can communicate with every component inside it.
<DataProvider /> // This component can't communicate with the component next to it.
<FormOPCConnect />
</App>
The easiest thing to do here would be to either render DataProvider inside FormOPCConnect and update DataProvider's props directly.
Or if that is not possible, keep a state in App which checks if your logic in FormOPCConnect has finished.
class App extends Component {
constructor(props) {
super(props);
this.state = { boolean: false }; //state is remembered when a component updates.
}
flipBoolean() { //this function updates the state and rerenders App when called.
this.setState(
boolean: boolean!
);
};
render {
return (
<Fragment>
<DataProvider />
<FormOPCConnect flipBoolean={this.flipBoolean} />
</Fragment>
)
}
}
Pass a function to FormOPCConnect which updates the state of App. When you want your DataProvider to rerender you simply call that flipBoolean function in FormOPCConnect. This will update the state in App. Which will trigger App to rerender. Which will in it's turn rerender it's children DataProvider andFormOPCConnect`.
(This variable doesn't need to be a boolean, you can do here whatever you want. This boolean is just an example).
I'm having an issue where react-loadable is causing one of my input components to re-render and lose focus after a state update. I've done some digging and I can't find anyone else having this issue, so I think that I'm missing something here.
I am attempting to use react-loadable to dynamically include components into my app based on a theme that the user has selected. This is working fine.
./components/App
import React from 'react';
import Loadable from 'react-loadable';
/**
* Import Containers
*/
import AdminBar from '../../containers/AdminBar';
import AdminPanel from '../../components/AdminPanel';
import 'bootstrap/dist/css/bootstrap.css';
import './styles.css';
const App = ({ isAdmin, inEditMode, theme }) => {
const MainContent = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/MainContent'),
loading: () => (<div>Loading...</div>)
});
const Header = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/Header'),
loading: () => (<div>Loading...</div>)
});
return (
<div>
{
(isAdmin) ? <AdminBar
className='admin-bar'
inEditMode={inEditMode} /> : ''
}
<Header
themeSettings={theme.settings.Header} />
<div className='container-fluid'>
<div className='row'>
{
(isAdmin && inEditMode) ? <AdminPanel
className='admin-panel'
theme={theme} /> : ''
}
<MainContent
inEditMode={inEditMode} />
</div>
</div>
</div>
);
};
export default App;
./components/AdminPanel
import React from 'react';
import Loadable from 'react-loadable';
import './styles.css';
const AdminPanel = ({ theme }) => {
const ThemedSideBar = Loadable({
loader: () => import('../../themes/' + theme.name + '/components/SideBar'),
loading: () => null
});
return (
<div className='col-sm-3 col-md-2 sidebar'>
<ThemedSideBar
settings={theme.settings} />
</div>
);
};
export default AdminPanel;
This is what my <ThemedSideBar /> components looks like:
./themes/Default/components/SideBar
import React from 'react';
import ThemeSettingPanel from '../../../../components/ThemeSettingPanel';
import ThemeSetting from '../../../../containers/ThemeSetting';
import './styles.css';
const SideBar = ({ settings }) => {
return (
<ThemeSettingPanel
name='Header'>
<ThemeSetting
name='Background Color'
setting={settings.Header}
type='text'
parent='Header' />
<ThemeSetting
name='Height'
setting={settings.Header}
type='text'
parent='Header' />
</ThemeSettingPanel>
);
};
export default SideBar;
./components/ThemeSettingPanel
import React from 'react';
import { PanelGroup, Panel } from 'react-bootstrap';
const ThemeSettingPanel = ({ name, children }) => {
return (
<PanelGroup accordion id='sidebar-accordion-panelGroup'>
<Panel>
<Panel.Heading>
<Panel.Title toggle>{name}</Panel.Title>
</Panel.Heading>
<Panel.Body collapsible>
{children}
</Panel.Body>
</Panel>
</PanelGroup>
);
};
export default ThemeSettingPanel;
./containers/ThemeSetting
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { themeSettingChange } from '../App/actions';
import ThemeSetting from '../../components/ThemeSetting';
class ThemeSettingContainer extends Component {
constructor(props) {
super(props);
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(name, parent, value) {
const payload = {
name: name,
parent,
value: value
};
this.props.themeSettingChange(payload);
}
render() {
return (
<ThemeSetting
name={this.props.name}
setting={this.props.setting}
parent={this.props.parent}
type={this.props.type}
handleOnChange={this.handleOnChange} />
);
}
}
//----Redux Mappings----//
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = {
themeSettingChange: (value) => themeSettingChange(value)
};
export default connect(mapStateToProps, mapDispatchToProps)(ThemeSettingContainer);
./component/ThemeSetting
import React from 'react';
import TextField from '../common/TextField';
import './styles.css';
const ThemeSetting = ({ name, setting, type, parent, handleOnChange }) => {
return (
<div className='row theme-setting'>
<div className='col-xs-7'>
{name}
</div>
<div className='col-xs-5'>
{
generateField(type, setting, name, parent, handleOnChange)
}
</div>
</div>
);
};
function generateField(type, setting, name, parent, handleOnChange) {
const value = setting ? setting[name] : '';
switch (type) {
case 'text':
return <TextField
value={value}
name={name}
parent={parent}
handleOnChange={handleOnChange} />;
default:
break;
}
}
export default ThemeSetting;
./components/common/TextField
import React from 'react';
import { FormControl } from 'react-bootstrap';
const TextField = ({ value, name, parent, handleOnChange }) => {
return (
<FormControl
type='text'
value={value}
onChange={(e) => {
handleOnChange(name, parent, e.target.value);
}} />
);
};
export default TextField;
When a field inside of my Admin Panel is updated, a state change is triggered. It seems like this triggers react-loadable to re-render my <ThemedSideBar /> components which destroys my input and creates a new one with the updated value. Has anyone else had this issue? Is there a way to stop react-loadable from re-rendering?
EDIT: Here is the requested link to the repo.
EDIT: As per conversation in the comments, my apologies, I misread the question. Answer here is updated (original answer below updated answer)
Updated answer
From looking at the react-loadable docs, it appears that the Loadable HOC is intended to be called outside of a render method. In your case, you are loading ThemedSideBar in the render method of AdminPanel. I suspect that the change in your TextEdit's input, passed to update your Redux state, and then passed back through the chain of components was causing React to consider re-rendering AdminPanel. Because your call to Loadable was inside the render method (i.e. AdminPanel is a presentational component), react-loadable was presenting a brand new loaded component every time React hit that code path. Thus, React thinks it needs to destroy the prior component to appropriately bring the components up to date with the new props.
This works:
import React from 'react';
import Loadable from 'react-loadable';
import './styles.css';
const ThemedSideBar = Loadable({
loader: () => import('../../themes/Default/components/SideBar'),
loading: () => null
});
const AdminPanel = ({ theme }) => {
return (
<div className='col-sm-3 col-md-2 sidebar'>
<ThemedSideBar
settings={theme.settings} />
</div>
);
};
export default AdminPanel;
Original answer
It seems that your problem is likely related to the way you've built TextField and not react-loadable.
The FormControl is taking value={value} and the onChange handler as props. This means you've indicated it is a controlled (as opposed to uncontrolled) component.
If you want the field to take on an updated value when the user types input, you need to propagate the change caught by your onChange handler and make sure it gets fed back to the value in the value={value} prop.
Right now, it looks like value will always be equal to theme.settings.Height or the like (which is presumably null/empty).
An alternative would be to make that FormControl an uncontrolled component, but I'm guessing you don't want to do that.