I have a react component that wraps a class that renders WebGL using three.js with the DOM and connects mobx store value and it changes with the class lifecycle methods.
The passed in mobx store is only used outside of the components render function in lifecycle functions (componentDidMount, componentDidUpdate, ..). Noticed that when the store changes, the component doesn't trigger a rerender. But I make a useless read within the render functions, such as in the example below passing a triggerRerenderListenerProp={this.props.store.debugSettings.showStats} prop to the div, the component becomes active only to store.debugSettings.showStats changes.
Is there a way of making the component listen to store changes wihtout using the store itself in the render function?
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {observer} from 'mobx-react';
import MapPreview from 'classes/MapPreview';
import style from './Preview.css';
class Preview extends Component {
static propTypes = {
store: PropTypes.object.isRequired,
imageUrl: PropTypes.string.isRequired
};
constructor (props) {
super(props);
this.containerEl = null;
}
componentDidMount () {
const options = {
debugSettings: this.props.store.debugSettings,
previewSettings: this.props.store.previewSettings
};
this.preview = new MapPreview(this.containerEl, options);
this.preview.setImage(imageUrl);
}
componentDidUpdate () {
this.preview.updateOptions({
debugSettings: this.props.store.debugSettings,
previewSettings: this.props.store.previewSettings
});
}
render () {
return (
<div
className={style.normal}
ref={(el) => { this.containerEl = el; }}
triggerRerenderListenerProp={this.props.store.debugSettings.showStats}
/>
);
}
}
export default observer(Preview);
The problem ultimately has two issues:
One, React is designed to only re-render when state or prop data changes.
Two, with mobx-react, I'm pretty sure the whole point is that the component won't re-render unless you dereference an observable value.
So while your props are technically changing, React doesn't do a deep object comparison of the props.
What you might try is setting options as internal component state -- that might force a re-render even though nothing in the render method would have changed.
The caveat here is that the updated props (from your store) might be too deeply nested as to force React to re-render even while updating internal state. You might also need to piggy-back on shouldComponentUpdate();
Related
The following React component is given:
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { store, StoreState } from "../../redux/actions";
import { setBackgroundAction } from "../../redux/title.actions";
import "./Loader.scss";
interface ReduxProps {
bgClass: string;
}
interface Props extends ReduxProps {
bgChange?: boolean;
}
export default function Loader(props: Props) {
const [bgClassOld, setBgClassOld] = useState<string>("");
const dispatch = useDispatch();
useEffect(() => {
const { bgChange, bgClass } = props;
if (bgChange) {
setBgClassOld(bgClass);
dispatch(setBackgroundAction("bg-white"));
dispatch(setBackgroundAction(bgClassOld));
}
});
return (
<div className="d-flex">
<div className="loader">
<img src="/loadscreen.gif" />
</div>
</div>
);
}
// function mapping(state: StoreState): ReduxProps {
// return {
// bgClass: state.title.backgroundClass,
// };
// }
This is more a theoretical question to see how to actually do the following change:
The component Loader will be imported from another npm package (shared components).
My problem is that I have a redux state in the current implementation included (changed it from Class to Functional component, so thats mapping() is still in there).
As I only import the component in my "main" client, I will not have the whole redux setup in place. So I think I need to pass the store and the dispatch functions via props.
So should I create a prop store for my component, where I pass the redux store when I import the shared component?
Do I also create two props for each dispatch functions?
Does is make sense or would there be a better approach?
You generally shouldn't import the Redux store directly into components. The hooks allow your component to access whatever Redux store has been injected into the component tree by a <Provider>.
You also don't need to pass dispatch as a prop. Any component can call useDispatch(), and dispatch actions to whatever Redux store is actually being used.
If I understand your question, you're planning on importing this component into an existing app, and it sounds like that app is already configured to use (React-)Redux with a <Provider> at the top. If that's the case, then you don't have to do anything else special to make this work. Just call the React-Redux hooks in any of your components.
Refactoring a React class component previously using Flux to use React Hooks instead.
Previous component utilized getStores() and calculateState() in order to subscribe to store objects and update local state.
Is there a way to refactor the class component to a functional React component using React Hooks (useState, useEffect, etc?) to achieve the same subscription to store objects?
flux component example:
class exampleComponent extends React.Component {
static getStores() {
return [storeOne];
}
static calculateState() {
const someData = storeOne.getState();
return {
someData
};
}
}
goal(something like this that subscribes to the store):
function exampleComponent(props) {
const [storeData] = useState(storeOne.getState());
return (
<someElement
data={storeData}
>
</someElement>
);
}
Note: tried using useState as above but it does not subscribe to storeOne, so the component does not re-render when storeOne state changes. I'd like to be able to somehow subscribe to changes in storeOne so that the component will re-render when there are changes to storeOne.
I'm writing a unit test for a React component that is connected to Redux. One of the functions is the component is that it displays data if questionReducer.showquestions == true. I have attempted to re-create this functionality in the component by setting props with wrapper.setProps({ questionReducer: { showquestions: true } }). However, when I attempt this approach, I get the error:
ReactWrapper::setProps() expects a function as its second argument
How can I properly set the props for the connected Reducer in the component I am testing?
You should test the component alone, without being connected to Redux. That allows you to give props directly to component.
Example:
export class Component_ extends React.Component {
// your component logic here
}
const mapStateToProps = {
showQuestions: questionReducer.showquestions
}
const Component = connect(mapStateToProps)(Component_)
export default Component
And then in test you can just do this
const wrapper = shallow(<Component_ showQuestions={true} />
I have the following react component:
class Cmp extends React.Component {
render () {
return <h3>{this.props.title}</h3>;
}
}
But I would like to expose or say to the consumer of my component to use it with a title otherwise it does not work the component.
Consumer would use it like
<Cmp title='Some fancy title' />
I need the consumer of my component to know that he should provide a title otherwise the component does not have any sense.
You can use PropTypes and set it to isRequired. You can also check if the prop is set at componentWillReceiveProps() and throw your error.
If you return null from a render method, nothing is rendered. You could use this knowledge to conditionally check if the prop is passed, and return null if the prop is not passed. The advantage here over using componentWillReceiveProps() is that you could use a functional component rather than a class component.
In rare cases you might want a component to hide itself even though it
was rendered by another component. To do this return null instead of
its render output.
Preventing Component from Rendering
Realistically you would also use PropTypes.
Cmp.propTypes = {
title: PropTypes.string.isRequired
};
Short Example
import React from 'react';
import PropTypes from 'prop-types';
const Cmp = (props) => props.title ? <h3>{props.title}</h3> : null
Cmp.propTypes = {
title: PropTypes.string.isRequired
}
export default Cmp;
I am currently building an app with React, React Router and React Redux
Versions:
React - v15.5.4
React Router - v4.0
React Redux - v.5.0.6
I am new to React and even newer to Redux and right when I got my head around the connect HOC I started to have this error that I cant seem to figure out.
When I connect a component to my redux store after a <switch> element and some <Route> elements. My connect within that returns my props as false boolean values where as the component within the connect has the correct props.
See code and error below for example.
Component
UserDashboardPage = connect(state => {
console.log("STATE", state);
return {
user: state.user.user,
userAuth: state.user.userAuth,
userFetched: state.user.fetched
};
})(UserDashboardPage);
UserDashboardPage.propTypes = {
user: PropTypes.shape(),
userAuth: PropTypes.shape(),
userFetched: PropTypes.boolean,
dispatch: PropTypes.func
};
CONSOLE LOG STATE
Connect with boolean prop values
Component with correct props
ERROR:
You are overwriting the local UserDashboardPage variable with the result of calling connect(). You then set PropTypes on the component returned by connect().
While you can do that, what you want in this case is to set the PropTypes of the wrapped component, not the wrapper component. Just swapping the order of execution will do it:
UserDashboardPage.propTypes = {
};
UserDashboardPage = connect(state => {
...
})(UserDashboardPage);
But you may want to consider using a different variable name for one component or the other, e.g.
UserDashboardPage.propTypes = {
};
const ConnectedUserDashboardPage = connect(state => {
...
})(UserDashboardPage);
This is usually not a problem since most people just immediately export the connected component as the default export:
export default connect(...)
The false values you're seeing are from React assigning default values to those props that failed validation. And they will always fail validation since those props are pulled from context, not passed down as normal props.
why are you passing UserDashboardPage into connect? This should be your non connected component