I have a component documents with a button, which onClick calls an action createDocument. The action returns a payload (on success) with id of the new document and then we have to route to the newly created document route /documents/:id
Now the way I have set it up right now, is not by using the Redux state at all, but simply by using the promise in the component to get the action and then making the route change.
import React, { Component } from 'react';
import { browserHistory } from 'react-router';
import { connect } from 'react-redux';
import { createDocument } from 'actions/documents';
#connect(null, { createDocument })
export default class Documents extends Component {
handleCreateClick () {
this.props.createDocument().then((action) => {
const { payload } = action;
browserHistory.push(`documents/${payload.id}`);
})
}
render () {
return (
<div>
<button onClick={this.handleCreateClick}>
Create
</button>
</div>
)
}
}
But again: this is not using the Redux state to handle errors, or anything. What would be the most "standard" way of handling such situation in Redux? I feel like putting the "new" id on the state would be overkill and way more boilerplate code.
Related
This question already has answers here:
How do I access store state in React Redux?
(8 answers)
Closed 1 year ago.
I've created the reducer and using it to change the state of my store. but as you can see in App.js whenever I click on button and update the state. it updates. I can see it in console. but component does not update. as you can see I have list of tracks there it is not updating. and if I make any changes to code because of that the component re-render I can see the new state after that. why is it not rendering automatically whenever the state updates.
Action
import * as actions from './actionTypes'
export const trackAdded = (title, artist, audioSrc, img) => {
return {
type: actions.TRACK_ADDED,
payload: {
title,
artist,
audioSrc,
img
}
}
}
Reducer
import * as actions from './actionTypes'
export default function reducer(state = [], action) {
switch (action.type) {
case actions.TRACK_ADDED:
return [
...state,
{
title: action.payload.title,
artist: action.payload.artist,
audioSrc: action.payload.audioSrc,
img: action.payload.img
}
]
default:
return state
}
}
App.js
import './App.css';
import store from './store'
import { trackAdded } from './actions'
function App() {
const add = (title) => {
store.dispatch(trackAdded(title, "Taylor Swift", "src", "image"))
console.log(store.getState())
}
return (
<div className="App">
{store.getState().map((track, track_id) => {
return (
<li key={track_id}>{track.title}</li>
)
})}
<button onClick={() => { add("Shake It Off") }}>Add Track</button>
</div>
);
}
export default App;
The component will not update because the store.getState() inside of <div className="App"> will only run once when the component is called. There is no logic that exists that tells the component to rerun the store.getState(). If you want the component to receive updates when the store's state changes, you need to connect it to the store using react-redux's connect function or a useSelector hook.
As an example, if using the connect function, you can map the redux state to a react component's props. So if the component's props change, then the component will "react" to it's props changing. The mapping of redux's state to the components props happens in the mapStateToProps function, returning a prop tracks that is mapped to the component. Otherwise there is no reason for the component to update. Also note: in the example below, the store is connected to React through a Provider component, providing the store to child components that wish to connect to it.
import { Provider, connect } from 'react-redux'
import './App.css';
import store from './store'
import { trackAdded } from './actions'
function App(props) {
const add = (title) => {
store.dispatch(trackAdded(title, "Taylor Swift", "src", "image"))
console.log(store.getState())
}
return (
<Provider store={store}>
<div className="App">
{props.tracks.map((track, track_id) => {
return (
<li key={track_id}>{track.title}</li>
)
})}
<button onClick={() => { add("Shake It Off") }}>Add Track</button>
</div>
</Provider>
);
}
const mapStateToProps = (state) => {
const tracks = state
return { tracks }
}
export default connect(mapStateToProps)(App);
Having said that, if you go the connect route, you might want to add a mapDispatchToProps function to the connect so you dont pass around the store everywhere. You can find that info in the redux docs, but that would be an answer to a different question.
This is happening because your component does not know that store has been updated, you can use something like this
useEffect(() => {
this.artistList = store.getState();
}, [store.getState()]); // kind of watcher of store.getState()
but this is definitely not recommended. You would want to use useSelector() from react-redux, which is much concise and recommended way to doing it.
I'm working on a React app where we are rendering an unknown number of form elements passed in from another application. I have code setup to pull in all possible components and dynamically render each with props when it's needed (below), but every time I make any change (i.e. entering text in a text field, clicking a checkbox, etc.), every form element is updated. I'm unsure how to get around this because the rendering is done inside a map function, and I can't get rid of the map function because we have no idea what or how many elements will be sent in to be rendered. I am using redux, so can't adequately use shouldComponentUpdate, but I have used the {pure: true} option in connect, with no luck. Any ideas on how to do this?
import React from 'react';
import {
InputBox,
RadioGroup,
ComboBox,
CheckboxGroup,
DatePicker
} from './components';
import {connect} from "react-redux";
import * as actions from 'redux/actions/actions';
import {convertToVariable} from 'components/Utility/utils'
const componentLookup = {
"InputBox": InputBox,
"RadioGroup": RadioGroup,
"ComboBox": ComboBox,
"CheckboxGroup": CheckboxGroup,
"DateField": DatePicker
};
class FormPanel extends React.Component {
constructor(props) {
super(props);
this.logData = this.logData.bind(this);
}
logData(eventValues) {
this.props.updateDialogValues(eventValues);
}
render() {
return (
<div>
{this.props.uiContent.map((element) => {
const Tag = componentLookup[element.type];
return (
<Tag
{...element.defaults}
onValueChange={this.logData}
key={element.defaults.label}
name={element.defaults.variable || convertToVariable(element.defaults.label)}
/>
)
})}
</div>
);
}
}
function mapStateToProps(state) {
return {
dialogValues: state.app.dialogValues,
actionInfo: state.xyz.actionInfo,
actionPayload: state.xyz.actionPayload
}
}
function mapDispatchToProps(dispatch) {
return {
updateDialogValues: (dialogValues) => {
dispatch(actions.updateDialogValues(dialogValues))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps, undefined, {pure: true})(FormPanel)
I'm trying to figure out how to user the reducers with and inside my React-Component.
My goal is pretty easy - at least i thought so: I want to toggle a Drawer-Menu. I know I can solve this with React-Only, but I want to learn Redux.
So, I've got a Component…
import React, { Component } from 'react';
class Example extends Component {
// ???
render() {
return (
<button className="burgerbutton" onClick={this.toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
);
}
}
export default Example;
also a Reducer
const initialState = {
buttonstate: false
};
const example = (state = initialState, action) => {
switch (action.type) {
case 'TOGGLE_BTN':
return Object.assign({}, state, {
buttonstate: !state.buttonstate
})
default:
return state
}
}
export default example
and an Action (although I don't know where to put that since it's so simple)
export const toggleDrawer = () => {
return {
type: 'TOGGLE_DRAWER'
}
}
I read a lot of tutorials and most of them want me to seperate between "Presentational Components" and "Container Components". I can't really see how these concepts apply here.
So what do I have to do to do to make this work? Am I looking at this problem from the right angle or do I need 12 "Container Components" to solve this?
I really hope this question makes sense at all and/or is not a duplicate!
In redux you have to dispatch action to update reducer state. So normally a component is connected to the redux store and communication is done through dispatch.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { toggleDrawer } from 'action file location';
class Example extends Component {
toggleDrawerHandler() {
this.props.dispatch(toggleDrawer())
}
render() {
// access button state from this.props.buttonstate
return (
<button className="burgerbutton" onClick={this.toggleDrawerHandler.bind(this)}</button>
<div className="drawerMenu isvisible" ></div>
);
}
}
export default connect((store) => {buttonstate: store.buttonstate})(Example);
First, I'm really enjoying using redux "ducks" which is basically a redux reducer bundle. You put your reducer, action constants, and action creators in one file (called a duck). Then you may have multiple ducks for different modules or pieces of state that you'd then combine with combineReducers.
While #duwalanise has the right idea, I'd rather see the second param of connect() be used to directly map the action to dispatch (and there's a good shortcut for it) instead of having to use this.props.dispatch
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { toggleDrawer } from './duck';
class Example extends Component {
render() {
const { buttonstate, togglerDrawer } = this.props;
return (
<div>
<button className="burgerbutton" onClick={toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
</div>
);
}
}
const mapStateToProps = (state) => ({
buttonstate: state.buttonstate,
});
export default connect(mapStateToProps, { toggleDrawer })(Example);
One side note, if you have a handler method in your component, it's better to do .bind(this) inside the constructor instead of using an arrow function or .bind(this) inside the event, ie don't do this onClick={() => /* do something */ } or this onClick={this.myHandler.bind(this)} This is an interesting (and long) read on it.
To touch on the Container vs Presentational Component piece: The idea would be to put all of your logic, handlers, redux actions etc into your containers, and pass that through props to your simple (hopefully stateless/pure function) presentational components. Technically, your component the way it's written could be turned into a stateless component:
const Example = ({ buttonstate, togglerDrawer }) => (
<div>
<button className="burgerbutton" onClick={toggleDrawer}</button>
<div className="drawerMenu isvisible" ></div>
</div>
);
I've gone through many of the Redux and ReactJS tuts. I understand setting actions => action creators => dispatch => store => render view (uni-directional flow) with more data substantial events. My problem is dealing with very simple events that change state. I know not all state always needs to be handled in Redux, and that local state events (set on React components) is an acceptable practice. However, technically Redux can handle all state events and this is what I am trying to do.
Here is the issue. I have a React component that renders a Button. This Button has an onClick event that fires a handleClick function. I set the state of the Button via the constructor method to isActive: false. When handleClick fires, setState sets isActive: true. The handleClick method also runs two if statements that, when either evaluate to true, run a function that either changes the background color of the body or the color of paragraph text. Clicking the same button again sets state back to false and will change back the body color or text color to the original value. This Button component is created twice within a separate component, Header. So long story short, I've got two buttons. One changes body color, the other changes p tag color after a click event.
Here's the code for the Button component:
import React, {Component} from 'react';
import {dimLights, invertColor} from '../../../actions/headerButtons';
import { connect } from 'react-redux';
import { Actions } from '../../../reducers/reducer';
const headerButtonWrapper = 'headerButton';
const headerButtonContext = 'hb--ctrls ';
const dimmedLight = '#333333';
const invertedTextColor = '#FFFFFF';
export default class Button extends Component {
constructor (props) {
super(props)
this.state = {
isActive: false
};
}
handleClick (e) {
e.preventDefault();
let active = !this.state.isActive;
this.setState({ isActive: active });
if(this.props.label === "Dim The Lights"){
dimLights('body', dimmedLight);
}
if(this.props.label === "Invert Text Color"){
invertColor('p', invertedTextColor)
}
}
render() {
let hbClasses = headerButtonContext + this.state.isActive;
return (
<div className={headerButtonWrapper}>
<button className={hbClasses} onClick={this.handleClick.bind(this)}>{this.props.label}</button>
</div>
);
}
}
Here's the code for the imported functions that handle changing the colors:
export function dimLights(elem, color) {
let property = document.querySelector(elem);
if (property.className !== 'lightsOn') {
property.style.backgroundColor = color;
property.className = 'lightsOn'
}
else {
property.style.backgroundColor = '#FFFFFF';
property.className = 'lightsOff';
}
}
export function invertColor(elem, textColor) {
let property = document.querySelectorAll(elem), i;
for (i = 0; i < property.length; ++i) {
if (property[i].className !== 'inverted') {
property[i].style.color = textColor;
property[i].className = 'inverted'
} else {
property[i].style.color = '#3B3B3B';
property[i].className = 'notInverted';
}
}
}
Here's the code for the reducers:
import * as types from '../constants/ActionTypes';
const initialState = {
isActive: false
};
export default function Actions(state = initialState, action) {
switch (action.type) {
case types.TOGGLE_LIGHTS:
return [
...state,
{
isActive: true
}
]
default:
return state
}
}
Here's the code for the actions:
import EasyActions from 'redux-easy-actions';
export default EasyActions({
TOGGLE_LIGHTS(type, isActive){
return {type, isActive}
}
})
If it helps, here's the Header component that renders two Button components:
import React, {Component} from 'react';
import Button from './components/Button';
const dimmer = 'titleBar--button__dimmer';
const invert = 'titleBar--button__invert';
export default class Header extends Component {
render() {
return (
<div id="titleBar">
<div className="titleBar--contents">
<div className="titleBar--title">Organizer</div>
<Button className={dimmer} label="Dim The Lights" />
<Button className={invert} label="Invert Text Color" />
</div>
</div>
);
}
}
Finally, here's the code containing the store and connection to Redux (NOTE: Layout contains three main components Header, Hero, and Info. The Buttons are created only within the Header component)
import React, { Component } from 'react';
import { combineReducers } from 'redux';
import { createStore } from 'redux'
import { Provider } from 'react-redux';
import Layout from '../components/Layout';
import * as reducers from '../reducers/reducer';
const reducer = combineReducers(reducers);
const store = createStore(reducer);
// This is dispatch was just a test to try and figure this problem out
store.dispatch({
type: 'TOGGLE_LIGHTS',
isActive: true
})
console.log(store.getState())
export default class Organizer extends Component {
render() {
return (
<Provider store={store}>
<div>
<Layout />
</div>
</Provider>
);
}
}
What I am looking to do is remove the state logic from the local React component and into Redux. I feel like the functions I have imported need to act as dispatchers. I also feel like I am setting up my initial actions incorrectly. This is such an incredibly simple event that finding an answer anywhere online is difficult. Anyone have any thoughts on what I can do to fix this?
You're almost there. It looks like you've left out the code for Layout component, which I assume is the component that's rendering your Button. The critical piece here is going to be your container, which is the component that's wrapped with Redux's connect to link it to the store. Docs for this. More details here.
What you did:
// components/Button.js - pseudocode
import {dimLights, invertColor} from '../../../actions/headerButtons';
handleClick() {
dimLights();
}
What Redux wants you to do instead:
// containers/App.js - pseudocode
import {dimLights, invertColor} from '../../../actions/headerButtons';
class App extends Component {
render() {
// Pass in your button state from the store, as well as
// your connected/dispatch-ified actions.
return (
<Button
state={this.props.buttonState}
onClick={this.props.buttonState ? dimLights : invertColor}
/>
);
}
}
function mapStateToProps(state, ownProps) {
return {
buttonState: state.buttonState
};
}
export default connect(mapStateToProps, {
// Your action functions passed in here get "dispatch-ified"
// and will dispatch Redux actions instead of returning
// { type, payload }-style objects.
dimLights, invertColor
})(App);
Hope that helps! Redux has a lot of boilerplate for simple stuff like this, however, because most of the pieces can be expressed as pure functions, you gain a lot in unit testing flexibility, and get to use the devtools debugger.
I have a pretty simple set of react components:
container that hooks into redux and handles the actions, store subscriptions, etc
list which displays a list of my items
new which is a form to add a new item to the list
I have some react-router routes as follows:
<Route name='products' path='products' handler={ProductsContainer}>
<Route name='productNew' path='new' handler={ProductNew} />
<DefaultRoute handler={ProductsList} />
</Route>
so that either the list or the form are shown but not both.
What I'd like to do is to have the application re-route back to the list once a new item has been successfully added.
My solution so far is to have a .then() after the async dispatch:
dispatch(actions.addProduct(product)
.then(this.transitionTo('products'))
)
Is this the correct way to do this or should I fire another action somehow to trigger the route change?
If you don't want to use a more complete solution like Redux Router, you can use Redux History Transitions which lets you write code like this:
export function login() {
return {
type: LOGGED_IN,
payload: {
userId: 123
}
meta: {
transition: (state, action) => ({
path: `/logged-in/${action.payload.userId}`,
query: {
some: 'queryParam'
},
state: {
some: 'state'
}
})
}
};
}
This is similar to what you suggested but a tiny bit more sophisticated. It still uses the same history library under the hood so it's compatible with React Router.
I ended up creating a super simple middleware that roughtly looks like that:
import history from "../routes/history";
export default store => next => action => {
if ( ! action.redirect ) return next(action);
history.replaceState(null, action.redirect);
}
So from there you just need to make sure that your successful actions have a redirect property. Also note, this middleware does not trigger next(). This is on purpose as a route transition should be the end of the action chain.
For those that are using a middleware API layer to abstract their usage of something like isomorphic-fetch, and also happen to be using redux-thunk, you can simply chain off your dispatch Promise inside of your actions, like so:
import { push } from 'react-router-redux';
const USER_ID = // imported from JWT;
function fetchUser(primaryKey, opts) {
// this prepares object for the API middleware
}
// this can be called from your container
export function updateUser(payload, redirectUrl) {
var opts = {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
};
return (dispatch) => {
return dispatch(fetchUser(USER_ID, opts))
.then((action) => {
if (action.type === ActionTypes.USER_SUCCESS) {
dispatch(push(redirectUrl));
}
});
};
}
This reduces the need for adding libraries into your code as suggested here, and also nicely co-locates your actions with their redirects as done in redux-history-transitions.
Here is what my store looks like:
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
import api from '../middleware/api';
import { routerMiddleware } from 'react-router-redux';
export default function configureStore(initialState, browserHistory) {
const store = createStore(
rootReducer,
initialState,
applyMiddleware(thunk, api, routerMiddleware(browserHistory))
);
return store;
}
I know I am little late in the party as react-navigation is already included in the react-native documentation, but still it can be useful for the user who have used/using Navigator api in their apps.
what I tried is little hackish, I am saving navigator instance in object as soon as renderScene happens-
renderScene(route, navigator) {
const Component = Routes[route.Name]
api.updateNavigator(navigator); //will allow us to access navigator anywhere within the app
return <Component route={route} navigator={navigator} data={route.data}/>
}
my api file is something lke this
'use strict';
//this file includes all my global functions
import React, {Component} from 'react';
import {Linking, Alert, NetInfo, Platform} from 'react-native';
var api = {
navigator,
isAndroid(){
return (Platform.OS === 'android');
},
updateNavigator(_navigator){
if(_navigator)
this.navigator = _navigator;
},
}
module.exports = api;
now in your actions you can simply call
api.navigator.push({Name:'routeName',
data:WHATEVER_YOU_WANTED_TO_PASS)
you just need to import your api from the module.
If you're using react-redux and react-router, then I think this link provides a great solution.
Here's the steps I used:
Pass in a react-router history prop to your component, either by rendering your component inside a react-router <Route/> component or by creating a Higher Order Component using withRouter.
Next, create the route you want to redirect to (I called mine to).
Third, call your redux action with both history and to.
Finally, when you want to redirect (e.g., when your redux action resolves), call history.push(to).