I'm using reactjs with redux for state management. I want to change state in a component with redux. but when I send props to the component and I inspect it with console.log(), returned undefined to me.
please guide me to solve problem...
thanks
Svg Viewer Component
import React, { useEffect, useState, useContext } from "react";
import * as d3 from "d3";
import store from "../../redux/store";
const SvgViewer = ({ nodesData, svgFilePath, props }) => {
//const { visible, invisible } = props;
const [svgContainer, setSvgContainer] = useState(undefined);
const showNodesOnSvg = nodes => {
let svgDoc = svgContainer.contentDocument;
let gTags = svgDoc.querySelectorAll("svg > g");
let container = null;
if (gTags.length > 1) container = svgDoc.querySelector("g:nth-of-type(2)");
else container = svgDoc.querySelector("g:nth-of-type(1)");
let node = d3.select(container);
nodesData.forEach(nodeData => {
node
.append("text")
.attr("id", "node" + nodeData["id"])
.attr("fill", "white")
.attr("text-anchor", "middle")
.attr("x", nodeData["positionX"])
.attr("y", nodeData["positionY"])
.attr("class", "clickable-node")
.style("font-size", "8px")
.style("position", "absolute")
.style("cursor", "pointer")
.style("display", "inline-block")
.on("click", function() {
clickHandler(nodeData["id"]);
})
.text("N/A" + " " + nodeData["symbol"]);
let nodeCreated = d3.select(
svgDoc.getElementById("node" + nodeData["id"])
);
nodeCreated
.append("title")
.attr("id", "title" + nodeData["id"])
.text(" " + nodeData["tagCode"]);
});
};
const clickHandler = nodeID => {
console.log(props); //not show props
};
useEffect(() => {
const svg = document.querySelector("#svgobject");
setSvgContainer(svg);
svg.onload = () => {
if (nodesData != null) {
showNodesOnSvg();
}
};
});
return (
<div className="unit-schema-container1" key={svgFilePath}>
{/* <Spin indicator={objectLoading} spinning={this.state.objectLoading}> */}
<object id="svgobject" type="image/svg+xml" data={svgFilePath}></object>
{/* </Spin> */}
</div>
);
};
export default SvgViewer;
store
import { createStore, combineReducers } from "redux";
import modalReducer from "./reducers/modalReducer";
const store = createStore(modalReducer);
export default store;
Reducer:
function modalReducer(state = initialState, action) {
const initialState = false;
switch (action.type) {
case "VISIBALE":
return (state = true);
case "INVISIBALE":
return (state = false);
default:
return state;
}
}
export default modalReducer;
Action
export function visible() {
return {
type: "VISIBLE"
};
}
export function invisible() {
return {
type: "INVISIBLE"
};
}
Svg Container
import { visible, invisible } from "../redux/actions/modalAction";
import { connect } from "react-redux";
import svgViewer from "../pages/unit-monitor/svg-viewer";
const mapStateToProps = state => ({
visibale: state.value
});
const mapDispatchToProps = dispatch => {
return {
visible: () => dispatch(visible()),
invisible: () => dispatch(invisible())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(svgViewer);
Svg Component
import React, { PureComponent } from "react";
import { Row, Col, Spin, Icon } from "antd";
import axios from "axios";
import "./tree-select.scss";
import History from "./history";
import SchemaTreeSelect from "./schema-tree-select";
import SvgViewer from "../../container/svgViewerContainer";
class UnitMonitor extends PureComponent {
constructor() {
super();
}
state = {
nodes: undefined,
nodeId: 25,
valueSignalR: [],
searchText: "",
selectedChart: "Line",
tsSchemaLoading: false,
objectLoading: false,
svgFilePath: ""
};
onChangeShcema = schemaID => {
axios.get("/api/schemata/get-schemata-nodes/" + schemaID).then(response => {
this.setState({ nodes: response.data });
let path = response.data[0].file;
let svgFile = require("./images/" + path);
this.setState({ svgFilePath: svgFile });
});
};
render() {
return (
<Row type="flex" className="">
<Col span={25}>
<SchemaTreeSelect handleChange={this.onChangeShcema} />
<History nodeId={this.state.nodeId} />
<SvgViewer
svgFilePath={this.state.svgFilePath}
nodesData={this.state.nodes}
/>
</Col>
</Row>
);
}
}
export default UnitMonitor;
You are trying to read the value property on state, but there is no such property on the state returned by your reducer... it is just true or false so replace the state.value with the state itself in your mapStateToProps.
const mapStateToProps = state => ({
visibale: state
});
Also there is an inconsistency between your types used for dispatching the actions on the redux state.
"VISIBALE"/ "UNVISIBALE" is used in reducer while "VISIBLE"/ "INVISIBLE" is used in action dispatcher.`
Related
I have a basic app here and I'm trying to config React Context properly but it isn't working. My goal is to render PlayScreen with the content of currentStage inside the React Context. Game changes the context but App keeps rendering PlayScreen with the "welcome" string, instead of "won" or "lost".
Also, I know that gameContext.js is for autocompletion but I added "welcome" there to have a first default state. Somehow I couldn't find a way to set up that very first "welcome" context when App is rendered for the first time.
I tried feeding PlayScreen with the context itself and didn't work, and now I tried setting a state with it but it doesn't work either (even when using useEffect and having the context as a dependency).
So I have two questions, what am I doing wrong? and, my way to set up the "welcome" default state is wrong? If so, how can I do it? Thanks.
gameContext.js
import React from 'react';
const GameContext = React.createContext({
currentStage: 'welcome',
playerStage: (stage) => {},
});
export default GameContext;
GameProvider.jsx
import React, { useReducer, useMemo } from 'react';
import PropTypes from 'prop-types';
import GameContext from './gameContext';
const defaultState = {
currentStage: '',
};
const gameReducer = (state, action) => {
if (action.type === 'STAGE') {
return {
currentStage: action.playerStage,
};
}
return defaultState;
};
const GameProvider = ({ children }) => {
const [gameState, dispatchGameAction] = useReducer(gameReducer, defaultState);
const playerStageHandler = (playerStage) => {
dispatchGameAction({
type: 'STAGE',
playerStage,
});
};
const gameContext = useMemo(
() => ({
currentStage: gameState.currentStage,
playerStage: playerStageHandler,
}),
[gameState.currentStage]
);
return (
<GameContext.Provider value={gameContext}>{children}</GameContext.Provider>
);
};
GameProvider.propTypes = {
children: PropTypes.node.isRequired,
};
export default GameProvider;
App.jsx
import React, { useContext, useState, useEffect } from 'react';
import GameProvider from './store/GameProvider';
import GameContext from './store/gameContext';
import PlayScreen from './components/PlayScreen';
import Settings from './components/Settings';
import Game from './components/Game';
const App = () => {
const gameContext = useContext(GameContext);
const [stage, setStage] = useState(gameContext.currentStage);
useEffect(() => {
setStage(gameContext.currentStage);
}, [gameContext]);
const [currentScreen, setCurrentScreen] = useState({
playScreen: true,
settings: false,
game: false,
});
const changeScreenHandler = (newScreen) => {
switch (newScreen) {
case 'playScreen':
setCurrentScreen({
playScreen: true,
settings: false,
game: false,
});
break;
case 'settings':
setCurrentScreen({
playScreen: false,
settings: true,
game: false,
});
break;
case 'game':
setCurrentScreen({
playScreen: false,
settings: false,
game: true,
});
break;
default:
break;
}
};
return (
<GameProvider>
{currentScreen.playScreen && (
<PlayScreen stage={stage} onChangeScreen={changeScreenHandler} />
)}
{currentScreen.settings && (
<Settings onChangeScreen={changeScreenHandler} />
)}
{currentScreen.game && <Game onChangeScreen={changeScreenHandler} />}
</GameProvider>
);
};
export default App;
PlayScreen.jsx
import PropTypes from 'prop-types';
const PlayScreen = ({ stage, onChangeScreen }) => {
const clickHandler = () => {
onChangeScreen('settings');
};
return (
<div>
<h1>{stage}</h1>
<button type="button" onClick={clickHandler}>
Go
</button>
</div>
);
};
PlayScreen.propTypes = {
stage: PropTypes.string.isRequired,
onChangeScreen: PropTypes.func.isRequired,
};
export default PlayScreen;
Game.jsx
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import GameContext from '../store/gameContext';
const Game = ({ onChangeScreen }) => {
const gameContext = useContext(GameContext);
const wonHandler = () => {
onChangeScreen('playScreen');
gameContext.playerStage('won');
};
const lostHandler = () => {
onChangeScreen('playScreen');
gameContext.playerStage('lost');
};
return (
<div>
<h1>GAME RUNNING</h1>
<button type="button" onClick={wonHandler}>
won
</button>
<button type="button" onClick={lostHandler}>
lost
</button>
</div>
);
};
Game.propTypes = {
onChangeScreen: PropTypes.func.isRequired,
};
export default Game;
You are consuming the GameContext above the GameProvider. The context shouldn't be available because App isn't being provided the context by the GameProvider.
Try moving everything underneath GameProvider into its own component and consume the context there.
I know that problem is that there is a mutation. Because mostly there is no rerendering because of it. But, can't understand what's wrong in the way I'm doing this.
For data which I get from backend everything is fine, but if I try to change state from FE it's not working.
The problem is with groupDevicesBySelectedFilter(devicesGroups).
After action is done, I get response that state was changed in console, but as in the title no changings on FE.
Filter.tsx
import * as React from 'react'
import {IAppState} from '../../reducers'
import {connect} from 'react-redux'
import { Dropdown, Header, Icon } from 'semantic-ui-react'
import { INodeTreeFilters, INodeTreeDevicesInfo } from './type-definition';
import * as nodeTreeActions from '../../actions/node-tree';
import * as _ from 'lodash';
interface INodeTreeFilterProps{
filters: INodeTreeFilters;
selectGroupsFilter: any;
groupDevicesBySelectedFilter: typeof nodeTreeActions.groupDevicesBySelectedFilter;
devices: INodeTreeDevicesInfo
}
class NodeTreeFilter extends React.Component<INodeTreeFilterProps>{
public render() {
const {filters, selectGroupsFilter, groupDevicesBySelectedFilter, devices} = this.props;
const groupsFilterSelected = (event: React.SyntheticEvent<HTMLDivElement>, data: any) => {
selectGroupsFilter({id:data.value});
const devicesGroups=_.chain(devices).groupBy(data.value).map((v, i) => {
return {
id: i,
name: i,
devices: v
}
}).value();
groupDevicesBySelectedFilter(devicesGroups);
}
return (
<Header as='h4'>
<Icon name='filter' />
<Header.Content>
Group nodes by {' '}
<Dropdown
inline = {true}
options={filters}
onChange={groupsFilterSelected}
/>
</Header.Content>
</Header>
)
}
}
const mapStateToProps = (state: IAppState) => (
{
filters: state.sidebar.filters,
devices: state.sidebar.devices,
});
const mapDispatchToProps = {
selectGroupsFilter: nodeTreeActions.selectNodeTreeGroupFilter,
groupDevicesBySelectedFilter: nodeTreeActions.groupDevicesBySelectedFilter
};
export default connect(mapStateToProps, mapDispatchToProps)(NodeTreeFilter)
My reducer
export const devicesGroupsReducer = (state: IDevicesGroups = [], action: IActionWithPayload) => {
switch (action.type) {
case nodeTreeActions.GROUP_DEVICES_BY_SELECTED_FILTER:
return action.payload
default:
return state;
} };
export interface IActionWithPayload extends Action {
payload: any;
}
And finally my child component, which should rerendering.
import * as React from 'react'
import {List} from 'semantic-ui-react'
import {IAppState,} from '../../reducers'
import {connect} from 'react-redux'
import {INodeTreeDevicesInfo, INodeTreeDeviceInterfaces, IDevicesGroups} from './type-definition'
import * as nodeTreeActions from '../../actions/node-tree'
// import * as nodeTreeService from '../../services/node-tree'
import {requestError} from "../../actions/error";
interface INodeTreeProps{
devices: INodeTreeDevicesInfo ;
interfaces: INodeTreeDeviceInterfaces;
getDeviceInterfaces: typeof nodeTreeActions.getNodeTreeDeviceInterfaces;
requestError: typeof requestError;
deviceGroups: IDevicesGroups;
}
class NodeTree extends React.Component<INodeTreeProps> {
public generateParentTree = (array: any) => {
const tree = array.map((item:any) => (
<List.Item key={item.id}>
<List.Icon name={ "caret right"} />
<List.Content onClick={this.generateChildren} verticalAlign='middle'>
<List.Description>{item.name}</List.Description>
</List.Content>
</List.Item>
))
return tree
}
public generateChildren = () => {
console.log('I will generate children')
}
public render() {
const {devices, deviceGroups} = this.props;
const parentArray = deviceGroups !== undefined && deviceGroups.length !== 0 ? deviceGroups : devices;
const Tree = this.generateParentTree(parentArray)
console.log('')
return (
<div>
<List>
{Tree}
</List>
</div>
);
}
}
const mapStateToProps = (state: IAppState) => (
{
devices: state.sidebar.devices,
interfaces: state.sidebar.interfaces,
deviceGroups: state.sidebar.deviceGroups
});
const mapDispatchToProps = {
requestError,
getDeviceInterfaces: nodeTreeActions.getNodeTreeDeviceInterfaces
};
export default connect(mapStateToProps, mapDispatchToProps)(NodeTree)
Pls, never mind on public and private states in code
You are mutating your state in the reducer. You need to return a new state object and update it with your payload.
return {
...state,
IDevicesGroups: [...state.IDevicesGroups, action.payload]
}
Should be something like that.
I'm building a small app using React, semantic-ui-react, redux-subspace.
I have many different tables and when the user clicks on one of the cells, the value supposed to come out on the console but the result is undefined when it clicked. I'm trying to reuse reducer. Same action with different instances.
I appreciate any comments that guide me to right direction.
PartA.js
This component renders Tables and wrapped with <SubspaceProvider>.
<Segment inverted color='black'>
<h1>Age </h1>
{ this.state.toggle ?
<SubspaceProvider mapState={state => state.withSpouseAge} namespace="withSpouseAge">
<TableForm
headers={spouse_ageHeaders}
rows={spouse_ageData}
namespace={'withSpouseAge'}
/>
</SubspaceProvider> :
<SubspaceProvider mapState={state => state.withoutSpouseAge} namespace="withoutSpouseAge">
<TableForm
headers={withoutSpouse_ageHeader}
rows={withoutSpouse_ageData}
namespace={'withoutSpouseAge'}
/>
</SubspaceProvider> }
TableForm.js
This component return Table with the Data and this is where I want to implement onClick method.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Table } from 'semantic-ui-react';
import { select } from '../actions';
const shortid = require('shortid');
class TableForm extends Component {
constructor(props){
super(props);
this.state = {
activeIndex: 0,
}
this.handleOnClick = this.handleOnClick.bind(this);
this.isCellActive = this.isCellActive.bind(this);
};
isCellActive(index) {
this.setState({ activeIndex: index });
}
handleOnClick(index, point) {
this.isCellActive(index);
this.props.onSelect(point);
};
tableForm = ({ headers, rows }) => {
const customRenderRow = ({ factor, point, point2 }, index ) => ({
key: shortid.generate(),
cells: [
<Table.Cell content={factor || 'N/A'} />,
<Table.Cell
content={point}
active={index === this.state.activeIndex}
textAlign={'center'}
selectable
onClick={() => this.handleOnClick(index, point)}
/>,
<Table.Cell
content={point2}
textAlign={'center'}
selectable
/>
],
});
return (
<Table
size='large'
padded
striped
celled
verticalAlign={'middle'}
headerRow={this.props.headers}
renderBodyRow={customRenderRow}
tableData={this.props.rows}
/>
)
};
render() {
console.log(this.props.withSpouseAgePoint);
const { headers, rows } = this.props;
return (
<div>
{this.tableForm(headers, rows)}
</div>
);
}
};
const mapDispatchToProps = (dispatch) => {
return {
onSelect: (point) => {dispatch(select(point))},
}
}
const mapStateToProps = state => {
return {
withSpouseAgePoint: state.withSpouseAge,
withSpouseLoePoint: state.withSpouseLoe,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TableForm);
Action
import {
SELECT,
} from './types';
export const select = (points) => ({
type: 'SELECT',
points,
});
Reducer.js
import { SELECT } from '../actions/types';
const INITIAL_STATE = {
point: 0,
};
const selectionReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SELECT':
return { ...state, point: state.point + action.points };
default:
return state;
}
};
export default selectionReducer;
Reducer index.js
import { createStore, combineReducers } from 'redux';
import { subspace, namespaced } from 'redux-subspace';
import selectionReducer from './selectionReducer';
import toggleReducer from './toggleReducer';
const reducers = combineReducers({
withSpouseAge: namespaced('withSpouseAge')(selectionReducer),
withSpouseLoe: namespaced('withSpouseLoe')(selectionReducer),
withSpouseOlp: namespaced('withSpouseOlp')(selectionReducer),
withSpouseOlp2: namespaced('withSpouseOlp2')(selectionReducer),
withSpouseExp: namespaced('withSpouseExp')(selectionReducer),
withoutSpouseAge: namespaced('withoutSpouseAge')(selectionReducer),
withoutSpouseLoe: namespaced('withoutSpouseLoe')(selectionReducer),
withoutSpouseOlp: namespaced('withoutSpouseOlp')(selectionReducer),
withoutSpouseOlp2: namespaced('withoutSpouseOlp2')(selectionReducer),
withoutSpouseExp: namespaced('withoutSpouseExp')(selectionReducer),
toggle: toggleReducer,
});
Update
I added below TableForm component
const mapDispatchToProps = (dispatch) => {
return {
onSelect: (point) => {dispatch(select(point))},
}
}
const mapStateToProps = state => {
return {
withSpouseAgePoint: state.withSpouseAge,
withSpouseLoePoint: state.withSpouseLoe,
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TableForm);
implement this.props.onSelect(point) on handleOnClick. It still shows me the same result undefined. I checked store states by getState(). consloe.log. I think my implementation of redux-subspace is wrong. I uploaded whole TableForm component and also updated reducer. Please help me out!
update 2
I replaced mapStateToProps and it worked like a magic. Thank you again #JustinTRoss.
but there is another problem, all the states are coming out with the same value when I clicked on the cell.
. my plan is each state has their own value stored.
const mapStateToProps = state => {
return {
withSpouseAgePoint: state,
withoutSpouseAge: state,
}
}
You have already namespaced your component to withSpouseAge and mapped state to state.withSpouseAge in your SubspaceProvider. Thus, you're calling the equivalent of state.withSpouseAge.withSpouseAge (undefined).
Another potential issue is the signature with which you are calling connect. From the snippet you provided, there's no way to be sure of the value of 'select'. Typically, connect is called with 2 functions, often named mapStateToProps and mapDispatchToProps. You are calling connect with a function and an object. Here's an example from http://www.sohamkamani.com/blog/2017/03/31/react-redux-connect-explained/#connect :
import {connect} from 'react-redux'
const TodoItem = ({todo, destroyTodo}) => {
return (
<div>
{todo.text}
<span onClick={destroyTodo}> x </span>
</div>
)
}
const mapStateToProps = state => {
return {
todo : state.todos[0]
}
}
const mapDispatchToProps = dispatch => {
return {
destroyTodo : () => dispatch({
type : 'DESTROY_TODO'
})
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TodoItem)
Additionally, there's one other issue, although it isn't affecting you yet: You're calling this.tableForm with 2 arguments (headers and rows), while you defined the this.tableForm function to take a single argument and destructure out 'headers' and 'rows' properties.
I'm pretty new to React and Redux and very new to react-dnd, and I think I'm doing something wildly incorrect here. Although there are other similar posts out there I can't quite find a solution in them.
I'm working on a Kanban board app that is somewhat based on the one found at https://survivejs.com/react/implementing-kanban/drag-and-drop/ though that version uses Alt.js and I'm using Redux.
The problem: when dragging a component, the action function is called but the case in the reducer (MOVE_TICKET) is not. This seems to be the case regardless of the content of the action function.
I linked the action to a click event and in this instance the action and reducer worked as expected. This leads me to think that it must be a problem with the way I've set up the Ticket component with the dnd functions.
Ticket.js:
import React from "react"
import {compose} from 'redux';
import { DragSource, DropTarget } from 'react-dnd';
import ItemTypes from '../constants/ItemTypes';
import { moveTicket } from "../actions/ticketsActions"
const Ticket = ({
connectDragSource, connectDropTarget, isDragging, isOver, onMove, id, children, ...props
}) => {
return compose (connectDragSource, connectDropTarget)(
<div style={{
opacity: isDragging || isOver ? 0 : 1
}} { ...props } className = 'ticket'>
<h3 className = 'summary'> { props.summary } </h3>
<span className = 'projectName'> { props.projectName }</span>
<span className = 'assignee'> { props.assignee } </span>
<span className = 'priority'> { props.priority } </span>
</div>
);
};
const ticketSource = {
beginDrag(props) {
return {
id: props.id,
status: props.status
};
}
};
const ticketTarget = {
hover(targetProps, monitor) {
const targetId = targetProps.id;
const sourceProps = monitor.getItem();
const sourceId = sourceProps.id;
const sourceCol = sourceProps.status;
const targetCol = targetProps.status;
if(sourceId !== targetId) {
targetProps.onMove({sourceId, targetId, sourceCol, targetCol});
}
}
};
export default compose(
DragSource(ItemTypes.TICKET, ticketSource, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
})),
DropTarget(ItemTypes.TICKET, ticketTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver()
}))
)(Ticket)
ticketsReducer.js:
export default function reducer(state={
tickets: [],
fetching: false,
fetched: false,
error: null,
}, action) {
switch (action.type) {
case "MOVE_TICKET": {
return [{...state, tickets: action.payload}]
}
}
return state
}
ticketsActions.js
import store from '../store';
export function moveTicket({sourceId, targetId, sourceCol, targetCol}) {
const columns = Object.assign({}, store.getState().tickets.tickets)
const sourceList = columns[sourceCol];
const targetList = columns[targetCol];
const sourceTicketIndex = sourceList.findIndex(ticket => ticket.id == sourceId);
const targetTicketIndex = targetList.findIndex(ticket => ticket.id == targetId);
if(sourceCol === targetCol){
var arrayClone = sourceList.slice();
arrayClone.splice(sourceTicketIndex, 1);
arrayClone.splice(targetTicketIndex, 0, sourceList[sourceTicketIndex]);
columns[sourceCol] = arrayClone;
}
return function(dispatch){
dispatch({type: "MOVE_TICKET", payload: columns});
}
}
Column.js (where each Ticket component is rendered)
import React from "react"
import uuid from "uuid"
import { connect } from "react-redux"
import ColumnsContainer from "./ColumnsContainer"
import Ticket from "./ticket"
import { moveTicket } from "../actions/ticketsActions"
#connect((store) => {
return {
columns: store.columns.columns
};
})
export default class Column extends React.Component {
console(){
console.log(this)
}
render(){
const tickets = this.props.tickets.map((ticket, id) =>
<Ticket
key = {uuid.v4()}
id={ticket.id}
summary = { ticket.summary }
assignee = { ticket.assignee }
priority = { ticket.priority }
projectName = { ticket.displayName }
onMove={ moveTicket }
status= { ticket.status }
/>
)
return(
<div key = {uuid.v4()} className = { this.props.className }>
<h2 key = {uuid.v4()}>{ this.props.title }</h2>
<ul key = {uuid.v4()}>{ tickets }</ul>
</div>
)
}
}
If anyone can see where I'm going wrong I could really use some assistance.
You are not connecting the moveTicket action to redux's dispatcher.
You'll have to do something like:
#connect((store) => {
return {
columns: store.columns.columns
};
}, {moveTicket})
export default class Column extends React.Component {
// ...
// use this.props.moveTicket instead of moveTicket
The second parameter to connect is called mapDispatchToProps, which will do the dispatch(actionFn) for you.
You might want to name the bound action differently, e.g.
#connect((store) => {
return {
columns: store.columns.columns
};
}, {connectedMoveTicket: moveTicket})
// then use this.props.connectedMoveTicket
Builder Action positionRComponent not called. Am I doing something wrong? Check out the commentLine inside moveBox function in the BuildView.js
Expecting output: to be printed in console.
Position R Component
Below are the code snippets of BuildView.js and builder-actions.js.
BuildView.js
import React, {PropTypes} from 'react';
import BuilderStore from '../stores/builder-store';
import BuilderActions from '../actions/builder-actions'
import update from 'react/lib/update';
import ItemTypes from './ItemTypes';
import RComponent from './RComponent';
import { DropTarget } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
function getViewRComponents(){
return( {components: BuilderStore.getViewRComponents()})
}
const rComponentTarget = {
drop(props, monitor, component) {
const item = monitor.getItem();
const delta = monitor.getDifferenceFromInitialOffset();
const left = Math.round(item.left + delta.x);
const top = Math.round(item.top + delta.y);
component.moveBox(item.id, left, top);
}
};
const wrapper = {
border: '1px solid grey'
}
function collect(connect, monitor){
return ({
connectDropTarget: connect.dropTarget()
})
}
class BuildView extends React.Component{
constructor(props){
super(props);
this.state = getViewRComponents();
this._onChange = this._onChange.bind(this);
}
moveBox(id, left, top) {
this.setState(update(this.state, {
components: {
[id]: {
$merge: {
left: left,
top: top
}
}
}
}));
//CALLING HERE>>> Not getting called
BuilderActions.positionRComponent.bind(null, this.state.components[id]);
}
componentWillMount(){
BuilderStore.addChangeListener(this._onChange)
}
render(){
const { hideComponentOnDrag, connectDropTarget } = this.props;
let components = this.state.components.map( (component, index) => {
return(<RComponent
key={index}
id={index}
left={component.left}
top={component.top}
hideComponentOnDrag={hideComponentOnDrag}>
{component.name}
</RComponent>);
})
return connectDropTarget(
<div>
{components}
</div>
);
}
_onChange(){
this.setState(getViewRComponents());
}
componentWillUnMount(){
BuilderStore.removeChangeListener(this._onChange())
}
}
BuildView.propTypes = {
hideComponentOnDrag: PropTypes.bool.isRequired,
connectDropTarget: PropTypes.func.isRequired
};
export default DropTarget(ItemTypes.RCOMPONENT,rComponentTarget, collect )(BuildView);
builder-actions.js
import BuilderConstants from '../constants/builder-constants';
import {dispatch, register} from '../dispatchers/builder-dispatcher';
export default {
addRComponent(component) {
console.log("Add R Component")
dispatch({
actionType: BuilderConstants.ADD_RCOMPONENT, component
})
},
removeRComponent(component){
dispatch({
actionType: BuilderConstants.REMOVE_RCOMPONENT, component
})
},
positionRComponent(component){
console.log("Position R Component");
dispatch({
actionType: BuilderConstants.POSITION_RCOMPONENT, component
})
}
}
Use call or execute the returned function from bind:
var f = BuilderActions.positionRComponent.bind(null, this.state.components[id])
f()
or:
BuilderActions.positionRComponent.call(null, this.state.components[id]);
The difference is bind doesn't execute but returns a new function with the argument list passed into the new function.
call basically does a bind then executes, apply is similar but takes an array of arguments.
Hope it helps.