I need help. In my colorcontrol I am trying to do a this.props.dispatch(triggerFBEvent(fbID, method, params)) with no luck.
What works though is if I were to just do just triggerFBEvent(fbID, method, params). I am getting the error:
index.bundle.js:61968 Uncaught TypeError: this.props.dispatch is not a function
What I am trying to accomplish is to be able to send in new props with the line above, and then on
componentWillMount() {
this.props.dispatch(fetchFBEvent(this.props.fbID, "getColor"))
}
Call a custom service to update state with appropriate colors. But this.props.dispatch is not a function there either.
import React from 'react'
import { connect } from 'react-redux'
import {triggerFBEvent, triggerFBClearEvent, fetchFBEvent} from '../actions/functionblocksActions'
`
import { CustomPicker, HuePicker, SaturationPicker, SliderPicker, CustomPointer } from 'react-color';
#connect((store) => {
return {
fb: store.functionblocks.functionblock
}
})
export default class Functionblock extends React.Component {
constructor(props) {
super(props);
this.state = {
};
this._getType = this._getType.bind(this)
}
_getType (wanted) {
const { fbID, fbName, func } = this.props;
let type;
let types = {
'com.xxxx.xx.service.dal.functions.Alarm': function () {
type = <Alarm fbID={fbID} fbName={fbName}/>;
},
'com.xxxx.xxx.service.dal.functions.BooleanControl': function () {
type = <BooleanControl fbID={fbID} fbName={fbName}/>;
},
'com.xxx.xxxx.service.dal.functions.BooleanSensor': function () {
type = <BooleanSensor fbID={fbID} fbName={fbName} />;
},
'com.xxxx.xxx.service.dal.functions.ColorControl': function () {
type = <ColorControl func={func} fbID={fbID} fbName={fbName} />;
}
'default': function () {
type = <WakeUp fbID={fbID} fbName={fbName} />;
}
};
// invoke it
(types[wanted] || types['default'])();
// return a String with chosen type
return type;
}
render() {
const { fbID, fbName, func } = this.props;
const type = this._getType(func.serviceProperties["clazz"]);
return(
<div>
{type}
</div>
)
}
}
// Classes for the different functions.
class ColorControl extends React.Component {
componentWillMount() {
this.props.dispatch(fetchFBEvent(this.props.fbID, "getColor"))
}
constructor(props) {
super(props);
this.state = {
color: {
h: 150.3197479248047,
s: 0.5,
l: 0.5
}
}
this.onChangeComplete = this.onChangeComplete.bind(this);
}
componentWillReceiveProps(nextProps) {
alert("YEP")
// let home = this._getHome(nextProps.areas, nextProps.statics);
// if(home!=null){
// this.setState({
// inputHome: home.name,
// })
// }
}
onChangeComplete(color, event) {
let hsl = color.hsl;
let hue = color.hsl.h / 360;
let saturation = color.hsl.s;
let lightness = color.hsl.l;
this.setState({ color: hsl })
// Update props
let fbID = this.props.fbID;
let method = "setColor";
let params = {"hue": hue, "sat": saturation, "light": lightness};
this.props.dispatch(triggerFBEvent(fbID, method, params))
}
_defineFunction(){
}
render() {
return (<div>
<SliderPicker {...this.props}
pointer={ CustomPointer }
color={this.state.color}
onChangeComplete={ this.onChangeComplete }
direction={ 'horizontal' || 'vertical' }/>
</div>
)
}
}
Can anyone help me understand whats going wrong?
You need to connect ColorControl to Redux, otherwise it doesn't get a dispatch prop.
#connect()
class ColorControl extends React.Component {
Here is the codebase to use your actions without problems.
import * as actions from './YourActionsPath'
import { bindActionCreators } from 'redux'
#connect(
state => ({
yourDerivedState : state.somePath
}),
dispatch => ({
actions : bindActionCreators( actions, dispatch )
})
)
export default class YourClass extends Component {
someMethod(){
this.props.actions.yourAction() // call it with no problems
}
render(){
return (
// your html
)
}
}
I hope you get the idea. If you use this patterns, you won't have problems.
As you can use your derived state as this.props.derivedState, which you define in the connect, you can also use your actions you defined on the connect.
Besides you can use this.props.dispatch if you connected your component. In case you need it as in your case, but this makes the code less clear and leads to maintainance problems.
import { createStore } from 'redux'
let store = createStore(//define reducer,preload state and enhancer)
//call action
store.dispatch(triggerFBEvent(fbID, method, params))
Related
I am trying to rerender a component in React.
The setup:
I am using React Context to fetch some data from a Firestore database. So the fetching is Async.
My component is then fetching the data using: static contextType = MyContext and accessing via this.context
I store this context data on the components state to try to trigger a rerender whenever this data is changed.
I pass the data to a child component where it renders a list based on this data.
The problem:
I manage to update the state and even when debugging I can see my state updating to the correct data BUT the component does not rerender either the childcomponent or the list.
The expected list shows as soon as I click anything on the page so my guess is that the data is trapped in some kind of middle stage.
What I've tried:
I tried using the componentDidUpdate to make a check if the context is different than the current state and trigger a function that sets the state (I have even tried with setState function directly after the check) => Still state updates but no rerender is triggered (I can see the new data on state)
I tried using the getDerivedStateFromProps on the child component to do a check if the Props have changed and also tried storing the props in the child components own state => Still same result as before.
I am not sure what else to try, I thought that React triggers a rerender everytime state chages but probably I am doing something really wrong.
Here is my parent:
import React, { Component } from 'react';
import styles from './artistdropdown.module.css';
import { returnCollection } from '../../utils/Firebase.js';
import MyContext from '../../utils/MyContext.js';
import ArtistSelected from './ArtistSelected.js';
import ArtistsList from './ArtistsList';
export class ArtistDropdown extends Component {
static contextType = MyContext;
constructor(props) {
super(props);
this.state = {
artists: [],
currentArtist: {
id: null,
name: null
}
};
this.fetchArtist = (aId, artists) => {
const state = {
id: null,
name: null,
};
artists && artists.forEach((a) => {
if (a.id === aId) {
state = {
...state,
id: a.id,
name: a.name,
}
}
})
return state;
}
this.loadToState = (state) => {
this.setState({
...this.state,
...state,
})
}
this.click = (id) => {
this.context.handleArtistSelection(id);
this.props.handleBandDropdown();
}
}
componentDidMount() {
const aId = this.context.state.user.last_artist;
const artists = this.context.state.user.artists;
const currentArtist = this.fetchArtist(aId, artists);
const state = {
artists: artists,
currentArtist: currentArtist,
}
this.loadToState(state);
}
componentDidUpdate(props, state) {
if (this.state.artists !== this.context.state.user.artists) {
const aId = this.context.state.user.last_artist;
const artists = this.context.state.user.artists;
const currentArtist = this.fetchArtist(aId, artists);
const state = {
artists: artists,
currentArtist: currentArtist,
}
this.loadToState(state);
}
}
render() {
const bandDropdown = this.props.bandDropdown;
return (
<>
<ArtistSelected
currentBand={this.state.currentArtist.name}
handleDropdown={this.props.handleBandDropdown}
expanded={bandDropdown}
/>
<ul className={bandDropdown ? styles.band_options + ' ' + styles.expanded : styles.band_options}>
<ArtistsList artists={this.state.artists} />
</ul>
</>
)
}
}
export default ArtistDropdown
and here is my child:
import React, { Component } from 'react';
import MyContext from '../../utils/MyContext.js';
import ArtistItem from './ArtistItem.js';
export class ArtistsList extends Component {
static contextType = MyContext;
constructor(props) {
super(props);
this.state = {
artists: [],
};
this.loadToState = (state) => {
this.setState({
...this.state,
...state,
}, () => { console.log(this.state) })
}
}
componentDidMount() {
const artists = this.props.artists;
const state = {
artists: artists,
}
this.loadToState(state);
}
componentDidUpdate(props, state) {
if (state.artists !== this.state.artists) {
this.loadToState(state);
}
}
static getDerivedStateFromProps(props, state) {
if (props.artists !== state.artists) {
return {
artists: props.artists,
}
} else {
return null;
}
}
render() {
// const artistList = this.state.artists;
const artistList = this.state.artists;
const list = artistList && artistList.map((a) => {
return (<ArtistItem key={a.id} onClick={() => this.click(a.id)} name={a.name} />)
})
return (
<>
{list}
</>
)
}
}
export default ArtistsList
I am new to MST and is having a hard time finding more examples with async actions. I have an api that will return different data depending on the params you pass to it. In this case, the api can either return an array of photos or tutorials. I have set up my initial values for the store like so:
data: {
photos: [],
tutorials: []
}
Currently, I am using applySnapshot to update the store and eventually, that will trigger a re-render of my React component. In order to display both photos and tutorials, I need to call the api twice (Once with the params for photos and the second time for tutorials). I am running into an issue where the snapshot from the first update shows that photos and tutorials have the same values and only on the second update, do I get the correct values. I am probably misusing applySnapshot to re-render my React components. I would like to know the better/proper way of doing this. What is the best way to re-render my React components after the api has yielded a repsonse. Any suggestions are much appreciated
I have set up my store like this:
import { RootModel } from '.';
import { onSnapshot, getSnapshot, applySnapshot } from 'mobx-state-tree';
export const setupRootStore = () => {
const rootTree = RootModel.create({
data: {
photos: [],
tutorials: []
}
});
// on snapshot listener
onSnapshot(rootTree, snapshot => console.log('snapshot: ', snapshot));
return { rootTree };
};
I have created the following model with an async action using generators:
import {types,Instance,applySnapshot,flow,onSnapshot} from 'mobx-state-tree';
const TestModel = types
.model('Test', {
photos: types.array(Results),
tutorials: types.array(Results)
})
.actions(self => ({
fetchData: flow(function* fetchData(param) {
const results = yield api.fetch(param);
applySnapshot(self, {
...self,
photos: [... results, ...self.photos],
tutorials: [... results, ...self.tutorials]
});
})
}))
.views(self => ({
getPhoto() {
return self.photos;
},
getTutorials() {
return self.tutorials;
}
}));
const RootModel = types.model('Root', {
data: TestModel
});
export { RootModel };
export type Root = Instance<typeof RootModel>;
export type Test = Instance<typeof TestModel>;
React component for Photos.tsx
import React, { Component } from 'react';
import Spinner from 'components/Spinner';
import { Root } from '../../stores';
import { observer, inject } from 'mobx-react';
interface Props {
rootTree?: Root
}
#inject('rootTree')
#observer
class Photos extends Component<Props> {
componentDidMount() {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('photo');
}
componentDidUpdate(prevProps) {
if (prevProps.ctx !== this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('photo');
}
}
displayPhoto() {
const { rootTree } = this.props;
if (!rootTree) return null;
// calling method in MST view
const photoResults = rootTree.data.getPhoto();
if (photoResults.$treenode.snapshot[0]) {
return (
<div>
<div className='photo-title'>{'Photo'}</div>
{photoResults.$treenode.snapshot.map(Item => (
<a href={photoItem.attributes.openUrl} target='_blank'>
<img src={photoItem.url} />
</a>
))}
</div>
);
} else {
return <Spinner />;
}
}
render() {
return <div className='photo-module'>{this.displayPhoto()}</div>;
}
}
export default Photos;
Similarly, Tutorials.tsx is like so:
import React, { Component } from 'react';
import Spinner from '';
import { Root } from '../../stores';
import { observer, inject } from 'mobx-react';
interface Props {
rootTree?: Root;
}
#inject('rootTree')
#observer
class Tutorials extends Component<Props> {
componentDidMount() {
if (this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('tuts');
}
}
componentDidUpdate(prevProps) {
if (prevProps.ctx !== this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.search.fetchData('tuts');
}
}
displayTutorials() {
const { rootTree } = this.props;
if (!rootTree) return null;
// calling method in MST view
const tutResults = rootTree.data.getTutorials();
if (tutResults.$treenode.snapshot[0]) {
return (
<div>
<div className='tutorials-title'>{'Tutorials'}</div>
{tutResults.$treenode.snapshot.map(tutorialItem => (
<a href={tutorialItem.attributes.openUrl} target='_blank'>
<img src={tutorialItem.url} />
</a>
))}
</div>
);
} else {
return <Spinner />;
}
}
render() {
return <div className='tutorials-module'>{this.displayTutorials()}</div>;
}
}
export default Tutorials;
Why are you using applySnapshot at all in this case? I don't think it's necessary. Just assign your data as needed in your action:
.actions(self => ({
//If you're fetching both at the same time
fetchData: flow(function* fetchData(param) {
const results = yield api.fetch(param);
//you need cast() if using Typescript otherwise I think it's optional
self.photos = cast([...results.photos, ...self.photos])
//do you really intend to prepend the results to the existing array or do you want to overwrite it with the sever response?
self.tutorials = cast(results.tutorials)
})
}))
Or if you need to make two separate requests to fetch your data it's probably best to make it two different actions
.actions(self => ({
fetchPhotos: flow(function* fetchPhotos(param) {
const results = yield api.fetch(param)
self.photos = cast([... results, ...self.photos])
}),
fetchTutorials: flow(function* fetchTutorials(param) {
const results = yield api.fetch(param)
self.tutorials = cast([... results, ...self.tutorials])
}),
}))
Regardless, it doesn't seem like you need applySnapshot. Just assign your data in your actions as necessary. There's nothing special about assigning data in an async action.
I'm trying to abstract away my onClick function(s) into its own file and then later use it in a component's button(s).
However the function needs to conditionally update the state of the component it is being used in.
export let ButtonClickHandlers = {
refreshDatabase: (url: string) => {
service.fetchJson<any>(url)
.then(res => {
if(res.isSuccessStatusCode){
//update state
}
else{
//update state
}
});
},
//more functions to be added later
}
In my component I want to do something like this:
import {ButtonClickHandlers} from '../ButtonClickHandlers';
<button onClick = {ButtonClickHandlers.refreshDatabase} />
How do I implement this functionality?
This is how i have done it, there might be better ways.I have imported the clickevent and passed the context as paremeter
clickevenhandler.js
const clickEvent = (context) => {
context.setState({ click: true });
}
export default clickEvent;
my test component
import clickEvent from './clickhandler';
class TestComp extends React.Component<any, any>{
constructor(props) {
super(props);
this.state = {
click: false
}
}
renderclicked() {
if (this.state.click) {
return (<div>clicked</div>);
}
else {
return (<div></div>);
}
}
render() {
return (<div>Test
<button onClick={() => clickEvent(this)}>click</button>
{this.renderclicked()}
</div>);
}
}
I have a simple React button component that when clicked should retrieve and download data on the client browser. The problem I am experiencing is that the download is triggered and the csv file downloaded before the data is passed into the href.
Here is my component:
import { Component } from 'react';
import { connect } from 'react-redux';
import { PropTypes } from 'prop-types';
import { ManageUsersSelectors } from 'selectors/Users';
import { BatchRoleActions } from 'actions/Users';
class UsersExportButton extends Component {
constructor() {
super();
this.state = {
users: ''
};
}
getUsers(){
const { userIds } = this.props;
BatchRoleActions.getAllRoleUsers(userIds)
.then((users) => {
this.setState({ users: users});
return this.state.users;
});
}
render() {
return (
<div className="roles-export-button">
<a className="button button-default" href={this.state.users} download={'roles.csv'} onClick={() => this.getUsers()} return true>Export Csv</a>
</div>
);
}
}
function mapStateToProps(state) {
const userIds = ManageUsersSelectors.batchUserIdsSelector(state);
return {
userIds: userIds
};
}
UsersExportButton.propTypes = {
text: PropTypes.string.isRequired,
data: PropTypes.array
};
export default connect(mapStateToProps)(UsersExportButton);
How can I get the getUsers()/onClick function to complete the data retrieval step before downloading?
When i debug my code I can see that the getUsers function returns data - however after the download is triggered
Make sure to bind this to your functions. In your constructor you can do:
constructor() {
super();
this.state = {
users: ''
};
this.getUsers = this.getUsers.bind(this);
}
or you can use the bind this function:
getUsers = () => {
const { userIds } = this.props;
BatchRoleActions.getAllRoleUsers(userIds)
.then((users) => {
this.setState({ users: users});
return this.state.users; // This should be removed, you can use this.state.users throughout this component.
});
}
Why not get the user data in the componentDidMount lifecycle method? It doesn't look like it needs to be called onClick.
{
// ...
componentDidMount() {
this.getUsers();
}
// ...
render() {
return (
<div className="roles-export-button">
<a className="button button-default" href={this.state.users} download={'roles.csv'}>Export Csv</a>
</div>
)
}
}
How about handling the default "link" behaviour manually to get more control? Also you should probably try to access state after setState has been executed via its callback.
e.g.
getUsers(cb){
const { userIds } = this.props;
BatchRoleActions.getAllRoleUsers(userIds)
.then((users) => {
// note the callback of setState which is invoked
// when this.state has been set
this.setState({ users: users }, cb);
});
}
const handleClick = () => {
this.getUsers(() => {
window.open(this.state.whatever)
})
}
<span onClick={handleClick}>Export Csv</span>
Have a code that delete element by request
class ArchOrDlt extends Component{
constructor(props) {
super(props)
}
deleteItem(itemId, e) {
console.log(itemId);
this.props.test();
this.props.DeleteListProfileItem(itemId);
}
ArchOrDlt() {
const isdel = this.props.isdel;
const itemId = this.props.itemId;
if (isdel == 1) {
return (<div><a onClick={this.deleteItem.bind(this, itemId)} >delete</a></div>);
}
return (<div>archived</div>);
}
render() {
return (
<div>
{this.ArchOrDlt()}
</div>
);
}
}
If I press link I get Uncaught TypeError: this.props.test is not a function
There I dispatch to props
const mapDispatchToProps = function(dispatch) {
return {
IncomeListProfile: () => dispatch(IncomeProfileList()),
DeleteListProfileItem: (id) => dispatch(DeleteListProfileItem(id)),
openPopUp: () => dispatch(openPopUp()),
test: () => dispatch(test())
}
}
Can't Understand Why it's happen for exaple if I move this.props.test();
to another click, everything working fine, there full component
https://plnkr.co/edit/OEugCIxoAGE8iVb57WOa?p=catalogue
First of all, you should use bindActionCreators function from redux
import { bindActionCreators } from 'redux';
...
const mapDispatchToProps = (dispatch) => {
return {
IncomeListProfile : bindActionCreators(IncomeProfileList, dispatch),
...
}
}
http://redux.js.org/docs/api/bindActionCreators.html