ReactJS not re-rendering after context changed - reactjs

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.

Related

change state in component with redux

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.`

A question about structure of class component in React

As we know, the structure of a class component can be simplified as the following:
// Blank 1
class Books extends Component {
// Blank 2
render(){
// Blank 3
return()
}
export default Books;
So just for example:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { updateFilters } from '../../../services/filters/actions';
import Checkbox from '../../Checkbox';
import GithubStarButton from '../../github/StarButton';
import './style.scss';
const availableSizes = ['XS', 'S', 'M', 'ML', 'L', 'XL', 'XXL'];
class Filter extends Component {
static propTypes = {
updateFilters: PropTypes.func.isRequired,
filters: PropTypes.array
};
componentWillMount() {
this.selectedCheckboxes = new Set();
}
toggleCheckbox = label => {
if (this.selectedCheckboxes.has(label)) {
this.selectedCheckboxes.delete(label);
} else {
this.selectedCheckboxes.add(label);
}
this.props.updateFilters(Array.from(this.selectedCheckboxes));
};
createCheckbox = label => (
<Checkbox
classes="filters-available-size"
label={label}
handleCheckboxChange={this.toggleCheckbox}
key={label}
/>
);
createCheckboxes = () => availableSizes.map(this.createCheckbox);
render() {
return (
<div className="filters">
<h4 className="title">Sizes:</h4>
{this.createCheckboxes()}
<GithubStarButton />
</div>
);
}
}
const mapStateToProps = state => ({
filters: state.filters.items
});
export default connect(
mapStateToProps,
{ updateFilters }
)(Filter);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fetchProducts } from '../../services/shelf/actions';
import { addProduct } from '../../services/cart/actions';
import Product from './Product';
import Filter from './Filter';
import ShelfHeader from './ShelfHeader';
import Clearfix from '../Clearfix';
import Spinner from '../Spinner';
import './style.scss';
class Shelf extends Component {
static propTypes = {
fetchProducts: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
addProduct: PropTypes.func.isRequired,
filters: PropTypes.array,
sort: PropTypes.string
};
state = {
loading: false
};
componentWillMount() {
const { filters, sort } = this.props;
this.handleFetchProducts(filters, sort);
}
componentWillReceiveProps(nextProps) {
const { filters: nextFilters, sort: nextSort } = nextProps;
if (nextFilters !== this.props.filters) {
this.handleFetchProducts(nextFilters, undefined);
}
if (nextSort !== this.props.sort) {
this.handleFetchProducts(undefined, nextSort);
}
}
handleFetchProducts = (
filters = this.props.filters,
sort = this.props.sort
) => {
this.setState({ loading: true });
this.props.fetchProducts(filters, sort, () => {
this.setState({ loading: false });
});
};
render() {
const { products } = this.props;
const p = products.map(p => {
return (
<Product product={p} addProduct={this.props.addProduct} key=
{p.id} />
);
});
return (
<React.Fragment>
{this.state.loading && <Spinner />}
<Filter />
<div className="shelf-container">
<ShelfHeader productsLength={products.length} />
{p}
<Clearfix />
</div>
<Clearfix />
</React.Fragment>
);
}
}
const mapStateToProps = state => ({
products: state.shelf.products,
filters: state.filters.items,
sort: state.sort.type
});
export default connect(
mapStateToProps,
{ fetchProducts, addProduct }
)(Shelf);
Except for state and life cycle methods, sometimes we define other types of attributes and functions in Blank 1, sometimes in Blank 2, sometimes in Blank 3. So I am wondering when we are going to define attributes and functions, which part should we choose? Is there a convention or something like that?
Block 1 is for defining variables and functions which are not depended on component ,these are general variables and functions which could be used in the component and can even be exported in another files.
Block 2 is for defining component specific variables and methods, define lifecycle methods.variables and methods defined in block 2 could be accessed using this keyword.
Block 3 is used when we want to execute certain piece of code,every time when render method is executed.Apart from initial render, render method is executed every time when setState is performed,so avoid writing code in block 3 as it's excessive.
Hope this helps,
Cheers !!

Connect() is not passing state from store to child

I am "new to react". I am working on a project where I am creating three buttons named as India, China, Russia. On button click, text of paragraph changes.
For this, I have created 4 Presentational Components, 3 actions, 1 reducer and extra reducer for initial state.
I am trying to send text to paragraph, from store to Presentational Component via connect(). However, it's not working.
My code is as following:
index.js
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './reducers';
import App from './components/App';
const store = createStore(rootReducer);
console.log(store.getState());
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
actions/index.js
export const india = text => ({
type: 'INDIA',
text
});
export const china = text => ({
type: 'CHINA',
text
});
export const russia = text => ({
type: 'RUSSIA',
text
});
reducers/country.js
const initialState = {
text: 'ABC',
isIClicked: false,
isCClicked: false,
isRClicked: false
};
const country = (state = initialState, action) => {
switch (action.type) {
case 'INDIA':
return {
text: action.text,
isIClicked: true,
isCClicked: false,
isRClicked: false
};
case 'CHINA':
return {
text: action.text,
isIClicked: false,
isCClicked: true,
isRClicked: false
};
case 'RUSSIA':
return {
text: action.text,
isIClicked: false,
isCClicked: false,
isRClicked: true
};
default:
return state;
}
};
export default country;
components/IndiaBtn.js
import React from 'react';
const IndiaBtn = ({ isIClicked, onClick }) => {
return (
<button
onClick={onClick}
style={{
color: isIClicked ? 'white' : 'black',
backgroundColor: isIClicked ? 'blue' : 'white'
}}
>
India
</button>
);
};
export default IndiaBtn;
components/ChinaBtn.js
import React from 'react';
const ChinaBtn = ({ isCClicked, onClick }) => {
return (
<button
onClick={onClick}
style={{
color: isCClicked ? 'white' : 'black',
backgroundColor: isCClicked ? 'blue' : 'white'
}}
>
China
</button>
);
};
export default ChinaBtn;
components/RussiaBtn.js
import React from 'react';
const RussiaBtn = ({ isRClicked, onClick }) => {
return (
<button
onClick={onClick}
style={{
color: isRClicked ? 'white' : 'black',
backgroundColor: isRClicked ? 'blue' : 'white'
}}
>
Russia
</button>
);
};
export default RussiaBtn;
components/display.js
import React from 'react';
const display = ({ text }) => {
return <div style={{ padding: '16px' }}>{text}</div>;
};
export default display;
components/App.js
import React from 'react';
import IndiaBtnContainer from '../containers/IndiaBtnContainer';
import ChinaBtnContainer from '../containers/ChinaBtnContainer';
import RussiaBtnContainer from '../containers/RussiaBtnContainer';
import DisplayContainer from '../containers/DisplayContainer';
const App = () => {
return (
<div>
<div>
<span><IndiaBtnContainer /></span>
<span><ChinaBtnContainer /></span>
<span><RussiaBtnContainer /></span>
</div>
<div>
<DisplayContainer />
</div>
</div>
);
};
export default App;
containers/IndiaBtnContainer.js
import { connect } from 'react-redux';
import IndiaBtn from '../components/IndiaBtn';
import { india } from '../actions';
const mapStateToProps = state => ({
isIClicked: state.isIClicked
});
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(india('india'))
});
export default connect(mapStateToProps, mapDispatchToProps)(IndiaBtn);
containers/ChinaBtnContainer.js
import { connect } from 'react-redux';
import ChinaBtn from '../components/ChinaBtn';
import { china } from '../actions';
const mapStateToProps = state => ({
isCClicked: state.isCClicked
});
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(china('china'))
});
export default connect(mapStateToProps, mapDispatchToProps)(ChinaBtn);
containers/RussiaBtnContainer.js
import { connect } from 'react-redux';
import RussiaBtn from '../components/RussiaBtn';
import { russia } from '../actions';
const mapStateToProps = state => ({
isCClicked: state.isCClicked
});
const mapDispatchToProps = dispatch => ({
onClick: () => dispatch(russia('russia'))
});
export default connect(mapStateToProps, mapDispatchToProps)(RussiaBtn);
containers/DisplayContainer.js
import { connect } from 'react-redux';
import display from '../components/display';
const mapStateToProps = state => ({
text: state.text
});
export default connect(mapStateToProps)(display);
Note:
Sorry, for long code. But, I thought it is necessary to understand problem
Focus on Container Components, connect, mapStateToProps, mapDispatchToProps. According to me, problem must be there.
Your reducer returns an array and hence mapStateToProps isn't giving you right values since you expect the state to be an object, what you need is
const initialState = {
text: '',
isIClicked: false,
isCClicked: false,
isRClicked: false,
}
const country = (state=initialState,action) => {
switch (action.type) {
case 'INDIA':
return {
text: action.text,
isIClicked: true,
isCClicked: false,
isRClicked: false,
}
case 'CHINA':
return {
text: action.text,
isIClicked: false,
isCClicked: true,
isRClicked: false,
}
case 'RUSSIA':
return {
text: action.text,
isIClicked: false,
isCClicked: false,
isRClicked: true,
}
default:
return state
}
export default country

Redux not Re-rendering React components even though store is updated

Hi I'm new to Redux and I'm using React and Redux to try to build a UI where I can drag and drop files (invoices in this case) into a portion of the UI, render them in a list and then be able to launch a popover to edit the metadata associated with each invoice. Dragging and dropping is all working fine - Redux is re-rendering the view each time a file is dropped and the list is being updated. However, when I try an click the edit button against each invoice the store is being updated but the props in my popover component are not. Indeed, it doesn't look like any re-rendering is happening at all when I attempt to click the edit invoice button
App.js
import React from 'react'
import AddInvoice from '../containers/AddInvoice'
import CurrentInvoiceList from '../containers/CurrentInvoiceList'
import ControlPopover from '../containers/ControlPopover'
const App = () => (
<div>
<AddInvoice />
<CurrentInvoiceList />
<ControlPopover />
</div>
)
export default App
containers/AddInvoice.js
import React from 'react'
import { connect } from 'react-redux'
import { addInvoice } from '../actions'
const recipientDataDefaults = {
name: '',
surname: '',
address: '',
phone: ''
};
const handleDragOver = event => {
event.stopPropagation();
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
};
const handleDragEnter = event => {
event.stopPropagation();
event.preventDefault();
};
const handleDragLeave = event => {
event.stopPropagation();
event.preventDefault();
};
let AddInvoice = ({ dispatch }) =>
const styles = {'minHeight': '200px', 'background': 'tomato'}
return (
<div style={styles}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
onDrop={event => {
event.stopPropagation();
event.preventDefault();
const data = event.dataTransfer;
const files = data.files;
const newInvoiceUploads = Object.keys(files)
.map(key => files[key])
.map(file => {
const invoiceObject = {};
invoiceObject.files = [file];
invoiceObject.recipientData = Object.assign({}, recipientDataDefaults);
return invoiceObject;
});
newInvoiceUploads.forEach(invoice => dispatch(addInvoice(invoice)))
}}>
Drag an invoice here to upload
</div>
)
}
AddInvoice = connect()(AddInvoice)
export default AddInvoice
containers/ControlPopover.js
import { connect } from 'react-redux'
import { closePopoverWithoutSave } from '../actions'
import Popover from '../components/Popover/Popover'
const mapStateToProps = (state) => {
return {
isActive: !!state.isActive
}
}
const mapDispatchToProps = {
handleCancel: closePopoverWithoutSave
}
const ControlPopover = connect(
mapStateToProps,
mapDispatchToProps
)(Popover)
export default ControlPopover
containers/CurrentInvoiceList.js
import { connect } from 'react-redux'
import { showInvoiceEditPopover } from '../actions'
import InvoiceList from '../components/InvoiceList/InvoiceList'
const mapStateToProps = state => {
return {
invoices: state.invoices
}
}
const mapDispatchToProps = dispatch => ({
handleEditInvoice: invoice => {
dispatch(showInvoiceEditPopover(invoice))
}
})
const CurrentInvoiceList = connect(
mapStateToProps,
mapDispatchToProps
)(InvoiceList)
export default CurrentInvoiceList
actions/index.js
let nextInvoiceId = 0
export const addInvoice = invoice => ({
type: 'ADD_INVOICE',
id: nextInvoiceId++,
invoiceData: invoice
})
export const showInvoiceEditPopover = invoice => ({
type: 'SHOW_POPOVER',
invoice
})
The popover reducer (combined in app but inlined here for brevity) reducers/index.js
const popover = (state = {}, action) => {
switch (action.type) {
case 'SHOW_POPOVER':
const popoverState = {}
popoverState.isActive = true
popoverState.data = action.invoice
return popoverState
case 'CLOSE_POPOVER_WITHOUT_SAVING':
const inactiveState = {}
inactiveState.isActive = false
inactiveState.data = {}
return inactiveState;
default:
return state
}
}
export default popover
components/InvoiceList.js
import React from 'react'
import PropTypes from 'prop-types'
import Invoice from '../Invoice/Invoice'
const InvoiceList = ({ invoices, handleEditInvoice }) => {
return (
<div>
{invoices.map(invoice =>
<Invoice
key={invoice.id}
invoice={invoice.invoiceData}
onClick={event => {
// here we invoke the action bound by the CurrentInvoiceList
// container
event.preventDefault()
handleEditInvoice(invoice)
}}
/>
)}
</div>
)
}
InvoiceList.propTypes = {
invoices: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
invoiceData: PropTypes.object
}).isRequired).isRequired,
handleEditInvoice: PropTypes.func.isRequired
}
export default InvoiceList
components/Invoice.js
import React from 'react'
import PropTypes from 'prop-types'
import TextInput from '../TextInput/TextInput';
import Button from '../Button/Button';
import './invoice.css'
const Invoice = ({ invoice, onClick }) => {
const fileNames = invoice.files.map((file, index) => {
return (<div key={index} className="invoice__file-title-legend">
{file.name}</div>);
});
return (
<div className="invoice">
<form className="invoice__form">
<TextInput id="invoice__input-amount" placeholder="enter invoice amount" label="Invoice Amount" />
<TextInput id="invoice__input-target" placeholder="enter payment target" label="Payment Target" />
<Button value="Add recipient" onClick={onClick} /> // clicking this button updates the store but does NOT re-render. Why?
</form>
<div className="invoice__files">{fileNames}</div>
</div>
)
}
Invoice.propTypes = {
onClick: PropTypes.func.isRequired,
invoice: PropTypes.object
}
export default Invoice
components/Popover/Popover.js
import React from 'react'
import PropTypes from 'prop-types'
import createModifiers from '../../lib/createModifiers';
import './Popover.css';
const Popover = ({ handleCancel, isActive }) => {
console.log('rendering popover component') // does not get called when invoice edit button is pressed
const popoverModifiers = createModifiers('popover', {
'is-active': isActive
})
return (
<div className={popoverModifiers}>
<div className="popover__header">
<button onClick={handleCancel}>x</button>
</div>
<div className="popover__content">
Popover content
</div>
</div>
)
}
Popover.propTypes = {
handleCancel: PropTypes.func.isRequired,
isActive: PropTypes.bool.isRequired
}
export default Popover
And finally the createModifiers Code for posterity. This code is merely producing some BEM modifier CSS classes based on a state boolean value passed in
const createModifiers = (element, modifiers) => {
const classList = Object.keys(modifiers)
.filter(key => modifiers[key])
.map(modifier => `${element}--${modifier}`)
.concat(element)
.join(' ')
return classList
}
export default createModifiers
I know this is a large amount of example code so I tried to keep it a brief and focused as possible whilst giving a comprehensive view of the application. Any help is most appreciated.
The problem is in containers/ControlPopover.js and the mapStateToProps function. The isActive property needs to be assigned to state.popover.isActive
I believe your problem is your mapDispatchToProp functions are not formatted properly.
You need to return an object that has methods. Those methods are what will be given to your connected component as props.
Example:
const mapDispatchToProps = ( dispatch ) => {
return {
doSomething: ( arguments ) => {
// here you can dispatch and use your arguments
}
};
}
doSomething is the prop that would be provided to the connected component.
All of your mapDispatchToProps functions are formatted improperly.
SIDE NOTE / OPINION - TLDR:
In the future if you have a lot of code to post, I believe it would be easier to digest if the pieces were linked together.
I.E.
// App.js
const App = () => (
<div>
<Header />
<Body />
<Footer />
</div>
);
The components appear in the order: header -> body -> footer. Provide the code for them in that order, with their actions, reducer, presentational, and container information in one block.
Header
// header.presentational.js ...
// header.container.js ... ( or where you mapStateToProps and connect )
// header.actions.js ...
// header.reducer.js ...
Body ...
Footer ...
I don't know if the code is different on your end, but your mapStateToDispatch function is still improperly formatted.
Change this...
const mapDispatchToProps = dispatch => ({
handleEditInvoice: invoice => {
dispatch(showInvoiceEditPopover(invoice))
}
})
To this:
const mapDispatchToProps = dispatch => ({
return {
handleEditInvoice: invoice => {
dispatch(showInvoiceEditPopover(invoice))
}
};
})

Implementing React Redux

I am slowly learning React and also learning to implement it with Redux. But I seem to have hit a road block. So this is what I have so far.
/index.jsx
import './main.css'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App.jsx'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import ShoppingList from './reducers/reducer'
let store = createStore(ShoppingList)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
/actions/items.js
import uuid from 'node-uuid'
export const CREATE_ITEM = 'CREATE_ITEM'
export function createItem(item) {
return {
type: CREATE_ITEM,
item: {
id: uuid.v4(),
item,
checked: false
}
}
}
/reducers/reducer.js
import * as types from '../actions/items'
import uuid from 'node-uuid'
const initialState = []
const items = (state = initialState, action) => {
switch (action.type) {
case types.CREATE_ITEM:
return {
id: uuid.v4(),
...item
}
default:
return state;
}
}
export default items
/reducers/index.js
UPDATE:
import { combineReducers } from 'redux'
import items from './reducer'
const ShoppingList = combineReducers({
items
})
export default ShoppingList
/components/Item.jsx
import React from 'react';
import uuid from 'node-uuid'
export default class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
isEditing: false
}
}
render() {
if(this.state.isEditing) {
return this.renderEdit();
}
return this.renderItem();
}
renderEdit = () => {
return (
<input type="text"
ref={(event) =>
(event ? event.selectionStart = this.props.text.length : null)
}
autoFocus={true}
defaultValue={this.props.text}
onBlur={this.finishEdit}
onKeyPress={this.checkEnter}
/>
)
};
renderDelete = () => {
return <button onClick={this.props.onDelete}>x</button>;
};
renderItem = () => {
const onDelete = this.props.onDelete;
return (
<div onClick={this.edit}>
<span>{this.props.text}</span>
{onDelete ? this.renderDelete() : null }
</div>
);
};
edit = () => {
this.setState({
isEditing: true
});
};
checkEnter = (e) => {
if(e.key === 'Enter') {
this.finishEdit(e);
}
};
finishEdit = (e) => {
const value = e.target.value;
if(this.props.onEdit) {
this.props.onEdit(value);
this.setState({
isEditing: false
});
}
};
}
/components/Items.jsx
import React from 'react';
import Item from './Item.jsx';
export default ({items, onEdit, onDelete}) => {
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
text={item.text}
onEdit={onEdit.bind(null, item.id)}
onDelete={onDelete.bind(null, item.id)}
/>
</li>
)}</ul>
);
}
// UPDATE: http://redux.js.org/docs/basics/UsageWithReact.html
// Is this necessary?
const mapStateToProps = (state) => {
return {
state
}
}
Items = connect(
mapStateToPros
)(Items) // `SyntaxError app/components/Items.jsx: "Items" is read-only`
//////////////////////////////////////
// Also tried it this way.
//////////////////////////////////////
Items = connect()(Items)
export default Items // same error as above.
Tried this as well
export default connect(
state => ({
items: store.items
})
)(Items) // `Uncaught TypeError: Cannot read property 'items' of undefined`
UPDATE:
After many attempts #hedgerh in Gitter pointed out that it should be state.items instead. so the solution was
export default connect(
state => ({
items: state.items
})
)(Items)
credits to #azium as well.
/components/App.jsx
export default class App extends React.Component {
render() {
return (
<div>
<button onClick={this.addItem}>+</button>
<Items />
</div>
);
}
}
What am I missing here in order to implement it correctly? Right now it breaks saying that Uncaught TypeError: Cannot read property 'map' of undefined in Items.jsx. I guess it makes sense since it doesn't seem to be hooked up correctly. This is the first part of the app, where the second will allow an user to create a many lists, and these lists having many items. I will probably have to extract the methods from Item.jsx since the List.jsx will do pretty much the same thing. Thanks
You're missing connect. That's how stuff gets from your store to your components. Read the containers section from the docs http://redux.js.org/docs/basics/UsageWithReact.html
import React from 'react'
import Item from './Item.jsx'
import { connect } from 'react-redux'
let Items = ({items, onEdit, onDelete}) => {
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
text={item.text}
onEdit={onEdit.bind(null, item.id)}
onDelete={onDelete.bind(null, item.id)}
/>
</li>
})
</ul>
)
}
export default connect(
state => ({
items: state.items
})
)(Items)
Also you seem to be expecting onEdit and onDelete functions passed from a parent but you're not doing that so those functions will be undefined.

Resources