i have a problem when dispatching an action inside useEffect in functional component
in class component it is looks like this
import getAllItem from './actions/item'
export class Home extends Component {
state = { menus: [] }
componentDidMount() {
this.props.dispatch(getAllItem())
this.setState({ menus: this.props.menu })
})
}
...
...
const mapStateToProps = state => {
return { menu: state.reItem.itemList}
}
export default connect(mapStateToProps)(Home);
in console it's return an array of my menus
i want convert it into functional component ,and i've tried
import getAllItem from './actions/item'
export default function Home() {
const dispatch = useDispatch();
const menuState = useSelector(state => state.reItem.itemList)
const [menus, setMenus] = useState();
useEffect(() => {
dispatch(getAllItem())
setMenus(menuState);
}, [])
...
...
i expected my functional component have the same result as class component do ,but in console it returning
"TypeError:(0,_item.default)is not a function"
my action code is simple
export const getAllItem = () => {
return {
type: 'GET_ITEM',
payload: Axios.get(`${URL}/item/`)
}
}
and the reducer
const initialState = {
itemList: []
}
const item = (state = initialState, action) => {
switch (action.type) {
case 'GET_ITEM':
return {
...state,
itemList: action.payload.data.result
}
You are importing named exports incorrectly.
your code:import getAllItem from './actions/item'
correct import:import {getAllItem} from './actions/item'
Related
I'm a little confused on passing an object to the redux store. I have successfully created the store and can add items from the initial state. The function also fires when called
Action:
import { GET_ITEM } from './OrderTypes'
export const getItem = (payload) => {
return {
type: GET_ITEM,
payload: { payload }
}
}
Reducer:
import { GET_ITEM } from './OrderTypes'
const initialState = {
orderList: [],
}
const orderReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ITEM: return {
...state,
orderList: [...state.orderList, action.payload]
}
default: return state
}
}
export default orderReducer
Component:
class TestComponentextends Component {
pushItem = () => {
this.props.getItem({
payload: 'test object'
})
}
render() {
return (
<input type='button' value='test btn' onClick={this.pushItem} />
)
}
}
const mapStateToProps = state => {
return {
orderList: state.orderList
}
}
const mapDispatchToProps = dispatch => {
return {
getItem: () => dispatch(getItem())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TestComponent)
What happens: An empty object is added to the orderList array.
What I want to happen: Store the object in pushItem in the orderList array.
Your mapDispatchToProps doesn't pass the arguments to the action creator (see mapDispatchToProps function arguments - 2nd example):
const mapDispatchToProps = dispatch => ({
getItem: (...args) => dispatch(getItem(...args))
})
Even easier is to let react-redux handle the mapping by using mapDispatchToProps as an object:
const mapDispatchToProps = {
getItem
}
Here is the onChange event:
onChange={(event, newValue) => {
this.props.changeTabState(newValue)
}}
And here is the dispatch to Props:
const mapDispatchToProps = (dispatch) => ({
changeTabState: (newTabState) => dispatch(changeTabState(newTabState))
})
I'm getting an error called 'TypeError: redux_actions_tab_action__WEBPACK_IMPORTED_MODULE_12_ is not a function' on the line where I call this.prop.changeTabState.
Here is the initial state:
export const TAB_STATE = () => {
return{
tab: 0
}
}
export const TAB_CHANGE = "tab_action"
The Tab Action file:
import {TAB_CHANGE} from '../utils'
export const changeTabState = (newTabState) => {
return {
type : TAB_CHANGE,
payload : newTabState
}
}
The Tab Reducer file:
import { TAB_STATE, TAB_CHANGE } from '../utils'
import _ from 'lodash'
export const tabReducer = (state = TAB_STATE(), action) => {
switch (action.type) {
case TAB_CHANGE: {
let newState = _.cloneDeep(state)
newState = { ...newState, ...action.payload }
return newState
}
}
return state
}
Ok so I solved the problem by changing the way I import.
Earlier I was importing like this:
import * as changeTabState from '../../redux/actions/tab_action'
But now I import like this:
import {changeTabState} from '../../redux/actions/tab_action'
I also changed the state the way I dispatch in onChange to like this:
onChange={(event, newTabState) => {
this.props.changeTabState({tab: newTabState})
})
}}
trying to share the states from one component to another: The state can be accessed from main component but it comes undefined when accessing from a new component
This is my reducer:
export const tableReducer=(state = [], action)=> {
switch (action.type) {
case 'SELECTED_LIST':
state = JSON.parse(JSON.stringify(action.payload));
return state;
default:
return state
}
}
access it from a different file:
const [userList, usersDispatch] = useReducer(tableReducer, []);
useEffect(() => {
const list = Object.keys(selectedRowIds).length > 0 ? selectedFlatRows.map(
d => d.original.email
)
: '';
usersDispatch({ type: 'SELECTED_LIST', payload: list, });
}, [selectedRowIds, selectedFlatRows]);
and in a new component:
const [userList] = useReducer(tableReducer);
const deleteUsers = () => {
console.log(userList)
}
but here console.log(userList) it results to undefined
For Sharing of state between components, you can use Context API with useReducer.
Context API provides a neat way of providing state to child components without ending up with a prop drilling situation. It requires that a Provider is setup, which provides its values to any of its Consumers. Any component that is a child of the Provider can consume the context.
First a piece of context is created.
CustomContext.js
import React from 'react';
const CustomContext = React.createContext();
export function useCustomContext() {
return React.useContext(CustomContext);
}
export default CustomContext;
We can define your reducer in a seperate file.
TableReducer.js
export const tableReducer=(state = [], action)=> {
switch (action.type) {
case 'SELECTED_LIST':
state = JSON.parse(JSON.stringify(action.payload));
return state;
default:
return state
}
}
next is to implement the provider, and give it a value within a "Parent" component (A higher up component)
Parent.js
import CustomContext from './CustomContext'
import { tableReducer } from './TableReducer'
const ParentComponent = () => {
const [userState, usersDispatch ] = React.useReducer(tableReducer, []);
const providerState = {
userState,
usersDispatch
}
return (
<CustomContext.Provider value={providerState} >
<ChildComponent /> //Any component within here can access value by using useCustomContext();
</CustomContext.Provider>
)
}
now any component nested within <CustomContext.Provider></CustomContext.Provider> can access whatever is passed into "value" prop of the Provider which is your context state and the dispatch method.
The child component will look like this (I have ommited your state values and such..)
Child.js
import { useCustomContext }from './CustomContext'
const ChildComponent = (props) => {
//your custom state variables and other methods
const { userState, usersDispatch } = useCustomContext();
useEffect(() => {
const list = Object.keys(selectedRowIds).length > 0 ? selectedFlatRows.map(
d => d.original.email
)
: '';
usersDispatch({ type: 'SELECTED_LIST', payload: list, });
}, [selectedRowIds, selectedFlatRows]);
return(
<div>your components dependent on selectedRowIds, selectedFlatRows<div>
)
}
You can't share the state with useReducer hook like you are trying to. Each call to useReducer returns a new state that is managed using the reducer function passed to useReducer hook.
Just as each call to useState returns a different state, each call to useReducer returns a different state. Two useReducer calls can't share the same state.
To share the state, you can use one of the following options:
Context API
React-Redux
Pass the state from parent component to child component using props
#Gandzal is correct but I found it was lacking a typscript version and also today createContext requieres a default parameter. This came up as one of the top answers on google so I thought I would share.
I setup my solution like this:
Custom context:
import React, {Dispatch} from 'react';
import {StateType, Action} from './reducer'
interface IContextProps {
state: StateType;
dispatch:Dispatch<Action>
}
const CustomContext = React.createContext({} as IContextProps);
export function useCustomContext() {
return React.useContext(CustomContext);
}
export default CustomContext;
Note StateType and Action:
export type StateType = {
items: Array<DataItems>;
date: Date;
};
export type Action = {
type: ActionKind;
payload: DataItems;
};
reducer:
export const reducer = (state: StateType, action: Action) => {
const { type, payload } = action;
let newArray: Array<DataItems> = [];
switch (type) {
case ActionKind.Checked:
newArray = state.items.map((item) => ({
...item,
checked: item.id === payload.id ? true : item.checked,
}));
return {
...state,
items: newArray,
}
default:
return state;
}
};
App.tsx:
import { reducer, initalState } from 'Shared/Reducer/reducer';
import CustomContext from 'Shared/Reducer/CustomContext';
const App: React.FC = () => {
const [state, dispatch] = React.useReducer(reducer, initalState);
const providerState = {
state,
dispatch,
};
return (
<CustomContext.Provider value={providerState}>
<main role="main">
// your components
</main>
</CustomContext.Provider>
);
};
export default App;
And one of your components:
import { useCustomContext } from 'Shared/Reducer/CustomContext';
export const MyComp: React.FC<MyType> = (props) => {
const { data} = props;
const { state, dispatch } = useCustomContext(); --- Your state and dispatch here
return (
<div>
// your component
</div >
);
}
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);
**Hello! my problem is my state is not uploading, is always empty altough my actions brings data correct. Can anyone give me some help of what am I doing wrong ?
I think is something with the name or the combine reducers part.
Maybe I am not accesing data correct with my reducer or something like that **
The object I receive from the api call has this format {categories: Array(4), items: Array(50)}
Component
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ItemList from '../components/ItemList/ItemList';
import { getItems } from './actions'
class ItemListContainer extends PureComponent {
async componentDidMount() {
const { getItems } = this.props;
await getItems()
console.log(this.props)
}
render() {
return <ItemList />;
}
}
const mapStateToProps = state => (
{
items: state.items.items,
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
getItems,
},
dispatch,
);
export default connect(mapStateToProps, mapDispatchToProps)(ItemListContainer);
actions.js
export const GET_ITEMS = 'GET_ITEMS';
export const GET_ITEM = 'GET_ITEM';
export const GET_ITEM_DESCRIPTION = 'GET_ITEM_DESCRIPTION';
export function getItems(query) {
return async function (dispatch) {
// const res = await fetch(`http://localhost:3000/api/items?q=${query}`)
const res = await fetch(`http://localhost:3000/api/items?q=ipad`)
const items = await res.json()
return dispatch({
type: 'GET_ITEMS',
items: items.items,
})
}
}
reducer.js
import { GET_ITEMS } from './actions';
const initialState = {
items: [],
itemsLoaded: false,
};
export default function(state = initialState, action) {
const { type, data } = action;
switch (type) {
case GET_ITEMS:
return {
...state,
items: data,
itemsLoaded: true,
};
default: {
return {
...state
}
}
}
}
I was accessing { data} in the reducer which of course it was empty. The correnct action was items.