I am learning Redux. I am trying to connect dispatch function from container component to presentation component.
Container Component:
//FILE : app/javascript/packs/containers/registration.js
import { connect } from 'react-redux'
import { fetchCountry } from '../actions'
import Countries from '../components/registration/countries';
const getCountry = (state, filter) => {
switch (filter) {
case 'region':
console.log("Get Country Triggered",state)
default:
console.log("Get Country Default Triggered",state)
}
}
const mapStateToProps = state => ({
countries:getCountry(state,'region')
})
const mapDispatchToProps = dispatch => ({
fetchCountry: region => dispatch(fetchCountry(region))
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(Countries)
Presentational Component
// FILE : /app/javascript/packs/components/registration/countries.jsx
import React from 'react'
import PropTypes from 'prop-types'
const Countries = ({ fetchCountry }) => (
<ul>
<li>
<button onClick={() => fetchCountry('region')}>Get Country</button>
</li>
</ul>
)
Countries.propTypes = {
fetchCountry: PropTypes.func.isRequired
}
Actions :
//FILE : /app/javascript/packs/actions/index.js
/* Action types */
export const FETCH_COUNTRY = "FETCH_COUNTRY";
export const FETCH_CITY = "FETCH_CITY";
/* Action creators */
export function fetchCountry(region) {
return { type: FETCH_COUNTRY, region };
}
Reducer
// FILE: /app/javascript/packs/reducers/fetchPlace.js
const fetchPlace = (state = [], action) => {
switch (action.type) {
case 'FETCH_COUNTRY':
console.log('FETCH COUNTRY Reducer');
default:
return state
}
}
export default fetchPlace
I am fetchCountry is undefined error when I try to load the page.
warning.js:33 Warning: Failed prop type: The propfetchCountryis marked as required inCountries, but its value isundefined.
I understand , i am missing some basics here, any help will be highly appreciated.
Maybe this line is your problem. Depends on if you have an index.js
import { fetchCountry } from '../actions'
should be
import { fetchCountry } from '../actions/action_file_name'
Related
Hi im new to redux and im trying to create a movie app using the API from www.themoviedb.org. I am trying to display the popular movies and im sure the API link works since ive tested it in postman but i cant seem to figure out why redux doesnt pick up the data.
//action
import { FETCH_POPULAR } from "./types";
import axios from "axios";
export const fetchPopularMovies = () => (dispatch) => {
axios
.get(
`https://api.themoviedb.org/3/movie/popular?api_key=${API}&language=en-US`
)
.then((response) =>
dispatch({
type: FETCH_POPULAR,
payload: response.data
})
)
.catch((err) => console.log(err));
};
//reducer
import { FETCH_POPULAR } from "../actions/types";
const initialState = {
popular: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_POPULAR:
return {
...state,
popular: action.payload,
};
default:
return state;
}
}
import React from "react";
import { connect } from "react-redux";
import Popular from "./Popular";
const FetchedPopular = (props) => {
const { popular } = props;
let content = "";
content =
popular.length > 0
? popular.map((item, index) => (
<Popular key={index} popular={item} />
))
: null;
return <div className="fetched-movies">{content}</div>;
};
const mapStateToProps = (state) => ({
popular: state.popular.popular,
});
export default connect(mapStateToProps)(FetchedPopular);
import React from "react";
import "../Styles.css";
const Popular = (props) => {
return (
<div className="movie-container">
<img
className="poster"
src={`https://image.tmdb.org/t/p/w400/${props.poster_path}`}
/>
</div>
);
};
export default Popular;
I cant really tell what I'm missing can someone help?
Next to mapStateToProps you need to create mapDispatchToProps. After that, you will be able to call your Redux action from your React component.
I suggest you the mapDispatchToProps as an Object form. Then you need to use this mapDispatchToProps as the second parameter of your connect method.
When you will have your action mapped to your component, you need to call it somewhere. It is recommended to do it for example on a component mount. As your React components are Functional components, you need to do it in React useEffect hook.
import React, { useEffect } from "react";
import { connect } from "react-redux";
import Popular from "./Popular";
import { fetchPopularMovies } from 'path_to_your_actions_file'
const FetchedPopular = (props) => {
const { popular } = props;
let content = "";
useEffect(()=> {
// call your mapped action (here it is called once on component mount due the empty dependency array of useEffect hook)
props.fetchPopularMovies();
}, [])
content =
popular.length > 0
? popular.map((item, index) => (
<Popular key={index} popular={item} />
))
: null;
return <div className="fetched-movies">{content}</div>;
};
const mapStateToProps = (state) => ({
popular: state.popular.popular,
});
// create mapDispatchToProps
const mapDispatchToProps = {
fetchPopularMovies
}
// use mapDispatchToProps as the second parameter of your `connect` method.
export default connect(mapStateToProps, mapDispatchToProps)(FetchedPopular);
Moreover, as I wrote above in my comment, your Popular does not have the prop poster_path but it has the prop popular which probably has the property poster_path.
import React from "react";
import "../Styles.css";
const Popular = (props) => {
return (
<div className="movie-container">
<img
className="poster"
src={`https://image.tmdb.org/t/p/w400/${props.popular.poster_path}`}
/>
</div>
);
};
export default Popular;
I made a reducer that fetches admins, and I want it to display certain admins when I call it in my reducer but I am getting Undefined.
I am still very new to redux so apologies for my mistakes.
I tried to include all the relevant folders:
App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../store/actions';
class App extends Component {
async componentDidMount() {
fetch(constants.adminUrl + '/admins/data', {
method: 'GET'
}).then((res) => {
return res.json()
}).then(async (res) => {
this.props.setAdminsInColumns(res.admins)
}).catch((error) => {
toast.error(error.message)
})
}
render() {
return (
{/* SOME CODE */}
);
}
}
let app = connect(null, actions)(App);
export default app;
columnsReducer.js
import { FETCH_ADMINS } from '../actions/types'
import { Link } from 'react-router-dom'
import constants from '../../static/global/index'
import React from 'react';
import { toast } from 'react-toastify'
const initialState = {
admins: [],
{
Header: "Responsible",
accessor: "responsibleAdmin",
style: { textAlign: "center" },
// Place where I want to fetch certain admins and get undefined
Cell: props => <span>{props.value && this.state.admins.name ? this.state.admins.find(admin => admin.id === props.value).name : props.value}</span>
}
}
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_ADMINS:
return { ...state, admins: action.admins}
default:
return state
}
}
index.js
import { FETCH_ADMINS } from "./types"
/**
* starts loader for setting admin
*/
export const setAdminsInColumns = (admins) => async dispatch => {
dispatch({ type: FETCH_ADMINS, admins })
}
types.js
export const FETCH_ADMINS = 'fetch_admins'
When I console.log(action.admins) inside the switch case FETCH_ADMINS in the columnsReducer.js file, I can see all the admin information I want, is there a way to make the state global in the columnsReducer.js file so I can read it?
Any help is appreciated!
use mapStateToProps in the connect method. like below
let mapStateToProps = (state)=>{
return {
admins :[yourcolumnsReducer].admins
}
}
let app = connect(mapStateToProps, actions)(App);
//you can use this.props.admins inside your component
MapStateToProps reference
I'm new to redux so just trying to apply redux to a very simple app. It just toggles the word whenever the button is clicked. But how should I dispatch my handleClick function except the action? For now nothing happens when I click the button.
App.js
import React, { Component } from "react";
import { connect } from 'react-redux';
import MyButton from "./MyButton";
import { handleClick } from "./actions";
import "./styles.css";
class App extends Component {
handleClick = () => {
if (this.state.text === "initial text") {
this.setState({ text: "" });
} else {
this.setState({ text: "initial text" });
}
}
render() {
return (
<div className="App">
<MyButton onClick={()=>this.props.handleClick('hi')} />
<p>{this.props.text}</p>
</div>
);
}
}
const mapStateToProps = state => ({
text: state.text
})
const mapDispatchToProps = dispatch => ({
handleClick: () => dispatch(handleClick)
})
export default connect(mapStateToProps, mapDispatchToProps)(App)
MyButton.js
import React, { Component } from "react";
class MyButton extends Component {
render() {
return <button onClick={this.props.onClick}>
Click Me!
</button>;
}
}
export default MyButton;
actions.js
export const handleClick = text => ({
type: "test_action",
payload: { ...text }
});
reducers.js
export const reducer = (state = {text:'initial_text'}, action) => {
if(action.type === 'test_action') {
return Object.assign({}, state, action.payload)
}
return state;
}
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { reducer } from "./reducers";
import App from "./App";
import "./styles.css";
const store = createStore(reducer);
const rootElement = document.getElementById("root");
ReactDOM.render(<Provider store={store}><App /></Provider>, rootElement);
You should pass an argument to your handleClick function:
const mapDispatchToProps = dispatch => ({
handleClick: (text) => dispatch(handleClick(text))
})
or just:
const mapDispatchToProps = { handleClick }
Your action is spreading a string inside an object, you should use it as-is:
export const handleClick = text => ({
type: "test_action",
payload: text
});
And your reducer is setting the whole state, instead of just the text property. You can avoid the confusion by splitting then recomining the reducer:
import { combineReducers } from 'redux'
export const text = (state='', action) => {
if(action.type === 'test_action') {
return action.payload;
}
return state;
}
export const reducer = combineReducers({
text
})
The problem is that the mapDispatchToProps handleClick prop in the above code does not accept arguments
const mapDispatchToProps = dispatch => ({
handleClick: (val) => dispatch(handleClick(val)) // update here so that the 'hi' text is passed to the action creator
})
<MyButton onClick={()=>this.props.handleClick('hi')} />
Update
The state is not updated correctly
return Object.assign({}, state, { text: action.payload }) //pass an object and not just the value
Im running into a problem with my redux to firebase connection i believe.
Trying to grab all jobs from users in firebase.
Have my store setup, action and reducer, not really sure where i am going wrong here so i must be overlooking something, nothing is showing up in console and i put a console.log call on my action and nothing shows still.
my action :
// Grab all Jobs
export const getJobs = (jobs) => ({
type: 'GET_JOBS',
jobs
});
export const startGetJobs = () => {
return(dispatch, getState) => {
const uid = getState().auth.uid;
return database.ref(`users/${uid}/jobs`)
.once('value')
.then((snapshot) => {
const jobs =[];
console.log(jobs);
//Parse the data using snapshot
snapshot.forEach((childSnapshot) => {
jobs.push({
id: childSnapshot.key,
...childSnapshot.val()
});
});
dispatch(getJobs(jobs));
});
};
};
my reducer file :
const jobReducerDefaultState = [];
export default (state= jobReducerDefaultState, action) => {
switch(action.type) {
case 'ADD_JOB':
return [
...state,
action.job
];
case 'REMOVE_JOB':
return state.filter(({ id }) => id !== action.id);
case 'EDIT_JOB':
return state.map((job) => {
if(job.id === action.id) {
return {
...job,
...action.updates
};
} else {
return job;
}
});
case 'GET_JOBS':
return action.jobs;
default:
return state;
}
};
my redux store file :
import { createStore, combineReducers, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import jobsReducer from '../reducers/jobs';
import filtersReducer from '../reducers/filters';
import authReducer from '../reducers/auth';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default () => {
const store = createStore(
combineReducers({
jobs: jobsReducer,
filters: filtersReducer,
auth: authReducer
}),
composeEnhancers(applyMiddleware(thunk))
);
return store;
};
And trying to call that with this component :
import React from 'react';
import { connect } from 'react-redux';
import JobDataItem from './JobDataItem';
import { startGetJobs } from '../actions/jobs';
class JobData extends React.Component {
ComponentDidMount() {
this.props.startGetJobs();
}
render() {
return (
<div>
{this.props.jobs.map((job) => {
return <JobDataItem key={job.id} company={job.company}/>
})}
</div>
);
};
};
const mapDispatchToProps = (dispatch) => {
return {
startGetJobs: (jobs) => dispatch(startGetJobs(jobs))
}
}
export default connect(undefined, mapDispatchToProps)(JobData);
which passes that data to the jobDataItem component to render to screen below:
import React from 'react';
import { Link } from 'react-router-dom';
const JobDataItem = ({ id, company}) => (
<div>
<Link to={`/edit/${id}`}>
<h3>{company}</h3>
</Link>
</div>
);
export default JobDataItem;
my firebase db formats like:
users/
user-uid/
jobs/
job-uid/
company:"Company Name",
jobTitle:"jobTitle:,
And so on...
Expected output is "Company Name" but nothing shows up at all. i try to just call props.jobs.length and it shows up as 0 as well.
EDITED
1. “Called startGetJobs in componentDidMount(),
2. “Changed props.jobs.map((job)... to this.props.jobs.map((job)...
I now get props is undefined error in console and nothing still appears on screen.
I am trying to learn the react and for that I am trying to create a sample todo app. I have a python flask backend which servers as REST server and react as web server.
Everything works find an I am able to show todos and delete particular todo as well. However now I have started learning Redux, and that seems really confusing.
I am not sure how to make call to my rest server. Following just returns promise, not sure how to get the data, rather than promise.
store.js
import axios from 'axios'
import { createStore } from 'redux'
export const ADD_TODO = 'ADD_TODO'
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
export const listTodo = todos => ({
type: 'LIST_TODO',
todos
})
const add_todo = (id, text) => {
return axios.post("http://localhost:5001/todos", {id:id, data:text})
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(listTodo(Response.data))
})
}
const initialState ={
todos: {},
new_todo: ''
}
function todoApp(state = initialState, action) {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
return Object.assign({}, state, {
new_todo: action.text
})
default:
return state
}
}
const store = createStore(todoApp)
export default store
app.js
import React, {Component} from 'react'
import {connect} from 'react-redux'
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo('testing')}>fetch_Data</button>
</div>
);
}
}
export default connect() (App)
index.js
ReactDOM.render(<Provider store={store}> <App /> </Provider>,
document.getElementById('root'));
Firstly, you should export the actions you have created which will then be imported and used in the components using the connect HOC.
You can dispatch the 'fetch_data' action to get the data in your component. Also, you can dispatch 'addTodo' action to add new todo in the list.
export const ADD_TODO = 'ADD_TODO';
export const GET_TODO = 'GET_TODO';
export const fetch_data = () => {
return (dispatch) => axios.get("http://localhost:5001/todos")
.then(response => {
dispatch({type: GET_TODO, todos: response.data});
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
});
Use the actions constants like ADD_TODO, GET_TODO to save or to update the redux state in reducers
const todoApp = (state = initialState, action) => {
console.log("reducer called...")
switch (action.type) {
case ADD_TODO:
const todos = {...state.todos};
todos[action.id] = action.text;
return Object.assign({}, state, {
todos: todos
});
case GET_TODO:
return Object.assign({}, state, {
todos: action.todos
});
default:
return state
}
}
Importing the actions and then call the function you have added in the 'mapDispatchToProps' to dispatch the actions.
import React, {Component} from 'react'
import {connect} from 'react-redux';
import { addTodo, fetch_data } from "../store";
class App extends Component{
render(){
return(
<div>
<button onClick={this.props.addTodo(todoId, 'testing')}>fetch_Data</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
todos: state.todoApp.todos
});
const mapDispatchToProps = (dispatch) => ({
addTodo: (id, text) => dispatch(addTodo(id, text)),
fetch_data: () => dispatch(fetch_data())
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
redux is based on actions and reducers, basically reducers are pure functions which means no side effects as for example api calls, I'd advice you read more about redux and how to use redux with redux-chunk for making api calls
You make this work like this. You need to dispatch action when you have response.
const fetch_data = () => {
return axios.get("http://localhost:5001/todos")
.then(Response=>{
store.dispatch(addTodo(Response.data));
})
}
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text: text
})