How to get payload in component in redux? - reactjs

import React from 'react';
import './search.styles.scss';
import { connect } from 'react-redux';
import { setSearchField } from '../../redux/search/search.actions';
import { combineReducers } from 'redux';
class Search extends React.Component{
render(){
const { onSetSearchField, search } = this.props;
return (
<div className="search-container">
<p>user: {search}</p>
<input className="search-box" type="text" onChange={onSetSearchField} />
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSetSearchField: (event) => dispatch(setSearchField(event.target.value))
}
}
const mapStateToProps = (state) => {
console.log(state.search, "-----------")
if(state.search === "user:"){
alert(state.search)
}
return {
search: state.search
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Search);
reducer:
const initialStateSearch = {
search: null
}
export const getSearchType = (state=initialStateSearch, action={}) => {
switch (action.payload) {
case 'user':
return Object.assign({}, state, {search: action.payload})
case 'post':
return Object.assign({}, state, {search: action.payload})
default:
return state
}
}
action:
import { SearchActionTypes } from './search.types';
export const setSearchField = (text) => ({
type: SearchActionTypes.SEARCH_START,
payload: text
})
Here I'm adding search functionality using react and redux
But when I'm checking my payload in the reducer. It is coming but it is not coming in my search component.
I am alerting and trying to alert and display the text on a p tag
Please have a look

As I mentioned in a comment to your question, the code you showed for your component and redux looks fine, and the fact that you can see your redux store being populated makes me believe the problem lies with the configuration.
Have you tried making sure the Provider is a wrapper at the root level, so the components will know about the redux store https://react-redux.js.org/api/provider? If that exists, then I would advise looking at the redux example https://codesandbox.io/s/9on71rvnyo online and try to follow there logic and see what potentially you could be missing.

Can you confirm if you have used the same constant in Action Creator setSearchField for which you are checking in reducer. Is SearchActionTypes.SEARCH_START === 'user' or 'post'.

Related

Unable to show todo List in Initial render in react

I am trying to build the todoList using react, redux, redux-form and firestore database, I am able to insert the todo inside the database using actions or action creator also, I am able to fetch the data store in database but I am unable to show data on the website when the user first visits the website, also when user add todo and submit the form the first todo item shows to me blank!. Can someone help me to tell what's wrong with my code?
App.js
import React from 'react'
import { Container} from 'react-bootstrap'
import { connect } from 'react-redux'
import InputForm from './InputForm'
import Todo from './Todo'
import {listTodo} from '../actions'
class App extends React.Component {
constructor(props){
super(props);
this.renderList = this.renderList.bind(this);
}
componentDidMount(){
this.props.listTodo();
console.log(this.props.list);
}
renderList(){
return this.props.list.map((todo,id) => {
return (
<Todo todo={todo} key={id}/>
)
})
}
render() {
console.log("rendered");
if(!this.props.list){
return <div>Loading...</div>
}
return (
<Container fluid>
<h1 className="text-center" style={{marginTop: "5vh"}}>Todo App</h1>
<InputForm />
{this.renderList()}
</Container>
)
}
}
const mapStateToProps = (state) => {
console.log(state.todoList);
return {
list: state.todoList
}
}
export default connect(mapStateToProps, {listTodo})(App)
Todo.js
import React from 'react'
import { Card } from 'react-bootstrap'
class Todo extends React.Component {
render(){
console.log(this.props.todo);
return (
<Card className="text-center">
<Card.Body>
<Card.Text>{this.props.todo.todo}</Card.Text>
</Card.Body>
<Card.Footer className="text-muted">DeadLine by {this.props.todo.date}</Card.Footer>
</Card>
)
}
}
export default Todo
action(index.js)
import database from '../firebase/firebase'
export const addTodo = (inputValue) => {
database.collection('Todo').add({
todo: inputValue.todo_input,
date: inputValue.todo_date
});
return {
type: "ADD_TODO",
payload: inputValue
};
}
export const listTodo = () => {
const data = [];
database.collection('Todo').onSnapshot(snapshot => snapshot.docs.map(doc => data.push(doc.data())));
return {
type: 'LIST_TODO',
payload: data
}
}
export const deleteTodo = id => {
//delete todo
return {type: 'DELETE_TODO'}
}
TodoReducer.js
export default (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.payload];
case 'LIST_TODO':
return action.payload;
case 'DELETE_TODO':
return state.filter(item => item.id !== action.payload);
default:
return state;
}
}
reducers.js
import { combineReducers } from "redux";
import {reducer as formReducer} from 'redux-form'
import todoReducer from './TodoReducer'
export default combineReducers({
form: formReducer,
todoList: todoReducer
});
In your listTodo action creator, you create an object data and return that as part of your LIST_TODO action. This data object is then persisted into your store and then manipulated by your onSnapshot callback to add items to that data after the fact.
Those changes cannot be detected by React-Redux and so your component does not update by itself with content.
export const listTodo = () => {
const data = [];
// do never do something like this!
database.collection('Todo').onSnapshot(snapshot => snapshot.docs.map(doc => data.push(doc.data())));
return {
type: 'LIST_TODO',
payload: data
}
}
This breaks the second Redux Principle "The only way to change the state is to emit an action, an object describing what happened".
To do something like this, you would need to use a middleware, most commonly the redux-thunk middleware.
That is already included in the official Redux Toolkit, which I would generally recommend you to check out.
Since showing you how to do this correctly would turn into a 1:1 copy of the official tutorials, check those out instead:
for old-style "Vanilla Redux": Redux Fundamentals, Part 6: Async Logic and Data Fetching
for "Modern Redux" with Redux Toolkit: Redux Essentials, Part 5: Async Logic and Data Fetching

React-redux connect() not subscribing to dispatched actions properly

I have a simple component I'm trying to make work with redux. I map both props and dispatch actions, however only the props I initially get from the store work properly. I traced it all down to my actions: they are being dispatched, but respective reducers don't really do anything. Pretty simple stuff I came up with according to the tutorial and everything looks good to me, but I can't wrap my head around the problem here.
Here is a simplified version of the app:
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import Search from './Search'
import { Provider } from 'react-redux'
import store from './store'
const root = document.querySelector('#app')
ReactDOM.render(
<Provider store={store}>
<Search/>
</Provider>, root)
// Search.js
import React from 'react'
import { setText } from '../../actions/appActions'
import { connect } from 'react-redux';
const mapStateToProps = state => {
return {
text: state.app.searchText
}
}
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
class Search extends React.Component {
constructor() {
super()
}
render() {
return (
<input type="text" onChange={() => this.props.setText("text")} value={this.props.text}/>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Search)
// store.js
import { createStore, combineReducers } from 'redux'
import app from './reducers/appReducer'
export default createStore(combineReducers({/*other non-relevant reducers*/, app}))
// appActions.js
export function setText(text) {
return {
type: "APP_SET_TEXT",
payload: text,
}
}
// appReducer.js
const initialState = {
isSearchActive: true,
searchText: "Text",
}
export default function reducer(state = initialState, action) {
switch (action.type) {
case "APP_SET_TEXT":
console.log("fart")
return {
...state,
searchText: action.payload,
}
default:
return state
}
}
What I'm trying to to is to simply make the input value change according to the redux state. I do get the text from {this.props.text}, the change handler onChange={() => this.props.setText("text")} is being dispatched, but the reducer for some reason fails to catch the action that was dispatched.
I think you should change the mapDispatchToProps variable like the following:
const mapDispatchToProps = dispatch => {
return {
setText = (text) => dispatch(setText(text)),
}
}
There are two ways to achieve this
// MODIFYING DISPATHCER
const mapDispatchToProps = dispatch => {
return {
changeText: data => dispatch(setText(data)),
}
}
or
// CONNECT
export default connect(mapStateToProps, {
setText
})(Search)
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
change to
const mapDispatchToProps = dispatch => {
return {
changeText: text => dispatch(setText(text)),
}
}
And in your component use this.props.changeText function
as most of the answers suggests you can dispatch the actions or else you can simply have mapDispatchToProps an object.
mapDispatchToProps = {
setText,
dispatch
}
Your HOC connect should take care of dispatching not need to external definition
Use bindActionCreators from redux
import { bindActionCreators } from 'redux';
const mapDispatchToProps = dispatch => {
const setText = bindActionCreators(setText, dispatch);
return setText;
}
Since you're mapping your dispatch to props like this:
const mapDispatchToProps = dispatch => {
return {
setText,
dispatch
}
}
You'll need to explicitly call dispatch in your component:
class Search extends React.Component {
constructor() {
super()
}
render() {
const {dispatch, setText} = this.props;
return (
<input type="text" onChange={() => dispatch(setText("text"))} value={this.props.text}/>
)
}
}
It is easier just to map dispatch to props like this: setText = (text) => dispatch(setText(text))

Redux api calling

I'm wanting to update my trending array with the results calling the tmdb api. I'm not sure if im going about this the wrong way with calling the api or if im messing up somewhere else along the way. So far I've really been going in circles with what ive tried. Repeating the same things and not coming to a real solution. Havent been able to find another question similar to mine.
my actions
export const getTrending = url => dispatch => {
console.log("trending action");
axios.get(url).then(res =>
dispatch({
type: "TRENDING",
payload: res.data
})
);
};
my reducer
const INITIAL_STATE = {
results: [],
trending: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case "SEARCH_INFO":
return {
results: [action.payload]
};
case "TRENDING":
return { trending: action.payload };
default:
return state;
}
};
and my component im trying to get the results from
import React, { Component } from "react";
import Trending from "./Treding";
import "../App.css";
import { getTrending } from "../actions/index";
import { connect } from "react-redux";
export class Sidebar extends Component {
componentDidMount = () => {
const proxy = `https://cors-anywhere.herokuapp.com/`;
getTrending(`${proxy}https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};
render() {
return (
<div>
<h3 className="trending">Trending</h3>
{
this.props.trending ? (
<Trending movies={this.props.trending} />
) : (
<div>Loading</div>
)}
</div>
);
}
}
const mapStateToProps = state => {
return {
trending: state.trending
};
};
export default connect(mapStateToProps)(Sidebar);
Since you are directly calling the getTrending without passing it to connect method, it might be the issue.
Instead that you can pass getTrending to connect method so it can be available as props in the component. After that it can be dispatched and it will be handled by redux/ redux-thunk.
export default connect(mapStateToProps, { getTrending })(Sidebar);
And access it as props in the component.
componentDidMount = () => {
// const proxy = `https://cors-anywhere.herokuapp.com/`;
this.props.getTrending(`https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};

React component not updating on final dispatch and update of redux state

I am fairly new to React and Redux and I have an issue with my component not updating on the final dispatch that updates a redux store. I am using a thunk to preload some data to drive various pieces of my site. I can see the thunk working and the state updating seemingly correctly but when the data fetch success dispatch happens, the component is not seeing a change in state and subsequently not re rendering. the interesting part is that the first dispatch which sets a loading flag is being seen by the component and it is reacting correctly. Here is my code:
actions
import { programsConstants } from '../constants';
import axios from 'axios'
export const programsActions = {
begin,
success,
error,
};
export const loadPrograms = () => dispatch => {
dispatch(programsActions.begin());
axios
.get('/programs/data')
.then((res) => {
dispatch(programsActions.success(res.data.results));
})
.catch((err) => {
dispatch(programsActions.error(err.message));
});
};
function begin() {
return {type:programsConstants.BEGIN};
}
function success(data) {
return {type:programsConstants.SUCCESS, payload: data};
}
function error(message) {
return {type:programsConstants.ERROR, payload:message};
}
reducers
import {programsConstants} from '../constants';
import React from "react";
const initialState = {
data: [],
loading: false,
error: null
};
export function programs(state = initialState, action) {
switch (action.type) {
case programsConstants.BEGIN:
return fetchPrograms(state);
case programsConstants.SUCCESS:
return populatePrograms(state, action);
case programsConstants.ERROR:
return fetchError(state, action);
case programsConstants.EXPANDED:
return programsExpanded(state, action);
default:
return state
}
}
function fetchPrograms(state = {}) {
return { ...state, data: [], loading: true, error: null };
}
function populatePrograms(state = {}, action) {
return { ...state, data: action.payload, loading: false, error: null };
}
function fetchError(state = {}, action) {
return { ...state, data: [], loading: false, error: action.payload };
}
component
import React from "react";
import { connect } from 'react-redux';
import { Route, Switch, Redirect } from "react-router-dom";
import { Header, Footer, Sidebar } from "../../components";
import dashboardRoutes from "../../routes/dashboard.jsx";
import Loading from "../../components/Loading/Loading";
import {loadPrograms} from "../../actions/programs.actions";
class Dashboard extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.dispatch(loadPrograms());
}
render() {
const { error, loading } = this.props;
if (loading) {
return <div><Loading loading={true} /></div>
}
if (error) {
return <div style={{ color: 'red' }}>ERROR: {error}</div>
}
return (
<div className="wrapper">
<Sidebar {...this.props} routes={dashboardRoutes} />
<div className="main-panel" ref="mainPanel">
<Header {...this.props} />
<Switch>
{dashboardRoutes.map((prop, key) => {
let Component = prop.component;
return (
<Route path={prop.path} component={props => <Component {...props} />} key={key} />
);
})}
</Switch>
<Footer fluid />
</div>
</div>
);
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error
});
export default connect(mapStateToProps)(Dashboard);
The component should receive updated props from the success dispatch and re render with the updated data. Currently the component only re renders on the begin dispatch and shows the loading component correctly but doesn't re render with the data is retrieved and updated to the state by the thunk.
I've researched this for a couple days and the generally accepted cause for the component not getting a state refresh is inadvertent state mutation rather than returning a new state. I don't think I'm mutating the state but perhaps I am.
Any help would much appreciated!
Update 1
As requested here's the code for creating the store and combining the reducers
store:
const loggerMiddleware = createLogger();
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(
thunk,
loggerMiddleware)
);
export const store = createStore(rootReducer, enhancer);
reducer combine:
import { combineReducers } from 'redux';
import { alert } from './alert.reducer';
import { programs } from './programs.reducer';
import { sidenav } from './sidenav.reducer';
const rootReducer = combineReducers({
programs,
sidenav,
alert
});
export default rootReducer;
The 2nd param is expected to be [preloadedState]:
export const store = createStore(rootReducer, {} , enhancer);
axios.get return a promise that you need to await for to get your data:
Try this:
export const loadPrograms = () => async (dispatch) => {
dispatch(programsActions.begin());
try {
const res = await axios.get('/programs/data');
const data = await res.data;
console.log('data recieved', data)
dispatch(programsActions.success(data.results));
} catch (error) {
dispatch(programsActions.error(error));
}
};
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
data: state.programs.data,
});
Action Call
import React from 'react';
import { connect } from 'react-redux';
import { loadPrograms } from '../../actions/programs.actions';
class Dashboard extends React.Component {
componentDidMount() {
// Try to call you action this way:
this.props.loadProgramsAction(); // <== Look at this
}
}
const mapStateToProps = state => ({
loading: state.programs.loading,
error: state.programs.error,
});
export default connect(
mapStateToProps,
{
loadProgramsAction: loadPrograms,
},
)(Dashboard);
After three days of research and refactoring, I finally figured out the problem and got it working. Turns out that the version of react-redux is was using (6.0.1) was the issue. Rolled back to 5.1.1 and everything worked flawlessly. Not sure if something is broken in 6.0.1 or if I was just using wrong.

Reusing Reducer Logic does not toggle object using Redux and React'

I am reusing the same reducer logic for two different events. The idea is to toggle a class depending on which text you clicked on. Each event fires, but my object is not toggling. Any thoughts?
App:
import React from "react"
import { bindActionCreators } from 'redux'
import { connect } from "react-redux"
import * as toggleactionCreators from '../actions/toggleActions';
function mapStateToProps(state) {
return {
hiddenA: state.toggleA.hidden,
hiddenB: state.toggleB.hidden
}
}
function mapDispachToProps(dispatch) {
return bindActionCreators({...toggleactionCreators}, dispatch)
}
class Main extends React.Component {
toggleDiv() {
this.props.toggleDiv();
console.log(this.props)
}
render() {
const { hiddenA, hiddenB } = this.props;
return (
<div>
<div>
<h3 onClick={this.toggleDiv.bind(this)} className={ hiddenA ? null : "toggled"} >Good Day!</h3>
<h1 onClick={this.toggleDiv.bind(this)} className={ hiddenB ? null : "toggled"} >Hello There!</h1>
</div>
</div>
)
}
}
export default connect(mapStateToProps, mapDispachToProps)(Main);
Index Reducer:
import { combineReducers } from "redux"
import toggle from "./toggleReducer"
function createNamedWrapperReducer(reducerFunction, reducerName) {
return (state, action) => {
const {name} = action;
const isInitializationCall = state === undefined;
if(name !== reducerName && !isInitializationCall) return state;
return reducerFunction(state, action);
}
}
const thereducer = combineReducers({
toggleA : createNamedWrapperReducer(toggle, 'A'),
toggleB : createNamedWrapperReducer(toggle, 'B'),
});
export default thereducer;
toggleReducer:
const toggle = (state = { hidden: true}, action) => {
switch(action.type) {
case 'TOGGLE_DIV':
return Object.assign({}, ...state, {hidden: !state.hidden});
default:
return state;
}
};
export default toggle;
toggleAction:
export const toggleDiv = () => {
return {
type: 'TOGGLE_DIV',
}
}
This is how I would debug this.
Download Redux DevTools for your browser. This is the URL for chrome: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
Download React devtools for you browser. This is the URL for chrome: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
Look in Redux Devtools:
Is the action emitted from your action creator
Does the reducer update the state correctly?
If both the actions, and reducers looks correctly, check your React component:
Does the component receive the correct props? If yes, it's something with how the props are rendered. If no, it's something with how the store is connected to your component.
Hope this debugging tutorial is useful for you. If you have any follow up questions, please don't hesitate to ask :)

Resources