react-native redux repopulate the state - react-redux

That's the component in question. Before the component is mounted, it successfully dispatches an action {this.props.populateGrid()}. Everything is fine, I can see the state in the logger (basically it's a nested array of random numbers). When I press the button, it should rehydrate the state with new random numbers. Yet, I get the following error: Cannot read property 'populateGrid' of undefined.
import React, { Component, PropTypes } from 'react';
import { View, StyleSheet, Button } from 'react-native';
import Grid from './Grid';
import * as globalStyles from '../styles/global';
export default class Body extends Component {
componentWillMount() {
this.refresh();
}
refresh() {
this.props.populateGrid();
}
render() {
return (
<View style={styles.body}>
<Grid inGrid={this.props.grid} />
<Button
onPress={this.refresh}
title={'Regenerate the Grid'}
/>
</View>
);
}
}
Container:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { listNumbers, pickNumber } from '../actions/numberActions';
import { populateRow, populateGrid } from '../actions/gridActions';
import Body from '../components/Body';
const mapStateToProps = state => ({
numbers: state.numbers,
grid: state.grid
});
const mapDispatchToProps = dispatch => (
bindActionCreators({
listNumbers,
pickNumber,
populateRow,
populateGrid
}, dispatch)
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Body);
Action:
import { POPULATE_ROW, POPULATE_GRID } from './actionTypes';
import { randNumbers, randGrid } from '../utils/generators';
export const populateRow = (n) => {
return {
type: POPULATE_ROW,
payload: randNumbers(n)
};
};
export const populateGrid = () => {
return {
type: POPULATE_GRID,
payload: randGrid()
};
};
reducer:
import { POPULATE_ROW, POPULATE_GRID } from '../actions/actionTypes';
export default (state = [], action = {}) => {
switch (action.type) {
case POPULATE_ROW:
return action.payload || [];
case POPULATE_GRID:
return action.payload || [];
default:
return state;
}
};
Generators of numbers (it's the second function in this case)
export const randNumbers = (n) => {
let numbers = new Array(n);
const shuffled = [];
// fill one array with the numbers 1-10
numbers = numbers.fill(1).map((_, i) => i + 1);
// shuffle by taking a random element from one array
// and pushing it to the other array
while (numbers.length) {
const idx = numbers.length * Math.random() | 0; // floor trick
shuffled.push(numbers[idx]);
numbers.splice(idx, 1);
}
return shuffled;
};
export const randGrid = () => {
const shuffled = randNumbers(6);
const array = shuffled.map(a => {
let r = new Array(6);
r = [a, ...randNumbers(5)];
return r;
});
return array;
};

I think you need to bind this to your refresh method in your onClick handler, so that this is set properly when refresh executes:
<Button
onPress={this.refresh.bind(this)}
title={'Regenerate the Grid'}
/>
Hope that helps!

Related

[updated]Intergrating NextJS and Redux State Management

this my updated version of intergrating redux and NextJS. Just to elobarate what I have done so far...
STEP 1. I've created a store.js file to set up my global store in reference to github's explanation from nextJS developers.
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import thunkMiddleware from 'redux-thunk';
import { customerListReducer } from './customerReducers';
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
const combinedReducer = combineReducers({
customerList: customerListReducer,
});
const reducer = (state, action) => {
console.log('Just Displaying the Store', state);
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
if (state.count) nextState.count = state.count; // preserve count value on client side navigation
return nextState;
} else {
return combinedReducer(state, action);
}
};
// create a makeStore function
const store = () =>
createStore(
reducer,
bindMiddleware([thunkMiddleware])
);
// export an assembled wrapper
export const wrapper = createWrapper(store);
STEP 2: Imported the wrapper above in my _app file to make the wrapper available across all pages in my application
import Nav from '../components/Nav';
import {wrapper} from '../reducers/store';
function MyApp({ Component, pageProps }) {
return (
<>
<Nav />
<Component {...pageProps} />
</>
);
}
export default wrapper.withRedux(MyApp);
STEP 3: CONFIGURATIONS
A) My Action that calls external API
import axios from 'axios';
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
export const listCustomers = () => async (dispatch) => {
try {
dispatch({
type: CUSTOMER_LIST_REQUEST,
});
const { data } = await axios.get(
'https://byronochara.tech/gassystem/api/v1/customers'
);
const result = data.results;
dispatch({
type: CUSTOMER_LIST_SUCCESS,
payload: result,
});
} catch (error) {
dispatch({
type: CUSTOMER_LIST_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
B)My Action Reducer
import {
CUSTOMER_LIST_REQUEST,
CUSTOMER_LIST_SUCCESS,
CUSTOMER_LIST_FAIL,
} from '../constants/customerConstants';
import { HYDRATE } from 'next-redux-wrapper';
export const customerListReducer = (state = { customers: [] }, action) => {
switch (action.type) {
case HYDRATE:
return { loading: true, customers: [] };
case CUSTOMER_LIST_REQUEST:
return { loading: true, customers: [] };
case CUSTOMER_LIST_SUCCESS:
return {
loading: false,
customers: action.payload,
};
case CUSTOMER_LIST_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
C)The finally bringing it all together in my index.js page to display the results:
import React, { useEffect } from 'react';
import Head from 'next/head';
import { useSelector} from 'react-redux';
import { listCustomers } from './../actions/customerActions';
import { wrapper } from '../reducers/store';
import styles from '../styles/Home.module.css';
const Home = () => {
//Select the loaded customers' list from central state
const customerList = useSelector((state) => {
console.log(state);
return state.customerList;
});
const { loading, error, customers } = customerList;
//displaying the customers data from the external API
console.log('Fetched Customers Data', customers);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{/* {loading && <h6>Loading...</h6>} */}
{/* {error && <h6>Error Occured...</h6>} */}
{/* {customers.map((customer) => (
<h3>{customer.customerName}</h3>
))} */}
{/* <ArticleList customers={customers} /> */}
</div>
);
};
// getStaticProp at build time
// getServerSideProp at every request slower
// getStaticPath to dynamically generate paths based on the data we are fetching
export const getStaticProps = wrapper.getServerSideProps(async ({ store }) => {
// console.log('STORE', store);
store.dispatch(listCustomers());
});
export default Home;
COMMENT ON THE PROBLEM I'M FACING FROM THE ABOVE CODE: once everything has been set up if you follow the code above, the code seems to run well the store is successfully created when I log the result on the console ``{ customerList: { loading: true, customers: [] } }. But then I guess this is the result from the HYDRATE action type since it will always be dispatch since am using getStaticProps``` that creates a new store instance in the server.
MAIN QUIZ: My challenge is how do I bypass the HYDRATED action and reconcile the server side state with the client side store and persist it and at least to finally be able to view the list from the external API. Thanks in advance. :)
I totally recommend you to use reduxjs/toolkit. It's very simple , less code, no wrappers, clean. And no matter your project on nextjs or created via CRA. Also you dont need to configure redux-thunk and redux-devtools cause they are enabled by default. Read documentation for more information ( how to persist state without any npm package and so on )
Here is a little example.
store.js
import { combineReducers, configureStore } from "#reduxjs/toolkit";
import userSlice from './user.slice.js';
//reducers
const rootReducer = combineReducers({
user: userSlice
});
const store = configureStore({
reducer: rootReducer,
});
export default store;
Wrap with Provider (in your case _app.js)
<Provider store={store}>
<Component {...pageProps} />
</Provider>
user.slice.js ( action + reducer )
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
id: '',
email: '',
roles: []
};
// export async action
export const signIn = createAsyncThunk('user/signIn', async (data) => {
try {
const payload = await api.auth.signin(data).then((res) => res.data);
// do some stuff if you want
return payload ;
} catch (err) {
console.log(err.response);
}
});
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
removeUser(state, payload) {
//cant be an async method
return initialState;
},
extraReducers: (builder) => {
builder.addCase(signIn.fulfilled, (state, { payload }) => {
// payload from the async method above (asyncThunk)
return payload;
});
},
},
});
// export actions
export const { removeUser } = userSlice.actions;
// export reducer
export default userSlice.reducer;
Thats it. Last step to call actions from any component e.g.
import { useDispatch, useSelector } from 'react-redux';
import { signIn, removeUser } from '../actions/userSlice';
// in function component
// call hooks
const dispatch = useDispatch();
// read the store
const { user } = useSelector((state) => state);
// dispatch any action , example below
dispatch(signIn(userCredentials));
// or
dispatch(removeUser());
I has an Issue with setting Redux with NextJS and this is my final answer after some insight from mirik999 too.
A. my store.
import { configureStore } from '#reduxjs/toolkit';
//importing the slice file with sliced reducers
import customerListReducer from '../slices/customerSlice';
// const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware));
const store = configureStore({
reducer: {
customerList: customerListReducer,
},
});
export default store;
B. The store is provided in my app component
function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<Nav />
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
C. The Slice file that automatically creates action creators and the reducer
import { createSlice } from '#reduxjs/toolkit';
//creating and action that calls API from a REST API backend
export const customersFetchedList = createAsyncThunk(
'customersList/customersListSuccess',
async () => {
try {
const { data } = await axios.get(
'https://example.com/api/your/endpoint'
);
const result = data.results;
//the payload
const payload = result;
return payload;
} catch (error) {
console.log(error.response);
const payload =
error.response && error.response.data.message
? error.response.data.message
: error.message;
return payload;
}
}
);
const initialState = {
loading: true,
customers: [],
error: false,
};
const customerListSlice = createSlice({
name: 'customersList',
initialState,
reducers: {
//reducer functions we've provided
customersRequest(state, action) {
if (state.loading == true) {
return state;
}
},
},
extraReducers: (builder) => {
initialState,
builder.addCase(customersFetchedList.fulfilled, (state, action) => {
state.loading = false;
state.customers = action.payload;
state.error = false;
return state;
});
},
});
export const {
customersRequest,
customersLoadingError,
} = customerListSlice.actions;
export default customerListSlice.reducer;
D. Then finally fired this action above in my component using the useEffect()
import React, { useEffect } from 'react';
import Head from 'next/head';
const Home = () => {
//method to fire the action
const dispatch = useDispatch();
//Select the loaded customers' list from central state
const customerList = useSelector((state) => state);
// const { loading, error, customers } = customerList;
useEffect(() => {
dispatch(listCustomers());
}, []);
return (
<div className={styles.container}>
<Head>
<title>Home | Next</title>
</Head>
<h1>Welcome to Home Page</h1>
{loading && <h6>Loading...</h6>}
{error && <h6>Error Occured...</h6>}
{customers.map((customer) => (
<h3>{customer.customerName}</h3>
))}
</div>
);
};
Thanks so much for your contribution. :)

Redux property increment not working, if call through child component

I have flatlist having images, videos. for images, I defined duration to show and then move to the next item in flatlist, but in case of the video once a video ended then move to next item.
I am using Redux for currentlyPlayingIndex and flatlist datasource.
If I have only images in flatlist it is working fine, but if I have a video, on video end I need to pass event from child to its parent. The parent calls the same method to move to the next index as for image duration end but the increment of currentlyPlayingIndex by one is not happening in case of video end.
parent component code or flatlist handler
import React, { Component } from 'react'
import { TouchableWithoutFeedback, View,StatusBar,Dimensions,FlatList } from 'react-native'
import {FileType,getFileType,getFileExtension} from '../services/FileManagerService'
import VideoPlayer from '../components/PlayerTypes/VideoPlayer'
import ImagePlayer from '../components/PlayerTypes/ImagePlayer'
import PdfPlayer from '../components/PlayerTypes/PdfPlayer'
import { ScaledSheet } from 'react-native-size-matters';
import NothingTpPlay from '../components/PlayerTypes/NothingTpPlay'
//redux
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../database/actions/ScheduleActions.js'; //Import your actions
import * as Animatable from 'react-native-animatable';
import constants from '../config/constants'
import KeepAwake from 'react-native-keep-awake';
import AudioPlayer from '../components/PlayerTypes/AudioPlayer'
import { showToastMessage } from '../utils/ToastMessage'
import I18n from "../locales/i18n-js";
import WebsitePlayer from '../components/PlayerTypes/WebsitePlayer'
import FullScreen from "../NativeBridgingHeader/FullScreen";
let deviceWidth = Dimensions.get('window').width
let deviceHeight = Dimensions.get('window').height
class PlaylistPlayerScreen extends Component {
constructor() {
super();
this.state = {
currentVisibleIndex:-1
}
this.playNextFile = this.playNextFile.bind(this)
this.videoEnded = this.videoEnded.bind(this)
this.schedulePlayDurationTimer = this.schedulePlayDurationTimer.bind(this)
this.viewabilityConfig = {
waitForInteraction: false,
itemVisiblePercentThreshold: 99,
}
}
static navigationOptions = {
header: null,
};
onViewableItemsChanged = ({ viewableItems }) => {
// viewableItems will show you what items are in view
// console.log("onViewableItemsChanged called" + JSON.stringify(viewableItems))
if(viewableItems.length >= 1) {
const visibleFileIndex = viewableItems[0].index
// console.log("visible index " + visibleFileIndex)
this.setState({currentVisibleIndex:visibleFileIndex})
const file = this.props.schedulesFiles[visibleFileIndex]
const fileType = getFileType(file)
console.log("file type is " + fileType)
if (fileType == FileType.Video) {
console.log("video file type")
} else {
this.schedulePlayDurationTimer(visibleFileIndex)
}
}
}
getItemLayout = (data, index) => ({
length: deviceWidth,
offset: deviceWidth * index,
index,
})
componentDidMount(){
this.props.getScheduleFiles()
}
shouldComponentUpdate(nextProps, nextState) {
return true
}
componentDidUpdate(){
console.log("componentDidUpdate")
}
schedulePlayDurationTimer(file_index) {
const file = this.props.schedulesFiles[file_index]
const playDuration = file.play_duration_in_milliseconds
this.timer = setTimeout(() => {
clearTimeout(this.timer)
this.playNextFile()
}, playDuration);
}
videoEnded = () => {
console.log("video ended")
this.playNextFile()
}
playNextFile = () => {
if(this.props.currentlyPlayingIndex == (this.props.schedulesFiles.length - 1)) {
//last file played
this.props.getScheduleFiles()
this.props.playNextFile(this.props.schedulesFiles,this.props.currentlyPlayingIndex)
this.listRef.scrollToIndex({animated: false, index: this.props.currentlyPlayingIndex})
} else {
console.log("playNextFile current index " + this.props.currentlyPlayingIndex)
this.props.playNextFile(this.props.schedulesFiles,this.props.currentlyPlayingIndex)
console.log("playNextFile next index " + this.props.currentlyPlayingIndex)
this.listRef.scrollToIndex({animated: true, index: this.props.currentlyPlayingIndex})
}
}
_renderItem = ({item, index}) => {
return (
this.renderPlayer(item,index)
);
}
renderPlayer(file,index) {
switch (getFileType(file)) {
case FileType.Video:
return <VideoPlayer file={file} onEnd={this.videoEnded} currentIndex={index} currentVisibleIndex={this.state.currentVisibleIndex} />
case FileType.Audio:
return <AudioPlayer file={file} onEnd={this.playNextFile} />
case FileType.Image:
return <ImagePlayer file={file} onEnd={this.playNextFile} />
case FileType.Pdf:
return <PdfPlayer file={file} onEnd={this.playNextFile} />
case FileType.WebpageContent:
return <WebsitePlayer file={file} onEnd={this.playNextFile} />
default:
showToastMessage(
I18n.t('ErrorMessage.FormatNotSupported', {
name: getFileExtension(file).toUpperCase()
})
)
this.playNextFile()
}
}
render() {
if(this.props.schedulesFiles.length > 0 ) {
return (
<View style={{flex:1}}>
<StatusBar hidden={true} />
<FlatList
style={{flex:1}}
bounces={false}
removeClippedSubviews={true}
scrollEnabled={false}
showsHorizontalScrollIndicator={false}
ref={el => this.listRef = el}
horizontal={true}
keyExtractor={(item, index) => index.toString()}
data={this.props.schedulesFiles}
renderItem={this._renderItem}
onViewableItemsChanged={this.onViewableItemsChanged}
viewabilityConfig={this.viewabilityConfig}
getItemLayout={this.getItemLayout}
initialNumToRender={2}
maxToRenderPerBatch={2}
windowSize={this.props.schedulesFiles.length}
/>
<KeepAwake />
</View>
)
}else {
return (
<TouchableWithoutFeedback delayLongPress={constants.REVEAL_SIDE_BAR_MENU_PRESS_DURATION} onLongPress={() => this.props.navigation.openDrawer()}>
<View style={styles.container}>
<NothingTpPlay/>
<KeepAwake />
</View>
</TouchableWithoutFeedback>
)
}
}
}
const styles = ScaledSheet.create({
container: {
flex:1,
backgroundColor : 'white',
}
});
//redux binding
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
return {
loading: state.scheduleReducer.loading,
schedulesFiles: state.scheduleReducer.data,
currentlyPlayingIndex: state.scheduleReducer.nextFileIndex,
}
}
// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/home.js)
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(PlaylistPlayerScreen);
Render method for video player child component code is:
<VideoPlayer file={file} onEnd={this.videoEnded} currentIndex={index} currentVisibleIndex={this.state.currentVisibleIndex} />
VideoPlayer.js relevant code
export default class VideoPlayer extends React.Component {
constructor() {
super();
this.state = {
}
this.videoEnded = this.videoEnded.bind(this)
}
videoEnded() {
if (this.props.shouldRepeat == true) {
} else {
this.video.paused = true
this.video.seek(0)
}
this.props.onEnd()
}
render() {
return (
<Video
ref={ref => {
this.video = ref;
}}
onError={this.videoEnded}
minLoadRetryCount={1}
useTextureView={true}
controls={false}
style={ContainerStyle.playerTypeStyle}
onEnd={this.videoEnded}
repeat={this.props.shouldRepeat}
playInBackground={false}
playWhenInactive={false}
ignoreSilentSwitch={"ignore"}
resizeMode={this.props.file.resize_mode}
source={{uri:getFileAbsolutePath(this.props.file)}}
paused={this.props.currentIndex != this.props.currentVisibleIndex}
/>
)
}
}
Reducer code
import { combineReducers } from 'redux';
import { SCHEDULE_REFRESHED,PLAY_NEXT_FILE } from "../actions/ScheduleActions.js" //Import the actions types constant we defined in our actions
let dataState = { data: [], loading:true };
const scheduleReducer = (state = dataState, action) => {
switch (action.type) {
case SCHEDULE_REFRESHED:
state = Object.assign({}, state, { data: action.data, nextFileIndex:action.nextFileIndex });
return state;
case PLAY_NEXT_FILE:
state = Object.assign({}, state, { nextFileIndex: action.nextFileIndex});
return state;
default:
return state;
}
}
// Combine all the reducers
const rootReducer = combineReducers({
scheduleReducer
// ,[ANOTHER REDUCER], [ANOTHER REDUCER] ....
})
export default rootReducer;
Action code
//gets called initially on app launch to get files to be played
export function getScheduleFiles(){
return (dispatch) => {
getOfflineNextScheduleFiles().then((files)=>{//get offline files/schedule first
plainFiles = convertToArray(files)
dispatch({type: SCHEDULE_REFRESHED, data:plainFiles,nextFileIndex:0});
}).catch((error)=>{//if offline schedules is not available to play, refresh online
triggerPlaylistsRefresh().then((files)=>{
plainFiles = convertToArray(files)
dispatch({type: SCHEDULE_REFRESHED, data:plainFiles,nextFileIndex:0});
}).catch((error)=>{
console.log("nothing to play")
dispatch({type: PLAY_NEXT_FILE, nextFileIndex:0});
showToastMessage(I18n.t("ErrorMessage.NoSchedulesAvailableForCurrentTimeError"))
})
})
}
}
//get called from PlaylistPlayerScreen after each file played
export function playNextFile(files,filePlayedIndex){
return (dispatch) => {
if(filePlayedIndex < files.length-1) {
dispatch({type: PLAY_NEXT_FILE, nextFileIndex:filePlayedIndex+1});
}else {
console.log("all files played")
dispatch({type: PLAY_NEXT_FILE, nextFileIndex:0});
}
}
}

Why is a state created automatically? (redux)

Redux automatically adds an entry called state. Practice creating counter function. However, the state item that I did not set is automatically added so that it does not appear in the desired direction when dispacth is executed.
I guess it's caused by combineReducers, but I don't know exactly.
Clearly, I didn't make State in counter.
//action name
const INCRE = 'counter/INCRE';
const DECRE = 'counter/DECRE';
export const incre = () => ({type: INCRE});
export const decre = () => ({type: DECRE});
//default
const initState = {
number: 0
};
//reducer
function counter(state = initState, action) {
console.log("frist value");
console.log(state.number);
console.log(initState);
if(action.type === INCRE){
console.log("add");
console.log(state.number)
return {number: state.number +1 }
}else if (action.type === DECRE) {
console.log("minus");
return {number: state.number -1 }
}
else {
return{state};
};
};
export default counter;
// C is containers & Counter is name
import React from 'react';
import Counter from '../components/Counter';
import { connect } from 'react-redux';
import {incre, decre} from '../modules/counter';
const CCounter = ({number, incre, decre}) => {
return (
<Counter number={number} onIncre={incre} onDecre={decre}/>
);
};
const mapStateToProps = state => ({
number: state.counter.number
});
const mapDispatchToProps = dispatch => ({
incre: () => {
dispatch(incre());
},
decre: () => {
dispatch(decre());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(CCounter);
//reducer
//...
else {
return{state};
};
The above line in your reducer is likely the problem because what it can return is what you're seeing:
{
state: {
number: 0,
}
}
You probably wanted to simply return the state.
else {
return state;
};

why this Component only works on start but not when pressing button

I learn React-Redux and need help understanding why this Component only works on start but not when I press the button.
When debug start the breakpoints in the picture break execution but when I press the button I get this error showed in the picture.
When breakpoints hit I hoower over the {toasts.map(toast => { and the Array size is zero. But when I press button the breakpoints does not even hit
Any ide?
UPDATE
I have this configureStore.js
import { combineReducers } from "redux";
import { createStore, applyMiddleware, compose } from "redux";
import { forbiddenWordsMiddleware } from "../middleware";
import ToastsReducer from '../reducers/ToastsReducer';
import RootReducer from '../reducers/RootReducer';
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const reducers = {
toastsReducer: ToastsReducer,
rootReducer: RootReducer
};
const reduce = combineReducers({
...reducers,
});
const store = createStore(
reduce,
storeEnhancers(applyMiddleware(forbiddenWordsMiddleware))
);
export default store;
RootReducer.js
import { ADD_ARTICLE } from "../constants/action-types";
import { FOUND_BAD_WORD } from "../constants/action-types";
const initialState = {
articles: []
};
export default function reducer(state = initialState, action) {
if (action.type === ADD_ARTICLE) {
return Object.assign({}, state, {
articles: state.articles.concat(action.payload)
});
}
if (action.type === FOUND_BAD_WORD) {
//return Object.assign({}, state, {
// articles: state.articles.concat(action.payload)
// });
}
return state;
}
ToastsReducer.js
import { ADD_TOAST, REMOVE_TOAST } from "../constants/action-types";
const initialState = {
toastList: []
};
export default function toasts(state = initialState, action) {
const { payload, type } = action;
switch (type) {
case ADD_TOAST:
return [payload, state.toastList];
case REMOVE_TOAST:
return state.toastList.filter(toast => toast.id !== payload);
default:
return state;
}
}
UPDATE
Picture showing RootReducer.jsx and Toasts.jsx when I press button two times,
Toast.js
import PropTypes from "prop-types";
import React, { Component } from "react";
class Toast extends Component {
render() {
return (
<li className="toast" style={{ backgroundColor: this.props.color }}>
<p className="toast__content">
{this.props.text}
</p>
<button className="toast__dismiss" onClick={this.props.onDismissClick}>
x
</button>
</li>
);
}
shouldComponentUpdate() {
return false;
}
}
Toast.propTypes = {
color: PropTypes.string.isRequired,
onDismissClick: PropTypes.func.isRequired,
text: PropTypes.string.isRequired
};
export default Toast;
Please share your reducer code. Most likely, you have not set an initial state for toastList in the reducer or there is an error with toastsReducer.toastList.
Try the following:
Change line 34 to toasts: state.toastsReducer
Comment the lines from 10 to 19 and insert the following to make sure toasts is an array.
console.log(toasts);
console.log(toasts.toastList);
return null;
If both are undefined, then the value returned by the reducer is not right.
In ToastsReducer.js:
Change the following:
case ADD_TOAST:
return [ ...state.toastList, payload]; //<--- Here
When you do return[payload,state.toastList], it appends another array to the toastList.
Run the following to see:
toastList = ['abc'];
// Right way to add an item to an array.
toastList = [...toastList, 'def'];
console.log(toastList);
console.log('-----');
// Adds an array to the array. Incorrect way.
toastList = [toastList, 'ghi'];
console.log(toastList);
---UPDATE---
Change your ADD_TOAST case to:
return { toastList: [...state.toastList, payload] };
and you should be good to go.
Just do check your toasts array contains data,
{toasts && toasts.length > 0 ? toasts.map(toast => {...}) : null}

Re-rendering items after sorting in React Redux

This is the smart component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Filter from '../Filter';
import Sort from '../Sort';
import { getItems, selectItem, reverseItems, findItems } from '../../actions/items';
import './app.css';
const App = ({filterList, sortList, onGetItems, onFindItems, reverseItems, onSelectItem}) => {
onGetItems();
return (
<div>
<Filter items={filterList} findText={onFindItems} reverseItems={reverseItems} selectItem={onSelectItem} />
<Sort items={sortList} selectItem={onSelectItem} />
</div>
)}
function mapStateToProps(state) {
return {
filterList: state.items.filter(item => item.name.includes(state.filter.toLowerCase())),
sortList: state.items,
}
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
onGetItems: getItems,
onSelectItem: selectItem,
onFindItems: findItems,
reverseItems: reverseItems
}, dispatch)}
export default connect(mapStateToProps, matchDispatchToProps)(App);
and actions:
let items = [];
(function onGetItems() {
let xhr = new XMLHttpRequest();
xhr.open('GET', '/items.json', false);
xhr.send();
if (xhr.status !== 200) {
console.log(xhr.status + ': ' + xhr.statusText);
} else {
items = JSON.parse(xhr.responseText.toLowerCase());
items.sort(function(a, b) {
if (a.name > b.name) return 1;
if (a.name < b.name) return -1;
return 0;
});
}
})();
export const getItems = () => dispatch => {
dispatch({ type: 'ADD_ITEMS', payload: items });
}
export const selectItem = (item) => {
console.log(item);
return {
type: "ITEM_SELECTED",
payload: item
}
};
export const reverseItems = (items) => {
console.log(items)
return {
type: "REVERSE_ITEMS",
payload: items.reverse()
}
};
export const findItems = (items) => {
return {
type: "FIND_ITEMS",
payload: items
}
};
and 2 reducers:
const initialState = '';
export default function filter(state = initialState, action) {
switch (action.type) {
case 'FIND_ITEMS': return action.payload;
default: return state
}
}
const initialState = [];
export default function items(state = initialState, action) {
switch (action.type) {
case 'ADD_ITEMS': return action.payload;
default: return state
}
}
The action reverseItems reverses the array, but the problem is that it doesn't rewrite state because it's formed by another action.
I realize that it's a basic issue, but I can't get how to do that.
Replace your
items.sort() statement with [...items].sort() which creates a new reference to the array and allows the re-rendering of the component. The sort function sorts the array using the same reference and does not cause a re-render.
Try to use Redux Thunk for your async calls.
You can dispatch an action, for example RECEIVED_ITEMS after your http request.

Resources