I'm a beginner of react & react-native.
I'm using react 16, react-thunk, react-redux.
I'm trying to fetch categories that I already made from firestore.
At first, I called action using connect(), and then, I typed action using thunk also fetched data from firestore.
Finally, I returned new states in reducer.
Definitely, I'm not aware of redux process, so please give some tips.
Here's my code. Thank you.
CategoryImageList.js (Component)
...
class CategoryImageList extends Component {
componentWillMount() {
this.props.getCategory();
}
renderImages() {
return this.state.categories.map(category =>
<CategoryImageCard key={category.imgName} category={category}/>
);
}
render() {
return (
<ScrollView>
{/*{this.renderImages()}*/}
</ScrollView>
);
}
}
export default connect(null, {getCategory})(CategoryImageList);
category.js (action)
...
export const getCategory = () => {
return (dispatch) => { //using redux-thunk here... do check it out
getCategories()
.then(querySnapshot => {
const test = [];
querySnapshot.forEach((doc) => {
test.push(
{
imgName : doc.data()['imgName'],
name : doc.data()['name']
});
});
dispatch({ type: GET_CATEGORY, payload: test} );
});
};
};
CategoryReducers.js (reducer)
...
const categoryInitialState = {
name: [],
imgName: []
}
export const CategoryReducer = (state = categoryInitialState, action) => {
switch (action.type) {
case GET_CATEGORY:
console.log(action);
return { ...state, categoryImg: {
name: action.payload.name,
imgName: action.payload.imgName
}};
default:
return state;
}
}
App.js
...
type Props = {};
export default class App extends Component<Props> {
render() {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider store={store}>
<View style={{flex:1}}>
<Header headerText={'FoodUp'}/>
<CategoryImageList />
</View>
</Provider>
);
}
}
reducers/index.js
import { combineReducers } from 'redux';
import { CategoryReducer } from './CategoryReducer';
export default combineReducers({
categories: CategoryReducer
});
UPDATED
Firebase.js
const config = {
...
};
firebase.initializeApp(config);
const db = firebase.firestore();
const storage = firebase.storage();
const settings = {timestampsInSnapshots: true};
db.settings(settings);
export const getCategories = () => {
return db.collection('categories').get();
}
export const getCategoryImg = (categoryName, imgName) => {
const ref = storage.ref(`category/${categoryName}/${imgName}`);
return ref.getDownloadURL();
}
You have to add mapstateToProps to your connect like,
const mapStateToProps = (state: any) => {
return {
name: state.categories.name,
imageName:state.categories.imageName
};
}
export default connect(mapStateToProps)(CategoryImageList)
And then, you will be able to access the name and image name like,
this.props.name and this.props.imageName
Edit: To dispatch GET_CATEGORY you can either use mapDispatchToProps or do the getCategory and dispatch from within your component like,
import {getCategory} from './category'
componentWillMount() {
this.props.getCategory(this.props.dispatch);
}
and change the getCategory function as,
export const getCategory = (dispatch) => {
...
dispatch({ type: GET_CATEGORY, payload: test} );
...
}
mapStateToProps has the Store state as an argument/param (provided by react-redux::connect) and its used to link the component with the certain part of the store state. in your case, you can use like this. and you can use name, imgName as a props in your component
const mapStateToProps = ({categories}) => {
const { name, imgName } = categories;
return {name, imgName};
};
export default connect(mapStateToProps, {getCategory})(CategoryImageList);
Related
I created an action creator that is simply supposed to make a get request to my API and return with a list of all projects. However, for some reason, my return dispatch in my thunk function is not firing at all. It gets to the console.log() statement and just ends. There are no consoles errors, and no network calls being made either as far as I can tell. Any ideas why it would do absolutely nothing?
Dashboard.js (component)
import ProjectItem from "../Project/ProjectItem";
import styles from "./Dashboard.module.css";
import CreateProjectButton from "../CreateProjectButton/CreateProjectButton";
import { connect } from "react-redux";
import { getProjects } from "../../Redux/getProjects/actions";
const Dashboard = props => {
useEffect(() => {
console.log("blah");
getProjects();
}, []);
return (
<div className={styles.dashboardContainer}>
<h1>Projects</h1>
<br />
<CreateProjectButton />
<br />
<hr />
<ProjectItem />
</div>
);
};
const mapStateToProps = state => {
return {
projects: state
};
};
const mapDispatchToProps = dispatch => {
return {
getProjects: () => dispatch(getProjects())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
action.js (action creator)
import { GET_PROJECTS_SUCCESS, GET_PROJECTS_ERROR } from "./constants";
export const getProjectsSuccess = payload => {
console.log("getProjectSuccess", payload);
return {
type: GET_PROJECTS_SUCCESS,
payload
};
};
export const getProjectsError = () => {
console.log("there was an error");
return {
type: GET_PROJECTS_ERROR
};
};
export function getProjects() {
console.log("getProject");
return dispatch => {
axios
.get("/project/all")
.then(res => dispatch(getProjectsSuccess(res.data)))
.catch(err => dispatch(getProjectsError(err)));
};
}
index.js (getProject reducer)
const initialState = {
projects: [], //array of projects
project: {}, // single project for update case
reRender: false
};
const getProjectsReducer = (state = initialState, action) => {
switch (action.type) {
case GET_PROJECTS_SUCCESS:
return { ...state, projects: action.payload }; // will need to change action.payload later on
default:
return state;
}
};
export default getProjectsReducer;
constants.js
export const GET_PROJECTS_SUCCESS = "GET_PROJECTS_SUCCESS";
export const GET_PROJECTS_ERROR = "GET_PROJECTS_ERROR";
rootReducer.js
import createProjectReducer from "./createProject/index";
import getProjectsReducer from "./getProjects/index";
const rootReducer = (state = {}, action) => {
return {
project: createProjectReducer(state.project, action),
projects: getProjectsReducer(state.projects, action)
};
};
export default rootReducer;
FIXED: After reading up on the use effect hook in functional components I realized I was missing props.getProjects in the useEffect function in dashboard.js!
My problem is that mapStateToProps returns undefined. Maybe I have some problems with dispatching in the state or maybe app rendering before data comes from the server? I can't understand. So app works right without redux with just componentDidMount, but I have some problems with redux
So I have a top-level component App:
const App = () => {
return (
<Provider store={store}>
<Screen />
</Provider>
)
}
I have store with thunk meddleware:
const store = createStore(reducer, applyMiddleware(ReduxThunk));
Two types of action:
export const fetchData = (newPhotos) => async (dispatch) => {
function onSuccess(success) {
dispatch({
type: FETCH_DATA,
payload: success})
return success
}
function onError(error) {
dispatch({type: FETCH_FAILED, error})
}
try {
const URL = 'https://api.unsplash.com/photos/?client_id=cf49c08b444ff4cb9e4d126b7e9f7513ba1ee58de7906e4360afc1a33d1bf4c0';
const res = await fetch(URL);
const success = await res.json();
console.log(success);
return onSuccess(success);
} catch (error) {
return onError(error)
}
};
reducer:
const initialState = {
data: []
}
export default dataReducer = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case FETCH_DATA:
return {
data: action.payload
}
case FETCH_FAILED:
return {
state
}
default: return state;
}
}
combine reducers:
export default combineReducers({
fetchedData: dataReducer
});
and my rendering component:
class HomeScreen extends Component {
render() {
console.log(this.props)
const {navigation, data} = this.props;
return (
<ScrollView>
<Header />
<ImageList navigation={navigation} data={data}/>
</ScrollView>
)
}
}
const mapStateToProps = state => {
return {
data: state.fetchedData.data
}
}
export default connect(mapStateToProps, {fetchData})(HomeScreen);
fetchData action will not be called on its own. You need to call that explicitly(in componentDidMount and, probably, componentDidUpdate) like
class HomeScreen extends Component {
componentDidMount() {
this.props.fetchData(/* I don't know where you are going to take newPhotos argument */);
}
render() {
//...
}
}
I'm learning Redux state management and got stuck with an issue. The mapStateToProps within a component is not triggered when the state changes. Gone through a lot of blogs, couldn't able to figure out the problem.
The store seems to update properly, as the "subscribe" method on store prints new changes. Attached screenshot as well.
Actions.js
export const GET_ITEMS_SUCCESS = "GET_ITEMS_SUCCESS";
export const GET_ITEMS_FAILURE = "GET_ITEMS_FAILURE";
export const getItemsSuccess = (items) => ({
type: GET_ITEMS_SUCCESS, payload: items
});
export const getItemsFailure = (error) => ({
type: GET_ITEMS_FAILURE, error: error
});
export function getItems(dispatch) {
return dispatch => {
fetch(myList)
.then(res => res.json())
.then(res => {
if(res.error) {
throw(res.error);
}
store.dispatch(getItemsSuccess(res));
return res;
})
.catch(error => {
store.dispatch(getItemsFailure(error));
})
}
}
Reducer
let initialState = {items: [], error: null}
function GetItemsReducer (state = initialState, action) {
switch (action.type) {
case GET_ITEMS_SUCCESS:
return Object.assign({}, state, {pending: false, items: action.payload});
case GET_ITEMS_FAILURE:
return Object.assign({}, state, {pending: false, error: action.error});
default:
return state;
}
}
export default const rootReducer = combineReducers({
GetItemsReducer: GetItemsReducer
});
Store
const mystore = createStore(rootReducer, applyMiddleware(thunk, promise));
mystore.subscribe(() => console.log("State Changed;", mystore.getState()));
Component
class Home extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchItems();
}
render() {
return (
<div>{this.props.items.length}</div>
)
}
}
const mapStateToProps = (state) => {
console.log('mapStateToProps ----------> ', state);
return {
items: state.GetItemsReducer.items,
error: state.GetItemsReducer.error
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchItems: bindActionCreators(getItems, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Main
class App extends React.Component {
render() {
return (
<Provider store={mystore}>
<Home />
</Provider>
)
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
Thanks in advance.
Have a component to display user information. However, when the user logouts out, and shouldn't be in the store anymore ( I have set a dispatch up for this as well). Also, I am able to reload the entire page and then the user information displays. I have been having a go with componentDidUpdate and componentDidMount but I can't seem to figure it out.
Here is the view component:
// import React from "react";
// import { connect } from "react-redux";
// import { getUser } from "../store/actions/userActions";
// import { withRouter } from 'react-router-dom';
import React from 'react';
import { connect } from 'react-redux';
import * as actions from '../store/actions/auth';
class UserDetailView extends React.Component {
componentDidMount() {}
shouldComponentUpdate(nextProps, props) {
console.log(nextProps);
const username = this.props.user.username;
console.log(username);
if (username !== nextProps.username) {
console.log(username);
return true;
} else {
return false;
}
}
render() {
const user = this.props.user;
return (
<div>
{this.props.user ? (
<div>
<h3>{user.username}</h3>
{this.props.user.email}
</div>
) : (
<h3>Not Logged In</h3>
)}
</div>
);
}
}
const mapStateToProps = state => ({
username: state.username,
user: state.user
});
const mapStateToDispatch = dispatch => ({
onTryAutoSignup: () => dispatch(actions.authCheckState()),
getfetchUser: id => dispatch(actions.fetchUser(id))
});
export default connect(
mapStateToProps,
mapStateToDispatch
)(UserDetailView);
// class UserDetailView extends React.Component {
// componentDidMount() {
// const { getUser, userID } = this.props
// getUser(userID) //fixed
// }
// render() {
// console.log(this.props.userID)
// console.log(this.props.user)
// return (
// <ul>
// {this.props.user.map(user =>
// <li key={user.id}>{user.username}</li>
// )}
// </ul>
// );
// }
// }
// const mapStateToProps = (state, ownProps) => ({
// user: state.user,
// userID: ownProps.match.params.userID,
// });
// const mapDispatchToProps = dispatch => ({ //added
// getUser: (userID) => dispatch(getUser(userID))
// })
// export default withRouter(connect(mapStateToProps, {getUser})(UserDetailView)); //fixed
Reducer:
const getUserInformation = (state, action) => {
return Object.assign({}, state, {
user: action.payload.user
});
};
Action Generator and Action
export const authSuccess = (token, username) => {
return {
type: actionTypes.AUTH_SUCCESS,
token: token,
username: username
};
};
export const fetchUser = username => {
return dispatch => {
return axios
.get(`http://127.0.0.1:8000/api/user/${username}/`)
.then(res => {
const user = res.data;
dispatch(getUserInformation(user));
});
};
};
I see no reason to override shouldComponentUpdate, just inherit from React.PureComponent.
You have some mix-ups in action creators and reducers. It should be something like this:
dispatch(setUserInformation(user)); // dispatch action
const setUserInformation = ({ type: 'SET_USER_INFORMATION', user }); // this is the action creator, returns an object with the type and the payload
const reducer = (state, action) { // this is the reducer
switch (action.type) {
case 'SET_USER_INFORMATION':
return {
...state,
user: action.user
}
}
}
I'm attempting to reconfigure a PixaBay clone application to Redux. The application retrieves photos as the user types a search text. However, it breaks as soon as I type inside the input.
From what I've researched, you can only call setState in a class so I gave fetchPhotos an arrow function, but that didn't work. I also tried to .bind(this), but that gave me a parsing error. Could someone kindly tell me what I'm doing wrong? Here are the following errors, along with my code.
ERRORS
TypeError: this.setState is not a function
fetchPhotos
src/actions/actions.js:10
7 |
8 | export function fetchPhotos(e) {
9 | const url = `${ROOT_URL}/?key=${API_KEY}&q=${searchText}&image_type=photo`;
> 10 | const request = this.setState({searchText: e.target.value}, () => {
11 | axios.get(url)
12 | .then(response => {
13 | this.setState({images: response.data.hits});
fetchPhotos
node_modules/redux/es/redux.js:475
Search._this.FetchPhotosHandler [as onChange]
src/components/search/Search.js:11
8 | class Search extends Component {
9 |
10 | FetchPhotosHandler = (e) => {
> 11 | this.props.fetchPhotos(e);
12 | }
13 |
14 | render() {
SEARCH CONTAINER
import React, { Component } from 'react';
import { fetchPhotos } from '../../actions/actions';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import TextField from 'material-ui/TextField';
import ImageResults from '../imageResults/ImageResults';
class Search extends Component {
state = {
searchText: '',
images: []
}
FetchPhotosHandler = (e) => {
this.props.fetchPhotos(e);
}
render() {
return (
<div>
<TextField
name="searchText"
value={this.props.searchText}
onChange={this.FetchPhotosHandler}
floatingLabelText="Search for photos"
fullWidth={true} />
<br />
<ImageResults images={this.props.images} />
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchPhotos, dispatch});
}
export default connect(null, mapDispatchToProps)(Search);
ACTION
import axios from 'axios';
export const FETCH_PHOTOS = 'FETCH_PHOTOS';
const ROOT_URL = 'https://pixabay.com/api';
const API_KEY = '10264275-868d83de96a4d0c47db26f9e0';
const searchText = '';
export function fetchPhotos(e) {
const url = `${ROOT_URL}/?key=${API_KEY}&q=${searchText}&image_type=photo`;
const request = this.setState({searchText: e.target.value}, () => {
axios.get(url)
.then(response => {
this.setState({images: response.data.hits});
})
.catch(error => {
console.log(error)
});
});
return {
type: FETCH_PHOTOS,
payload: request
};
}
REDUCER
import { FETCH_PHOTOS } from '../actions/actions';
const initialState = {
searchText: '',
images: []
}
const reducer = (state = initialState, action) => {
switch(action.type) {
case FETCH_PHOTOS:
return {
...state,
images: action.data.hits
};
default:
return state;
}
}
export default reducer;
You should avoid attempting to use setState() in your action as it goes against Redux entirely. setState() is meant for managing the local of a React.Component. As you are attempting to utilize Redux, you should instead dispatch actions from your actions creators that update the store via your reducers and finally mapping store values to your component's props via connect(). Below is an example of your code restructured similar to the Async Redux example.
Instead of attempting to call setState() in the action, instead an action is dispatched containing the image payload. The Search component utilizes mapStateToProps (1st argument of connect()) to map store properties such the images array to the component's props. These props are used to render a list of data. This completely eliminates the need to have an images local state property on Search as values are being retrieved from the store as changes happen via actions/reducers. This example uses redux-thunk middleware to handle async actions, but there are plenty of other options out there that you could consider.
store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const middleware = [ thunk ];
const store = createStore(
rootReducer,
applyMiddleware(...middleware)
);
export default store;
actions:
export const FETCH_PHOTOS = 'FETCH_PHOTOS';
export const RECEIVE_PHOTOS = 'RECEIVE_PHOTOS';
// fake data
const testPhotos = [
{ id: 1, src: 'https://placehold.it/250' },
{ id: 2, src: 'https://placehold.it/250' }
];
// fake API call as promise
const getTestPhotos = () => {
return new Promise((resolve) => {
setTimeout(() => {
return resolve(testPhotos);
}, 500);
});
}
const fetchPhotos = (searchText) => ({
type: FETCH_PHOTOS
});
const receivePhotos = (photos) => ({
type: RECEIVE_PHOTOS,
data: {
hits: photos
}
});
export const searchPhotos = (searchText) => dispatch => {
// dispatch an action to handle loading/waiting for API response
dispatch(fetchPhotos(searchText));
// dispatch another action with payload within then()
return getTestPhotos()
.then(photos => dispatch(receivePhotos(photos)));
}
reducer:
import { FETCH_PHOTOS, RECEIVE_PHOTOS } from '../actions';
const initialState = {
loading: false,
searchText: '',
images: []
}
const photos = (state = initialState, action) => {
switch(action.type) {
case FETCH_PHOTOS:
return {
...state,
loading: true
};
case RECEIVE_PHOTOS:
return {
...state,
loading: false,
images: action.data.hits
};
default:
return state;
}
}
export default photos;
Search:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { searchPhotos } from './actions';
class Search extends Component {
constructor(props) {
super(props);
this.state = {
searchText: ''
};
this.fetchPhotosHandler = this.fetchPhotosHandler.bind(this);
}
fetchPhotosHandler(e) {
const { value: searchText } = e.target;
this.setState({ ...this.state, searchText }, () => {
this.props.dispatch(searchPhotos(e));
})
}
render() {
const { loading, images } = this.props;
return (
<div>
<h1>Search</h1>
<div>
<label htmlFor="search">Search:</label>
<input name="search" id="search" type="text" value={this.state.searchText} onChange={this.fetchPhotosHandler} />
</div>
{loading ? (
<div>Loading...</div>
) : (
<ul>
{images.map(image => <li key={image.id}>{image.src}</li>)}
</ul>
)}
</div>
);
}
}
const mapStateToProps = ({ photos: { loading, images } }) => ({ loading, images });
export default connect(mapStateToProps)(Search);
I've created an example to show this functionality in action at a basic level.
Hopefully that helps!
You can bind component class instance to your action and it should work.
FetchPhotosHandler = (e) => {
this.props.fetchPhotos.bind(this)(e);
}
Since you have fetchPhotos exported from different module and in order to do setState there you need to pass this context to fetchPhotos as a param and use the param to do setState. That's how this context will be available
Pass this to fetchPhotos as a param
FetchPhotosHandler = (e) => {
this.props.fetchPhotos(e, this);
}
And here access this and do seState
export function fetchPhotos(e, this) {
const url = `${ROOT_URL}/?key=${API_KEY}&q=${searchText}&image_type=photo`;
const request = this.setState({searchText: e.target.value}, () => {
axios.get(url)
.then(response => {
this.setState({images: response.data.hits});
})
.catch(error => {
console.log(error)
});
});
return {
type: FETCH_PHOTOS,
payload: request
};
}