I need your help.
I have an assignment, create two buttons(these buttons are initially white background).
When I click on one of them, I need both buttons to change color, using Redux.
The buttons I created. I don't know how to change the color.
Can anyone help? I would really appreciate it.
this is my code
import { COUNTER_GREEN, COUNTER_RED } from "../constants";
export const actionRed = () => ({
type: COUNTER_RED
});
export const actionGreen = () => ({
type: COUNTER_GREEN
});
=========================
import React from "react";
import { connect } from "react-redux";
import { actionGreen, actionRed } from "../../actions/counter";
const Counter = ({ actionRed, actionGreen }) => {
return (
<div>
<button onClick={actionRed}>Red</button>
<button onClick={actionGreen}>Green</button>
</div>
);
}
const mapStateToProps = () => ({});
const mapDispatchToProps = {
actionRed,
actionGreen
};
export const CounterContainer = connect(mapStateToProps,mapDispatchToProps)(Counter);
======================
const red = 'red';
const green = 'green';
const redtest = {
color: 'red'
}
export const counterReducer = (state={redtest}, action) => {
switch(action.type) {
case "COUNTER_RED": {
return {
state: {redtest}
};
}
case "COUNTER_GREEN": {
return {
state:green
};
}
default: {
return state;
}
}
};
create a buttonColor variable in your redux store. Import same variable as a prop in your component to give background to buttons. on button click dispatch an event which will change the buttonColor.
if you are not using this color property anywhere else in your application I would suggest you move it to the component state instead of redux store. If you are using functional component you can make use of hooks to update the state.
Please use hooks as below if you want to continue with functional component.
import React from "react";
import { connect } from "react-redux";
const Counter = () => {
const [btnColor, changeBtnColor] = useState('blue');
return (
<div>
<button onClick={()=>changeBtnColor('red')} style={{backgroundColor: btnColor}}>Red</button>
<button onClick={()=>changeBtnColor('green')} style={{backgroundColor: btnColor}}>Green</button>
</div>
);
}
export default Counter;
Try the below snippet if you want to switch to class-based component.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
btnColor: 'blue'
};
}
render() {
return (
<div>
<button onClick={()=>this.setState({this.state.btnColor: 'red'})} style={{backgroundColor: this.state.btnColor}}>Red</button>
<button onClick={()=>this.setState({this.state.btnColor: 'green'})} style={{backgroundColor: this.state.btnColor}}>Green</button>
</div>
);
}
}
Related
I am trying to learn Redux by simply add/delete users. I have an action 'ADD_PROFILE', with payload : name,account-number. On clicking add button, I wanted to update the store, hide the 'add user' form and show a message 'User added successfully'. If it is in React, I can have a boolean state variable, update/reset variable and switch the views. If I wanted to do the same using Redux bit not sure how.
This is what I tried :
Action
export const addProfile = (name, account_number) => {
console.log(name, account_number)
return{
type :'ADD_PROFILE',
payload : {
name : name,
account_number : account_number
}
};
}
Reducer:
export default (profilesList=[],action) => {
switch(action.type){
case 'ADD_PROFILE':
return [...profilesList, action.payload]
case 'DELETE_PROFILE':
return profilesList.filter(name => name!== action.payload.name)
default:
return profilesList;
}
}
AddView.js
import React from 'react';
import { connect } from 'react-redux';
import { addProfile } from '../actions';
class AddView extends React.Component{
constructor(props) {
super(props);
this.state={
isProfileAdded: false
};
}
addValuesView(){
return(
<div>
Name : <input type="text" value={this.props.profiles.name} ref={el => (this.nameInputRef = el)}/>
Account Number : <input type="text" value={this.props.profiles.account_number} ref={el => (this.accountInputRef = el)}/>
<button onClick={() => {
this.setState(isProfileAdded=true),
this.props.addProfile(this.nameInputRef.value,this.accountInputRef.value)
}
}>Add</button>
</div>
);
}
profileAddedView(){
return(
<div>Profile added succesfully</div>
)
}
view(){
return !this.props.profiles.isProfileAdded ? this.addValuesView() : this.profileAddedView()
}
render(){
console.log(this.state)
return this.view()
}
}
const mapStateToProps = (state) => {
return { profiles : state.profiles }
}
const mapDispatchToProps = dispatch => ({
onAddProfile: dispatch(addProfile())
});
export default connect(mapStateToProps, {addProfile}) (AddView);
App.js
import React from 'react';
import AddView from './AddView';
const App = () =>{
return (
<div className="ui container">
<AddView />
</div>
);
}
export default App;
Method this.setState should receive an object:
() => {
this.setState({ isProfileAdded: true});
this.props.addProfile(this.nameInputRef.value, this.accountInputRef.value);
}
I'm working on this project where the frontend is in React with UIkit for the user interface. The integration between the parts looks poorly implemented. I'm going to explain why. There is a Modal component, something like
export class Modal extends Component {
static getByName = name => UIkit.modal(`[data-modal-name='${name}']`)
static show = name => {
const modal = Modal.getByName(name)
if (modal) modal.show()
}
static hide = name => {
const modal = Modal.getByName(name)
if (modal) modal.hide()
}
render() {
// a modal
}
}
this is used in this way
export const LoginFormModal = props => (
<Modal name="login-form" className="login-form-modal" hideClose>
<LoginForm />
</Modal>
)
and show/hide is called programmatically where needed (even redux's actions)
Modal.hide("login-form")
this is in a Redux action, like this
export const login = credentials => {
return dispatch => {
dispatch(showLoader())
API.authentication.login(
credentials,
response => {
setCurrentUser(
Object.assign({}, response.user, { user_id: response.user.id })
)
Modal.hide("login-form")
dispatch(loginSucceded(response))
dispatch(hideLoader())
dispatch(push("/"))
dispatch(fetchNotificationsCounter())
},
error => {
dispatch(loginFailed(error))
dispatch(hideLoader())
}
)
}
}
This seems to work. Until you leave a component. When you come back to it, the second time the programmatically hide does not work anymore.
Anyone can lead me to how integrate the parts in a more react-appropriate way?
Using the parts of uikit which manipulate the dom (show, hide) is obviously hard to connect with React (and probably you shouldn't), however:
You need to move the call of the functions show and hide inside the Component by passing the bool of the state of the modal (eg. modalopen) . A good hook is the componentWillReceiveProps which can be used to check the previus props
componentWillReceiveProps(nextProps) {
if (nextProps.modalopen !== this.props.modalopen) {
if (nextProps.modalopen) {
getByName(...).show()
} else {
getByName(...).hide()
}
}
}
(this is inside the Modal class)
The thing I don't like and that is definitely not a "React-way" is that the code is mutating state directly from an action creator (!). From React docs:
For example, instead of exposing open() and close() methods on a
Dialog component, pass an isOpen prop to it.
So what if you had one modal that would be controlled by the redux state? Here is a possible implementation:
ModalWindow - will react to state changes and render depending what's in store:
import React from 'react';
import InfoContent from './InfoContent';
import YesOrNoContent from './YesOrNoContent';
import { MODAL_ACTION } from './modal/reducer';
class ModalWindow extends React.Component {
renderModalTitle = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return 'Info';
case MODAL_ACTION.YES_OR_NO:
return 'Are you sure?';
default:
return '';
}
};
renderModalContent = () => {
switch (this.props.modalAction) {
case MODAL_ACTION.INFO:
return <InfoContent />;
case MODAL_ACTION.YES_OR_NO:
return <YesOrNoContent />;
default:
return null;
}
};
render() {
return (
this.props.isModalVisible ?
<div>
<p>{this.renderTitle()}</p>
<div>
{this.renderModalContent()}
</div>
</div>
:
null
);
}
}
export default connect((state) => ({
modalAction: state.modal.modalAction,
isModalVisible: state.modal.isModalVisible,
}))(ModalWindow);
modal reducer it will expose API to show/hide modal window in the application:
export const SHOW_MODAL = 'SHOW_MODAL';
export const HIDE_MODAL = 'HIDE_MODAL';
const INITIAL_STATE = {
isModalVisible: false,
modalAction: '',
};
export default function reducer(state = INITIAL_STATE, action) {
switch (action.type) {
case SHOW_MODAL:
return { ...state, isModalVisible: true, modalAction: action.modalAction };
case HIDE_MODAL:
return { ...state, isModalVisible: false };
default:
return state;
}
}
export const MODAL_ACTION = {
YES_OR_NO: 'YES_OR_NO',
INFO: 'INFO',
};
const showModal = (modalAction) => ({ type: SHOW_MODAL, modalAction });
export const hideModal = () => ({ type: HIDE_MODAL });
export const showInformation = () => showModal(MODAL_ACTION.INFO);
export const askForConfirmation = () => showModal(MODAL_ACTION.YES_OR_NO);
So basically you expose simple API in form of redux action-creators to control the state of your ModalWindow. Which you can later use like:
dispatch(showInformation())
...
dispatch(hideModal())
Of course, there could be more to it like optional configuration that would be passed to action creators or queue for modals.
I use a combination of a hook and a component for this.
Hook:
import { useState } from "react";
import UIkit from "uikit";
export default function useModal() {
const [isOpen, setIsOpen] = useState(false);
const [ref, setRef] = useState(null);
const open = (e) => {
UIkit.modal(ref).show();
setIsOpen(true);
};
const close = (e) => {
UIkit.modal(ref).hide();
UIkit.modal(ref).$destroy(true);
setIsOpen(false);
};
return [setRef, isOpen, open, close];
}
Component:
import React, { forwardRef } from "react";
const Modal = forwardRef(({ children, isOpen, full, close }, ref) => (
<div
ref={ref}
data-uk-modal="container: #root; stack: true; esc-close: false; bg-close: false"
className={`uk-flex-top ${full ? "uk-modal-container" : ""}`}
>
<div className="uk-modal-dialog uk-margin-auto-vertical">
<button
type="button"
className="uk-modal-close-default"
data-uk-icon="close"
onClick={close}
/>
{isOpen && children()}
</div>
</div>
));
export default Modal;
Consumption:
function Demo() {
const [ref, isOpen, open, close] = useModal();
return (
<div>
<button
type="button"
className="uk-button uk-button-default"
onClick={open}
>
upgrade
</button>
<Modal isOpen={isOpen} close={close} ref={ref} full>
{() => (
<div>
<div className="uk-modal-header">
<h2 className="uk-modal-title">title</h2>
</div>
<div className="uk-modal-body">
body
</div>
</div>
)}
</Modal>
</div>
);
}
Read more: https://reactjs.org/docs/integrating-with-other-libraries.html
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.
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))
}
};
})
If I have a simple react component that records a click count for a button and on each click records a new history state without changing the URL. When the user clicks back how do I restore the state to as it was?
I can do as it is here using the native JavaScript history object, but it fails when the user transitions back to the first state and back from a different component into the last state of this one.
I suspect that there is a better to do this using react-router (1.0)?
import React, { Component } from 'react';
export default class Foo extends Component {
state = {
clickCount: 0,
};
componentWillMount() {
window.onpopstate = (event) => {
if (event.state.clickCount) {
this.setState({ clickCount: event.state.clickCount });
}
};
}
onClick() {
const newClickCount = this.state.clickCount + 1;
const newState = { clickCount: newClickCount };
this.setState(newState);
history.pushState(newState, '');
}
render() {
return (
<div>
<button onClick={this.onClick.bind(this)}>Click me</button>
<div>Clicked {this.state.clickCount} times</div>
</div>
);
}
}
localStorage or even cookies are options, but probably not the best way. You should store the count in a database, this way you can set the initial state in your constructor to the last value saved in the database.
Another option, if you only need to persist the count on the client-side(and not in a database) is using a closure.
// CountStore.js
var CountStore = (function() {
var count = 0;
var incrementCount = function() {
count += 1;
return count;
};
var getCount = function() {
return count;
};
return {
incrementCount: incrementCount,
getCount: getCount
}
})();
export default CountStore;
So your code would change to the below.
import React, { Component } from 'react';
import CountStore from './CountStore';
export default class Foo extends Component {
state = {
clickCount: CountStore.getCount()
};
componentWillMount() {
window.onpopstate = (event) => {
if (event.state.clickCount) {
this.setState({ clickCount: event.state.clickCount });
}
};
}
onClick() {
const newClickCount = CountStore.incrementCount();
const newState = { clickCount: newClickCount };
this.setState(newState);
}
render() {
return (
<div>
<button onClick={this.onClick.bind(this)}>Click me</button>
<div>Clicked {this.state.clickCount} times</div>
</div>
);
}
}
There may be a cleaner way of using react-router, but this is an option.
An example:
import React, {Component} from "react";
import {NavLink} from "react-router-dom";
interface Props {
}
interface State {
count: number
}
export default class About extends Component<Props, State> {
UNSAFE_componentWillMount(): void {
this.setState(Object.getPrototypeOf(this).constructor.STATE || {});
}
componentWillUnmount(): void {
Object.getPrototypeOf(this).constructor.STATE = this.state;
}
constructor(props: Props) {
super(props);
this.state = {count: 0}
}
render() {
const {count} = this.state;
return <div style={{width: "100%", height: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "space-evenly", fontSize: "2em"}}>
<span>Count: {count}</span>
<button onClick={() => this.setState({count: count + 1})}>PLUS ONE</button>
<NavLink to="/">Redirect to HOME</NavLink>
</div>
}
}