Say I have the following:
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return {
type: SOME_ACTION,
}
}
And in that action creator, I want to access the global store state (all reducers). Is it better to do this:
import store from '../store';
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return {
type: SOME_ACTION,
items: store.getState().otherReducer.items,
}
}
or this:
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return (dispatch, getState) => {
const {items} = getState().otherReducer;
dispatch(anotherAction(items));
}
}
There are differing opinions on whether accessing state in action creators is a good idea:
Redux creator Dan Abramov feels that it should be limited: "The few use cases where I think it’s acceptable is for checking cached data before you make a request, or for checking whether you are authenticated (in other words, doing a conditional dispatch). I think that passing data such as state.something.items in an action creator is definitely an anti-pattern and is discouraged because it obscured the change history: if there is a bug and items are incorrect, it is hard to trace where those incorrect values come from because they are already part of the action, rather than directly computed by a reducer in response to an action. So do this with care."
Current Redux maintainer Mark Erikson says it's fine and even encouraged to use getState in thunks - that's why it exists. He discusses the pros and cons of accessing state in action creators in his blog post Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability.
If you find that you need this, both approaches you suggested are fine. The first approach does not require any middleware:
import store from '../store';
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return {
type: SOME_ACTION,
items: store.getState().otherReducer.items,
}
}
However you can see that it relies on store being a singleton exported from some module. We don’t recommend that because it makes it much harder to add server rendering to your app because in most cases on the server you’ll want to have a separate store per request. So while technically this approach works, we don’t recommend exporting a store from a module.
This is why we recommend the second approach:
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return (dispatch, getState) => {
const {items} = getState().otherReducer;
dispatch(anotherAction(items));
}
}
It would require you to use Redux Thunk middleware but it works fine both on the client and on the server. You can read more about Redux Thunk and why it’s necessary in this case here.
Ideally, your actions should not be “fat” and should contain as little information as possible, but you should feel free to do what works best for you in your own application. The Redux FAQ has information on splitting logic between action creators and reducers and times when it may be useful to use getState in an action creator.
When your scenario is simple you can use
import store from '../store';
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return {
type: SOME_ACTION,
items: store.getState().otherReducer.items,
}
}
But sometimes your action creator need to trigger multi actions
for example async request so you need
REQUEST_LOAD REQUEST_LOAD_SUCCESS REQUEST_LOAD_FAIL actions
export const [REQUEST_LOAD, REQUEST_LOAD_SUCCESS, REQUEST_LOAD_FAIL] = [`REQUEST_LOAD`
`REQUEST_LOAD_SUCCESS`
`REQUEST_LOAD_FAIL`
]
export function someAction() {
return (dispatch, getState) => {
const {
items
} = getState().otherReducer;
dispatch({
type: REQUEST_LOAD,
loading: true
});
$.ajax('url', {
success: (data) => {
dispatch({
type: REQUEST_LOAD_SUCCESS,
loading: false,
data: data
});
},
error: (error) => {
dispatch({
type: REQUEST_LOAD_FAIL,
loading: false,
error: error
});
}
})
}
}
Note: you need redux-thunk to return function in action creator
I agree with #Bloomca. Passing the value needed from the store into the dispatch function as an argument seems simpler than exporting the store. I made an example here:
import React from "react";
import {connect} from "react-redux";
import * as actions from '../actions';
class App extends React.Component {
handleClick(){
const data = this.props.someStateObject.data;
this.props.someDispatchFunction(data);
}
render(){
return (
<div>
<div onClick={ this.handleClick.bind(this)}>Click Me!</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return { someStateObject: state.someStateObject };
};
const mapDispatchToProps = (dispatch) => {
return {
someDispatchFunction:(data) => { dispatch(actions.someDispatchFunction(data))},
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
I would like to point out that it is not that bad to read from the store -- it might be just much more convenient to decide what should be done based on the store, than to pass everything to the component and then as a parameter of a function. I agree with Dan completely, that it is much better not to use store as a singletone, unless you are 100% sure that you will use only for client-side rendering (otherwise hard to trace bugs might appear).
I have created a library recently to deal with verbosity of redux, and I think it is a good idea to put everything in the middleware, so you have everyhing as a dependency injection.
So, your example will look like that:
import { createSyncTile } from 'redux-tiles';
const someTile = createSyncTile({
type: ['some', 'tile'],
fn: ({ params, selectors, getState }) => {
return {
data: params.data,
items: selectors.another.tile(getState())
};
},
});
However, as you can see, we don't really modify data here, so there is a good chance that we can just use this selector in other place to combine it somewhere else.
Presenting an alternative way of solving this. This may be better or worse than Dan's solution, depending on your application.
You can get the state from the reducers into the actions by splitting the action in 2 separate functions: first ask for the data, second act on the data. You can do that by using redux-loop.
First 'kindly ask for the data'
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return {
type: SOME_ACTION,
}
}
In the reducer, intercept the ask and provide the data to the second stage action by using redux-loop.
import { loop, Cmd } from 'redux-loop';
const initialState = { data: '' }
export default (state=initialState, action) => {
switch(action.type) {
case SOME_ACTION: {
return loop(state, Cmd.action(anotherAction(state.data))
}
}
}
With the data in hand, do whatever you initially wanted
export const ANOTHER_ACTION = 'ANOTHER_ACTION';
export function anotherAction(data) {
return {
type: ANOTHER_ACTION,
payload: data,
}
}
Hope this helps someone.
I know I'm late to the party here, but I came here for opinions on my own desire to use state in actions, and then formed my own, when I realized what I think is the correct behavior.
This is where a selector makes the most sense to me. Your component that issues this request should be told wether it's time to issue it through selection.
export const SOME_ACTION = 'SOME_ACTION';
export function someAction(items) {
return (dispatch) => {
dispatch(anotherAction(items));
}
}
It might feel like leaking abstractions, but your component clearly needs to send a message and the message payload should contain pertinent state. Unfortunately your question doesn't have a concrete example because we could work through a 'better model' of selectors and actions that way.
I would like to suggest yet another alternative that I find the cleanest, but it requires react-redux or something simular - also I'm using a few other fancy features along the way:
// actions.js
export const someAction = (items) => ({
type: 'SOME_ACTION',
payload: {items},
});
// Component.jsx
import {connect} from "react-redux";
const Component = ({boundSomeAction}) => (<div
onClick={boundSomeAction}
/>);
const mapState = ({otherReducer: {items}}) => ({
items,
});
const mapDispatch = (dispatch) => bindActionCreators({
someAction,
}, dispatch);
const mergeProps = (mappedState, mappedDispatches) => {
// you can only use what gets returned here, so you dont have access to `items` and
// `someAction` anymore
return {
boundSomeAction: () => mappedDispatches.someAction(mappedState.items),
}
});
export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);
// (with other mapped state or dispatches) Component.jsx
import {connect} from "react-redux";
const Component = ({boundSomeAction, otherAction, otherMappedState}) => (<div
onClick={boundSomeAction}
onSomeOtherEvent={otherAction}
>
{JSON.stringify(otherMappedState)}
</div>);
const mapState = ({otherReducer: {items}, otherMappedState}) => ({
items,
otherMappedState,
});
const mapDispatch = (dispatch) => bindActionCreators({
someAction,
otherAction,
}, dispatch);
const mergeProps = (mappedState, mappedDispatches) => {
const {items, ...remainingMappedState} = mappedState;
const {someAction, ...remainingMappedDispatch} = mappedDispatch;
// you can only use what gets returned here, so you dont have access to `items` and
// `someAction` anymore
return {
boundSomeAction: () => someAction(items),
...remainingMappedState,
...remainingMappedDispatch,
}
});
export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);
If you want to reuse this you'll have to extract the specific mapState, mapDispatch and mergeProps into functions to reuse elsewhere, but this makes dependencies perfectly clear.
I wouldn't access state in the Action Creator. I would use mapStateToProps() and import the entire state object and import a combinedReducer file (or import * from './reducers';) in the component the Action Creator is eventually going to. Then use destructuring in the component to use whatever you need from the state prop. If the Action Creator is passing the state onto a Reducer for the given TYPE, you don't need to mention state because the reducer has access to everything that is currently set in state. Your example is not updating anything. I would only use the Action Creator to pass along state from its parameters.
In the reducer do something like:
const state = this.state;
const apple = this.state.apples;
If you need to perform an action on state for the TYPE you are referencing, please do it in the reducer.
Please correct me if I'm wrong!!!
Related
My action creator is not calling my reducer. Any help will be much appreciated.
types.js
export const SELECTED_FOOD = 'selected_food';
index.js (action creator / action)
import {
SELECTED_FOOD
} from './types';
export function selectedFood({data}) {
console.log('SELECTED_FOOD **********************',data);
return({
type: SELECTED_FOOD,
payload: data
});
}
Output from console.log in action creator
Object {_id: "18240", description: "Croissants, apple", score: 0.75, fields: Object}
selected_food_reducer.js
import {
SELECTED_FOOD
} from '../actions/types';
export default function(state = [], action) {
switch(action.type) {
case SELECTED_FOOD:
console.log('Selected Food Reducer *************', state);
return action.payload ;
}
return state;
}
EDIT component failing to call dispatch.
I should have added this on my initial post, it appears there is something wrong in how dispatch is called. ESLint is flagging dispatch on line 3 for defined but never used.
import React from 'react';
import { connect } from 'react-redux';
import { dispatch } from 'react-redux';
import { selectedFood } from '../actions/index';
class TableRow extends React.Component {
render() {
const {
data
} = this.props;
console.log('PROPS TableRow', this.props);
const row = data.map((data) =>
<tr onClick={() => selectedFood({data})}>
<td key={data.description}>{data.description}</td>
<td key={data.id}>{data.fields.nutrients[0].amountPer100G}</td>
<td key={data.id}>{data.fields.nutrients[1].amountPer100G}</td>
<td key={data.id}>{data.fields.nutrients[4].amountPer100G}</td>
</tr>
);
return (
<tbody>{row}</tbody>
);
}
}
const mapStateToProps = (state) => {
return {
selectedFood: state.selectedFood
}
}
const mapDispatchToProps = (dispatch) => {
console.log('IN mapDispatchToProps')
return {
onClick: ({data}) => {
dispatch(selectedFood({data}))
}
}
}
export default connect(mapStateToProps, mapDispatchToProp)(TableRow);
The action creator does not call the reducer. It is what it is, nothing more, it creates an action - that is, an object with the action type and the payload for that action.
You need to dispatch an action, to force redux to call the reducer, and that is the place where you use your action creator, that is:
import { Dispatch } from "redux";
import { selectedFood } from "./actions";
Dispatch(selectedFood({type:"hamburger"}));
that should call the reducer, however mostly you'll not call the Dispatch directly, rather in your mapDispatchToProps method used to connect your react component to the redux store.
There are plenty of sample how to use react-redux to use above map functionality, so I would suggest to read into it, and to read how redux works.
====== EDIT after question updated =========
So firstly dispatch from import is not used and ESLint is right telling it, you don't need that import since in:
const mapDispatchToProps = (dispatch) => {
console.log('IN mapDispatchToProps')
return {
onClick: ({data}) => {
dispatch(selectedFood({data}))
}
}
}
you don't call dispatch from your import only from the argument, it's passed to your mapDispatchToProps by the connect function.
Then this is just plain wrong:
<tr onClick={() => selectedFood({data})}>
you imported an action creator which is called on click of table row, that is an action definition is created by the action creator, and that's it. Your code does exactly what you wrote.
The mapDispatchToProps function does what the name suggests - it maps dispatch functions to props of your component.
So it should be:
<tr onClick={() => this.props.onClick({data})}>
and that should dispatch the action and work.
However I would strongly suggest to take some courses or read more about react, redux and react-redux, because your code samples and the question itself suggest, that you are just trying to get something to work, without the basic understanding how it works, and even how javascript works. Sorry for that comment but that's how it looks like.
I think you might need to show how you're importing other files. My observations from what you shared:
1) You need to import SELECTED_FOOD from types.
2) Your return state should be within the context of the switch statement.
There is an often forgotten rule in Redux that when you change the reducer you need to restart your localhost server.
You are doing it correctly- there are many ways to architect Redux into React. You don't need to use mapDispatchToProps if you are importing your action creators using connect()().
Just restart your localhost server if you don't see any typos. (I usually use NPM, so I control+c out of npm start in the terminal and run npm start again each time I add a new reducer.)
I have a problem when trying to fetch initial data for my app from an api.
My problem is that after the console.log(url) in the action, nothing happens. I see the url in the console, but the rest of the code in getInitialRuns() doesn't seem to be executed, atleast not the way I expect. I get no error messages.
When using Postman, I can succesfully get a response from that API endpoint, so the API should be fine.
I have actions that looks like this:
function requestRuns(){
console.log('request')
return {
type: 'getInitialRuns'
}
}
export function getInitialRuns(){
var url = 'http://localhost:32118/api/Runs';
console.log(url);
return dispatch => {
dispatch(requestRuns())
return fetch(url)
.then(response => response.json().then(body => ({response, body})))
.then(({response, body}) => {
if(!response.ok){
console.log('fail')
}
else{
console.log('success')
}
})
}
The component that calls the action looks like this:
class RunList extends Component{
constructor(props) {
super(props)
}
componentWillMount(){
getInitialRuns()
}
render() {
const {runs, isFetching, error} = this.props
return (
<ul className="run-list">
{runs.map((run) =>
<Run key={run.Id} id={run.Id} date={run.DateString} day={run.Day} distance={run.Distance} duration={run.DurationString} avgpace={run.AvgPaceString} calories={run.Calories}/>
)}
</ul>
)
}
}
RunList.propTypes = {
isFetching: PropTypes.bool.isRequired,
runs: PropTypes.instanceOf(Immutable.List),
error: PropTypes.object
}
function mapStateToProps(state) {
return{
runs: state.runs,
isFetching: state.isFetching,
error: state.error
}
}
export default connect(mapStateToProps)(RunList)
My store is set up like this:
import { createStore, applyMiddleware } from 'redux';
import {composeWithDevTools} from 'redux-devtools-extension';
import runs from './reducers/runs';
import thunk from 'redux-thunk';
export default createStore(runs,composeWithDevTools( applyMiddleware(thunk) ))
And these are my reducers
import Immutable from 'immutable'
let initialState = {
runs: Immutable.List([]),
isFetching: false,
error: undefined
};
export default (state = initialState, action) => {
switch(action.type) {
case 'addRun':
return state.unshift(action.run)
case 'deleteRun':
return Object.assign({}, state, {
runs: state.runs.filter((run) => run.Id !== action.id)
})
case 'getInitialRuns':
console.log('initial')
return Object.assign({}, state, {
isFetching: true
})
case 'getInitialRunsSuccess':
console.log('success')
return Object.assign({}, state, {
isFetching: false,
runs: action.runs
})
case 'getInitialRunsFailure':
return Object.assign({}, state, {
isFetching: false,
error: action.error
})
default:
return state
}
}
In order to dispatch an action on redux, you should provide a mapDispatchToProps function to connect. From redux docs:
(..) You can define a function called mapDispatchToProps() that receives the dispatch() method and returns callback props that you want to inject into the presentational component
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
I see you are using some advanced libraries like Immutablejs. I suggest
you start with reading the awesome redux documentation as it will take you step by step. And until you're familiar with basic redux concepts, avoid any other library.
Here are some notes i hope are useful for you: (they are taken from redux docs)
Notes
An action object which is a payload of information that send data from your application to your store. It is of this form:
var ADD_TODO = {
type: ADD_TODO,
text: 'Build my first Redux app'
}
It's recommended to name you actions types in Upper case, like ADD_TODO.
dispatch accepts an action object (see the example above).
It is recommended to use action creators which are functions that return an action object. It makes them easily testable and portable
Action creators are usually named in lower case: addTodo().
I hope this helps a bit
You should be returning a new state if you want Redux to notice any state change.
Your reducer cases should look something like that:
return {
...previousState,
newValues
}
'addRun'
return state.unshift(action.run)
should be
return {...state, runs: state.run.unshift(action.run) }
The function getInitialRuns returns a function, so calling it doesn't execute anything of the returned function. Btw I'm not sure executing it would be of any use for your app.
Reducers are synchronous, so if you need to do anything asynchronous you would need a middleware, like redux-thunk or redux-observable.
Flow should be:
State -> Views -> Actions -> Middleware -> Actions -> Reducers -> State and back to Views
Please look at the docs at http://redux.js.org/docs/advanced/AsyncActions.html
Also you can enjoy the excellent free courses on egghead:
https://egghead.io/courses/getting-started-with-redux
https://egghead.io/courses/building-react-applications-with-idiomatic-redux
On github you'll find a lot of material on the courses too.
I'm using ReactJS + Redux, along with Express and Webpack. There is an API built, and I want to be able to make REST calls -- GET, POST, PUT, DELETE -- from the client-side.
How and what is the properly way to go about doing so with the Redux architecture? Any good example of the flow, in terms of reducers, action creators, store, and react routes, would be extremely helpful.
Thank you in advance!
The simpliest way, is to do it using redux-thunk package. This package is an redux middleware, so first of all, you should connect it to redux:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
This allows you to dispatch async actions along with regular sync actions. Let's create one of them:
// actions.js
export function fetchTodos() {
// Instead of plain objects, we are returning function.
return function(dispatch) {
// Dispatching REQUEST action, which tells our app, that we are started requesting todos.
dispatch({
type: 'FETCH_TODOS_REQUEST'
});
return fetch('/api/todos')
// Here, we are getting json body(in our case it will contain `todos` or `error` prop, depending on request was failed or not) from server response
// And providing `response` and `body` variables to the next chain.
.then(response => response.json().then(body => ({ response, body })))
.then(({ response, body }) => {
if (!response.ok) {
// If request was failed, dispatching FAILURE action.
dispatch({
type: 'FETCH_TODOS_FAILURE',
error: body.error
});
} else {
// When everything is ok, dispatching SUCCESS action.
dispatch({
type: 'FETCH_TODOS_SUCCESS',
todos: body.todos
});
}
});
}
}
I prefer to separate react components on presentational and container components. This approach was perfectly described in this article.
Next, we should create TodosContainer component, which would provide data to presentational Todos component. Here, we are using react-redux library:
// TodosContainer.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchTodos } from '../actions';
class TodosContainer extends Component {
componentDidMount() {
// When container was mounted, we need to start fetching todos.
this.props.fetchTodos();
}
render() {
// In some simple cases, it is not necessary to create separate `Todos` component. You can put todos markup directly here.
return <Todos items={this.props.todos} />
}
}
// This function is used to convert redux global state to desired props.
function mapStateToProps(state) {
// `state` variable contains whole redux state.
return {
// I assume, you have `todos` state variable.
// Todos will be available in container component as `this.props.todos`
todos: state.todos
};
}
// This function is used to provide callbacks to container component.
function mapDispatchToProps(dispatch) {
return {
// This function will be available in component as `this.props.fetchTodos`
fetchTodos: function() {
dispatch(fetchTodos());
}
};
}
// We are using `connect` function to wrap our component with special component, which will provide to container all needed data.
export default connect(mapStateToProps, mapDispatchToProps)(TodosContainer);
Also, you should create todosReducer, which will handle FETCH_TODOS_SUCCESS action, and other 2 actions if you want display loader / error message.
// reducers.js
import { combineReducers } from 'redux';
const INITIAL_STATE = {
items: [],
isFetching: false,
error: undefined
};
function todosReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case 'FETCH_TODOS_REQUEST':
// This time, you may want to display loader in the UI.
return Object.assign({}, state, {
isFetching: true
});
case 'FETCH_TODOS_SUCCESS':
// Adding derived todos to state
return Object.assign({}, state, {
isFetching: false,
todos: action.todos
});
case 'FETCH_TODOS_FAILURE':
// Providing error message to state, to be able display it in UI.
return Object.assign({}, state, {
isFetching: false,
error: action.error
});
default:
return state;
}
}
export default combineReducers({
todos: todosReducer
});
For other operations like CREATE, UPDATE, DELETE there is nothing special, they are implementing the same way.
The short answer is:
redux is not an architecture
You can use any library. A lot of people these days use the fetch API directly.
To be able to integrate redux with asynchronous actions (which you need for AJAX), you need to use a library to help. The most popular two are redux-thunk and redux-saga, as others have said.
For a brain-dead simple library that you can drop in to your redux app, you could try redux-crud-store. Disclaimer: I wrote it. You could also read the source for redux-crud-store if you are interested in integrating the fetch API, or another API client, with redux-saga
This is the primary use case for libraries like redux-thunk, redux-saga, and redux-observable.
redux-thunk is the simplest, where you would do something like this:
import fetch from 'isomorphic-fetch'
export const REQUEST_POSTS = 'REQUEST_POSTS'
function requestPosts(subreddit) {
return {
type: REQUEST_POSTS,
subreddit
}
}
export const RECEIVE_POSTS = 'RECEIVE_POSTS'
function receivePosts(subreddit, json) {
return {
type: RECEIVE_POSTS,
subreddit,
posts: json.data.children.map(child => child.data),
receivedAt: Date.now()
}
}
// Meet our first thunk action creator!
// Though its insides are different, you would use it just like any other action creator:
// store.dispatch(fetchPosts('reactjs'))
export function fetchPosts(subreddit) {
// Thunk middleware knows how to handle functions.
// It passes the dispatch method as an argument to the function,
// thus making it able to dispatch actions itself.
return function (dispatch) {
// First dispatch: the app state is updated to inform
// that the API call is starting.
dispatch(requestPosts(subreddit))
// The function called by the thunk middleware can return a value,
// that is passed on as the return value of the dispatch method.
// In this case, we return a promise to wait for.
// This is not required by thunk middleware, but it is convenient for us.
return fetch(`http://www.reddit.com/r/${subreddit}.json`)
.then(response => response.json())
.then(json =>
// We can dispatch many times!
// Here, we update the app state with the results of the API call.
dispatch(receivePosts(subreddit, json))
)
// In a real world app, you also want to
// catch any error in the network call.
}
}
The above example is taken directly from http://redux.js.org/docs/advanced/AsyncActions.html which is really the definitive source for answers on your question.
I am using this starter kit https://github.com/davezuko/react-redux-starter-kit and am following some tutorials at the same time, but the style of this codebase is slightly more advanced/different than the tutorials I am watching. I am just a little lost with one thing.
HomeView.js - This is just a view that is used in the router, there are higher level components like Root elsewhere I don't think I need to share that, if I do let me know, but it's all in the github link provided above.
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { searchListing } from '../../redux/modules/search'
export class HomeView extends React.Component {
componentDidMount () {
console.log(this.props)
}
render () {
return (
<main onClick={this.props.searchListing}>
<NavBar search={this.props.search} />
<Hero/>
<FilterBar/>
<Listings/>
<Footer/>
</main>
)
}
}
I am using connect() and passing in mapStateToProps to tell the HomeView component about the state. I am also telling it about my searchListing function that is an action which returns a type and payload.
export const searchListing = (value) => {
console.log(value)
return {
type: SEARCH_LISTINGS,
payload: value
}
}
Obviously when I call the method inside the connect() I am passing in an empty object searchListing: () => searchListing({})
const mapStateToProps = (state) => {
return {
search: { city: state.search }
}
}
export default connect((mapStateToProps), { searchListing: () => searchListing({}) })(HomeView)
This is where I am stuck, I am trying to take the pattern from the repo, which they just pass 1, I think anytime that action is created the logic is just add 1 there is no new information passed from the component.
What I am trying to accomplish is input search into a form and from the component pass the users query into the action payload, then the reducer, then update the new state with the query. I hope that is the right idea.
So if in the example the value of 1 is hardcoded and passed into the connect() method, how can I make it so that I am updating value from the component dynamically? Is this even the right thinking?
You almost got it right. Just modify the connect function to pass the action you want to call directly:
const mapStateToProps = (state) => ({
search: { city: state.search }
});
export default connect((mapStateToProps), {
searchListing
})(HomeView);
Then you may use this action with this.props.searchListing(stringToSearch) where stringToSearch is a variable containing the input value.
Notice : You don't seem to currently retrieve the user query. You may need to retrieve it first and then pass it to the searchListing action.
If you need to call a function method, use dispatch.
import { searchListing } from '../../redux/modules/search';
const mapDispatchToProps = (dispatch) => ({
searchListing: () => {
dispatch(searchListing());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(HomeView);
Then, you have made the function a prop, use it with searchListing.
In my project, I want to be able to dispatch modal popups for things such as logging in and registering accounts. The problem I have is that I want them to be able to be used for any purpose, but I'm not sure about how to do it in the "React way". At first, I thought about doing it like this:
/constants/Modal.js
export const DISPATCH = 'MODAL/DISPATCH';
export const UPDATE = 'MODAL/UPDATE';
export const DISMISS = 'MODAL/DISMISS';
/actions/Modal.js
import {
DISPATCH,
UPDATE,
DISMISS
} from '../constants/Modal';
export function dispatch(type, props) {
return {
type: DISPATCH,
payload: {type, props}
};
}
export function update(props) {
return {
type: UPDATE,
payload: {props}
};
}
export function dismiss() {
return {type: DISMISS};
}
/reducers/Modal.js
import {Map} from 'immutable';
import {
DISPATCH,
UPDATE,
DISMISS
} from '../constants/Modal';
const initialState = Map({
type: '',
props: Map(),
isDispatched: false
});
export default function modalReducer(state = initialState, action) {
switch (action.type) {
case DISPATCH:
return state.set('type', action.payload.type)
.set('props', Map(action.payload.props))
.set('isDispatched', true);
case UPDATE:
return state.set('props', Map(action.payload.props));
case DISMISS:
return state.set('isDispatched', false);
default:
return state;
}
}
Components would then fire these actions, then the ModalContainer component would render the appropriate popup based on state.modal.get('type') and pass state.modal.get('props') to the Modal component. The issue with this is that props would end up including the component's children and various methods, which have no business being in the store. How can I render popups from components that aren't children of the ModalContainer component without doing something asinine like React.render(<Modal {...props}>, document.getElementById('modal-container'))?
I described a similar way of doing modal dialogues in this answer.
In my experience, I just don’t pass non-serializable props with the payload.
For example, rather than pass the children, I would encapsulate behavior in the appropriate modal component itself, e.g. <DeleteUserModal kind='sad' userId={42} hasErrors={false} />. It is then up to DeleteUserModal to connect to the store if necessary, retrieve the data it needs, dispatch the actions, and choose which children to render.
Alternatively you can avoid routing modals through Redux altogether and just use something like react-modal which actually does this for you:
doing something asinine like React.render(<Modal {...props}>, document.getElementById('modal-container'))?
(It’s not asinine, people call this pattern “a portal”.)