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
Related
I'm learning redux and I struggle to understand it.
I have a list of article and each articles contain title and url.
I would like to get the url of the current article and pass it to an other component for show the preview url
I'm trying to pass the url of current article when I click to the button to put on other component in Article.js but it's not working. it's only display all the url of all the article.
If someone can help me or explain me what I'm wrong it's could be very nice.
in actions.js
export const articleClick = url => ({
type: SHOW_ARTCILE,
payload: { url }
});
in redux, article.js
article: null
}
const articleReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SHOW_ARTCILE':
return [
...state,
{
url: action.url,
}
]
;
default:
return state;
}
}
export default articleReducer;
in Article.js
import { articleClick } from '../../redux/actions';
import { connect } from "react-redux";
class Article extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>{this.props.article.title}</p>
<div>
<p>Posted by : {this.props.article.by}</Posted>
<button onClick={() => this.props.articleClick(this.props.article.url), console.log(this.props.article.url)}>Visit website</button>
</div>
</div>
)
}
}
export default connect(
null,
{ articleClick }
)(Article);
in Preview.js
import { connect } from "react-redux";
import { articleClick } from '../../redux/actions';
class PreviewArticle extends Component {
render() {
return (
<p>
{this.url}
</p>
);
}
}
export default connect(
null,
{ articleClick }
)(PreviewArticle);
Let's try to optimize your react-redux flow here so we can explain things a bit easier.
First let's try to make your App component look like this:
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, combineReducers } from "redux";
import articles from "./reducers/articles";
import Articles from "./components/Articles";
import Preview from "./components/Preview";
const store = createStore(
combineReducers({
articles: articles
})
);
function App() {
return (
<Provider store={store}>
<div className="App">
<Articles />
<Preview />
</div>
</Provider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The main component displays the Articles component and Preview component. Additionally, we feed the redux-store to the Main component.
Let's take a look at all the aspects that comprise our redux-store.
reducers.js
const initialState = {
articles: [
{
title: "Corgi",
url: "https://www.akc.org/dog-breeds/cardigan-welsh-corgi/"
},
{
title: "Leopard Dog",
url: "https://www.akc.org/dog-breeds/catahoula-leopard-dog/"
}
],
article: {}
};
const articlesReducer = (state = initialState, action) => {
switch (action.type) {
case "SHOW_URL":
return {
...state,
article: action.payload
};
default:
return state;
}
};
export default articlesReducer;
In the reducer, we have a default state that holds an array of objects (articles) and a current article. We'll use both to populate the display of our Articles and Preview components. The only time our reducer will get updated is when an action with a type of "SHOW_URL" is dispatched, and thus we will update the current article.
See action-creator in actions.js:
export const showUrl = article => {
return {
type: "SHOW_URL",
payload: article
};
};
Now for our components, Articles, Article and Preview.
In Articles, we need to use mapStateToProps to get the list of articles available in our Redux-state. Then, we iterate over each article and render an individual Article component. Passing, the iterated article as a prop.
import React from "react";
import { connect } from "react-redux";
import Article from "./Article";
const Articles = ({ articles }) => {
return (
<div>
{articles.articles.map(article => {
return <Article article={article} />;
})}
</div>
);
};
const mapStateToProps = state => {
return {
articles: state.articles
};
};
export default connect(mapStateToProps)(Articles);
Now in each unique Article component, we use our action-creator, passing in the article we received as a prop to update the redux-state.
import React from "react";
import { connect } from "react-redux";
import { showUrl } from "../actions/articleActions";
const Article = ({ article, showUrl }) => {
return (
<div>
<button onClick={() => showUrl(article)}>{article.title}</button>
</div>
);
};
const mapDispatchToProps = dispatch => {
return {
showUrl: article => {
dispatch(showUrl(article));
}
};
};
export default connect(
null,
mapDispatchToProps
)(Article);
When the action completes, our reducer gets an updated state, which causes our Preview component to re-render and reflect that updated data.
import React from "react";
import { connect } from "react-redux";
const Preview = ({ articles }) => {
const thisArticle = articles.article;
return (
<div>
<h4>{thisArticle.title}</h4>
<a href={thisArticle.url}>Go To Link</a>
</div>
);
};
const mapStateToProps = state => {
return {
articles: state.articles
};
};
export default connect(mapStateToProps)(Preview);
I've created a sample codesandbox for you here for reference: https://codesandbox.io/s/simple-redux-with-dogs-s1v6h
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'.
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
`);
};
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.
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 :)