I configured redux this way and it works.
This is the _app.js file reconfigured :
import App from 'next/app';
import { Provider } from 'react-redux';
import withRedux from 'next-redux-wrapper';
import store from '../redux/store';
import React from 'react';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
const appProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
console.log(appProps);
return {
appProps: appProps
};
}
render() {
const { Component, appProps } = this.props;
return (
<Provider store={store}>
<Component {...appProps} />
</Provider>
);
}
}
const makeStore = () => store;
export default withRedux(makeStore)(MyApp);
This is the index.js file which I've connected to redux :
import { connect } from 'react-redux';
import { callAction } from '../redux/actions/main';
const Index = (props) => {
console.log(props);
return (
<div>
Index js state <button onClick={() => props.callAction()}>Call action</button>
</div>
);
};
const mapStateProps = (state) => ({
name: state.main.name
});
const mapDispatchProps = {
callAction: callAction
};
export default connect(mapStateProps, mapDispatchProps)(Index);
This is the rootReducer file which gets only one reducer named main :
import { main } from './main';
import { combineReducers } from 'redux';
export const rootReducer = combineReducers({
main: main
});
And this is the store.js file :
import { createStore } from 'redux';
import { rootReducer } from './reducers/rootReducer';
const store = createStore(rootReducer);
export default store;
It all works fine but it throws a warning in the console which says :
/!\ You are using legacy implementaion. Please update your code: use createWrapper() and wrapper.withRedux().
What changes to which files I need to make to fix the legacy implementation warning?
I solved the warning by changing the way I get redux states and actions in index.js and the way passing them in _app.js files by using the createWrapper and withRedux :
_app.js
import App from 'next/app';
import store from '../redux/store';
import { Provider } from 'react-redux';
import { createWrapper } from 'next-redux-wrapper';
class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
}
const makeStore = () => store;
const wrapper = createWrapper(makeStore);
export default wrapper.withRedux(MyApp);
index.js
import { callAction } from '../redux/action';
import { connect } from 'react-redux';
const Index = (props) => {
return (
<div>
hey {props.name}
<br />
<button onClick={() => props.callAction()}>Call action</button>
</div>
);
};
const mapState = (state) => {
return {
name: state.name
};
};
const mapDis = (dispatch) => {
return {
callAction: () => dispatch(callAction())
};
};
export default connect(mapState, mapDis)(Index);
I would like to leave this answer, it worked for me in TS
================== _app.tsx ==================
import type { AppProps } from 'next/app'
import { Provider } from 'react-redux';
import { createWrapper } from 'next-redux-wrapper';
import { store } from '../redux/store';
function MyApp({ Component, pageProps }: AppProps & { Component: { layout: any }}) {
const Layout = Component.layout || (({ children }) => <>{children}</>);
return (
<Provider store={store}>
<Layout>
<Component {...pageProps} />
</Layout>
</Provider>
);
}
MyApp.getInitialProps = async ({ Component, router, ctx }) => {
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
return { pageProps };
}
const makeStore = () => store;
const wrapper = createWrapper(makeStore);
export default wrapper.withRedux(MyApp);
================== store.tsx ==================
import { applyMiddleware, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import { rootReducer } from '../reducers';
export const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunkMiddleware)),
);
export type AppDispatch = typeof store.dispatch;
================== reducers.tsx ==================
import * as redux from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { Action } from 'typesafe-actions';
import user from './user';
export const rootReducer = redux.combineReducers({
user,
});
export type AppThunkDispatch = ThunkDispatch<RootState, void, Action>;
export type RootState = ReturnType<typeof rootReducer>;
================== onereducer.tsx ==================
import { HYDRATE } from "next-redux-wrapper";
import { Reducer } from 'redux';
import { ActionType } from 'typesafe-actions';
import { USER_ACTIONS } from '../../actions/types';
import { IUserData } from './types';
const userState: IUserData = {
_id: '',
email: '',
password: '',
role: '',
};
const userReducer: Reducer<IUserData, ActionType<any>> = (
state = userState,
action,
) => {
switch (action.type) {
case HYDRATE:
return { ...state, ...action.payload.userData };
case USER_ACTIONS.SET_USER_DATA:
return { ...state, ...action.payload.userData };
default:
return { ...state };
}
};
export default userReducer;
PD: Still a work in progress but works!
Related
I'm trying to use redux on my current project. However, I can't use the dispatch data to another component.
Here's my code:
Authenticate.js
import { useDispatch, useSelector } from 'react-redux';
import { setLoginDetails } from 'redux/actions/loginActions';
function Authenticate() {
const [data, setData] = useState([]);
const dispatch = useDispatch();
const loginDetails = useSelector((state) => state);
const validateLogin = async() => {
const response = await.get('/api')
let responseValue = response.data.success
if(responseValue === true) {
let responseResp = JSON.parse(response.data.resp)
dispatch(setLoginDetails(responseResp.data.user))
/** here's the console logs
console.log('responseResp',responseResp)
console.log('AuthenticationOAK/responseResp.data.',responseResp.data)
console.log('AuthenticationOAK/responseResp.data.user',responseResp.data.user)
*/
window.location = '/home'
}
else{
//error
}
}
useEffect(()=>{
validateLogin();
},[])
return(
<div>Authenticating....</div>
)
}
export default Authenticate;
loginAction.js
import { ActionTypes } from '../constants/action-types';
export const setLoginDetails = (loginDetails) => {
return {
type: ActionTypes.SET_LOGIN,
payload: loginDetails,
}
}
action-types.js
export const ActionTypes = {
SET_LOGIN: 'SET_LOGIN',
}
loginReducer.js
import { ActionTypes } from '../constants/action-types';
const initialState = {
loginDetails: [],
}
export const loginReducer = (state = initialState, {type, payload}) => {
console.log('loginReducer/state', state)
console.log('loginReducer/payload',payload)
console.log('loginReducer/type', type)
console.log('loginReducer/initialState',initialState)
switch(type){
case ActionTypes.SET_LOGIN:
return {...state, loginDetails: payload}
default:
return state;
}
}
Home.js
import { useSelector } from 'react-redux';
function Home(){
const loginDetails = useSelector((state) => state.allLoginDetails.loginDetails)
const { email, userName, firstName } = loginDetails;
return(
<h1>username {userName}</h1>
)
}
export default Home;
index.js /redux/reducers/index.js
import { combineReducers } from 'redux'
import { loginReducer } from './loginReducer'
const reducers = combineReducers({
allLoginDetails: loginReducer,
});
export default reducers;
store.js
import { createStore } from 'redux'
import reducers from './index'
const store = createStore(
reducers,
{},
);
export dafault store;
Index.js
import React from 'react'
import { render } from 'react-dom'
import App from './App'
import {createStore} from 'redux'
import { Provider } from 'react-redux'
import store from '../src/redux/reducers/store'
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
Below are the result of console logs from Authenticate.js
screenshots of console log from loginReducer.js
Hoping that you can help me with this, I'm having a hard time on this. Tried to use hard coded value on initialState and it's rendering properly. But when I use dynamic data it's not rendering. Thank you in advance. Stay safe!
in this file: Authenticate.js
useEffect(()=>{
Authenticate(); // it must be validateLogin()?
},[])
I am developing a lottery statistics app that gets data from a csv loaded from an input then I was wanting to read this data to the redux store so I can use it across multiple components.
I have successfully saved the data to the redux store once I import the file and read it through Header.js and using an action, but I am not sure how to access this in other components like e.g. Main.js.
I feel like I am still confused on how react/redux all fits together. I'm sorry if this has been asked before but everything I looked up online I couldn't get to work.
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
import reducers from "./reducers";
import App from "./components/App";
const store = createStore(reducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
);
// App.js
import React from "react";
import Header from "./Header";
import Main from "./Main";
const App = () => {
return (
<div>
<Header />
<Main />
<div className="numbers-for-draw"></div>
</div>
);
};
export default App;
// Header.js
import React from "react";
import { CSVReader } from "react-papaparse";
import { fetchData } from "../actions";
import { connect } from "react-redux";
class Header extends React.Component {
constructor(props) {
super(props);
this.fileInput = React.createRef();
}
handleReadCSV = data => {
this.props.fetchData(data);
console.log(this.props.data);
};
handleOnError = (err, file, inputElem, reason) => {
console.log(err);
};
handleImportOffer = () => {
this.fileInput.current.click();
console.log("Got to handleImportOffer");
};
render() {
return (
<header>
<CSVReader
onFileLoaded={this.handleReadCSV}
inputRef={this.fileInput}
style={{ display: "none" }}
onError={this.handleOnError}
/>
<button onClick={this.handleImportOffer}>Import</button>
</header>
);
}
}
//Map what is in the redux store (e.g. state) to props
const mapStateToProps = state => ({
data: state.data
});
export default connect(mapStateToProps, {
fetchData: fetchData
})(Header);
// Main.js
import React from "react";
import { fetchData } from "../actions";
import { connect } from "react-redux";
const Main = () => {
console.log("In main");
console.log(this.props.data); //Blows up here.
return <div>Main</div>;
};
//Map what is in the redux store (e.g. state) to props
const mapStateToProps = state => ({
data: state.data
});
export default connect(mapStateToProps, {
fetchData: fetchData
})(Main);
// actions/index.js
export const fetchData = data => dispatch => {
console.log("Action");
const lottoData = {
stringNumbers: [
"one",
"two",
"three",
...
],
allResults: [],
winningNumbers: [],
winningNumbersAsStrings: []
};
const localData = data.data;
localData.shift();
localData.forEach(line => {
const lineObject = {
draw: line[0],
drawDate: line[1],
ballOne: line[2],
ballTwo: line[3],
ballThree: line[4],
ballFour: line[5],
ballFive: line[6],
ballSix: line[7],
bonusBall: line[8],
bonusBall2: line[9],
powerBall: line[10]
};
lottoData.allResults.push(lineObject);
let nums = [];
nums.push(parseInt(line[2]));
nums.push(parseInt(line[3]));
nums.push(parseInt(line[4]));
nums.push(parseInt(line[5]));
nums.push(parseInt(line[6]));
nums.push(parseInt(line[7]));
nums.sort((a, b) => {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
lottoData.winningNumbers.push(nums);
lottoData.winningNumbersAsStrings.push(nums.toString());
});
dispatch({ type: "FETCH_DATA", payload: lottoData });
};
// lottoReducer.js
export default (state = {}, action) => {
switch (action.type) {
case "FETCH_DATA":
return action.payload;
default:
return state;
}
};
// reducers/index.js
import { combineReducers } from "redux";
import lottoReducer from "./lottoReducer";
export default combineReducers({
data: lottoReducer
});
I haven't tested your code, but it seems to me that the only problem is in your Main.js
While you use a function component and not a class, you shouldn't use this to access your props. The following should work as expected:
const Main = (props) => {
console.log("In main");
console.log(props.data);
return <div>Main</div>;
};
//Map what is in the redux store (e.g. state) to props
const mapStateToProps = state => ({
data: state.data
});
export default connect(mapStateToProps, {
fetchData: fetchData
})(Main);
In your main.js you used functional components so this.props doesn't work there. You must pass props to your component and console.log(props.data).
I am trying to test my React "supersquadapp" and getting the following error.
Uncaught Error: Could not find "store" in either the context or props of "Connect(CharacterList)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(CharacterList)".
characterlist.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class CharacterList extends Component {
render() {
console.log('this.props', this.props);
return (
<div>
<h4>characters</h4>
</div>
)
}
}
function mapStateToProps(state) {
return {
characters: state.characters
}
}
export default connect(mapStateToProps, null)(CharacterList);
app.js
import React, {Component} from 'react';
import CharacterList from './CharacterList';
class App extends Component {
render() {
return (
<div>
<h2>SUPER SQUAD</h2>
<CharacterList />
</div>
)
}
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './Components/App';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducers from './reducers';
import { addCharacterById } from './actions';
const store = createStore(rootReducers);
console.log(store.getState());
store.subscribe(() => console.log('store',store.getState()))
store.dispatch(addCharacterById(3));
ReactDOM.render(
<Provider>
<App />
</Provider>
,document.getElementById('root')
)
character.js(in reducers folder)
import characters_json from '../Data/characters.json';
import { ADD_CHARACTER } from '../actions';
function characters(state = characters_json, action) {
switch(action.type) {
case ADD_CHARACTER:
let characters = state.filter(item => item.id !== action.id);
return characters;
default:
return state;
}
}
export default characters;
heroes.js(reducers folder)
import { ADD_CHARACTER } from '../actions';
import {createCharacter} from './helper';
function heroes(state = [], action) {
switch(action.type) {
case ADD_CHARACTER:
let heroes = [...state, createCharacter(action.id)];
return heroes;
default:
return state;
}
}
export default heroes;
helper.js(reducers folder)
import characters_json from '../Data/characters.json';
export function createCharacter(id) {
let character = characters_json.find(c => c.id === id);
return character;
}
index.js(reducers folder)
import { combineReducers } from 'redux';
import characters from './characters_reducer';
import heroes from './heroes_reducer';
const rootReducer = combineReducers({
characters,
heroes
})
export default rootReducer;
index.js(action folder)
export const ADD_CHARACTER = 'ADD_CHARACTER';
export function addCharacterById(id) {
const action = {
type:ADD_CHARACTER,
id
}
return action;
}
The above error occurred in the component:
in Connect(CharacterList) (at App.js:9)
in div (at App.js:7)
in App (at index.js:19)
in Provider (at index.js:18)
please help me...?
If you are running a test and you have something like alechill's answer, you'd need this to change in the test, for example:
let mockedStore = configureStore([])({});
test('some test', () => {
const wrapper = mount(<SomeComponent foo="bar" />, {
context: { store: mockedStore },
childContextTypes: { store: PropTypes.object.isRequired }
});
expect(.... your tests here);
});
You must pass the store instance to Provider...
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
)
I am trying to dispatch another action inside one async action creator. However, when I am using dispatch function provided by redux-thunk middleware, it is throwing Uncaught TypeError: dispatch is not a function error.
Below are the files for more help
store/store.dev.js
use strict';
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from '../reducers';
import ReduxThunk from 'redux-thunk';
export default function configureStore(initialState = {}) {
const store = createStore(rootReducer, initialState, applyMiddleware(ReduxThunk));
console.log('store returned', store);
return store;
}
src/index.js
'use strict';
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { hydrate } from 'react-dom';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import configureStore from './store';
import App from './containers/App.js';
import routes from './routes/index.js';
hydrate(
<Provider store={configureStore()}>
<BrowserRouter>
{renderRoutes(routes)}
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
reducer
'use strict';
import { LOGIN_SUBMITTED, LOGIN_COMPLETED } from '../constants/ActionTypes.js';
const loginSubmitReducer = (state = [], action) => {
console.log('in reducer');
switch (action.type) {
case LOGIN_SUBMITTED:
console.log('login submitted case');
return Object.assign({}, state, {
loginSubmitted: true
});
case LOGIN_COMPLETED:
console.log('login completed case');
return Object.assign({}, state, {
loginCompleted: true
});
default:
console.log('in default case');
return {
a:1
};
}
}
export default loginSubmitReducer;
containers/App.js
'use strict';
import React from 'react';
import { loginCompleted, loginSubmitted } from '../actions';
import { createStructuredSelector, createSelector } from 'reselect';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Header, ButtonDemo, LoginForm } from '../components';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
class App extends React.Component {
constructor(props, context) {
super(props);
this.state = {
appState: {
entry: 'yes'
}
}
console.log('props in App constructor', props);
console.log('context in App constructor', context);
this.newFunc = this.newFunc.bind(this);
}
newFunc () {
console.log('props', this.props);
}
render() {
console.log('props in main App', this.props);
console.log('Actions', this.props.actions);
console.log('context in App', this.context);
// this.props.actions.loginCompleted();
const muiTheme = getMuiTheme({
lightBaseTheme
});
return (
<MuiThemeProvider muiTheme={muiTheme}>
<div>
<Header />
<LoginForm
appstate = {this.props.appState}
dispatchAction = {this.props.actions}
/>
</div>
</MuiThemeProvider>
);
}
}
// const mapStateToProps = createStructuredSelector({});
const mapStateToProps = (state) => {
return { appState: state.loginSubmitReducer };
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ loginCompleted, loginSubmitted }, dispatch)
};
// dispatch(loginCompleted());
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
components
'use strict';
import React, { PropTypes } from 'react';
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';
import FloatingActionButton from 'material-ui/FloatingActionButton';
import ContentAdd from 'material-ui/svg-icons/content/add';
import { loginAction } from '../actions';
// import '../css/style.css';
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.loginSubmit = this.loginSubmit.bind(this);
}
loginSubmit() {
console.log('Login fomr submitted', this.props);
console.log('loginAction', loginAction);
loginAction()();
}
render () {
console.log('props when login rendered', this.props);
return (
<div className="loginForm">
<div className="title">LOGIN</div>
<form>
<TextField
hintText="Username"
floatingLabelText="Username"/><br/>
<br/>
<TextField
hintText="Password"
type="Password"
floatingLabelText="Password"/><br/>
<br/>
<RaisedButton
label="Login"
secondary={true}
onClick={this.loginSubmit}
className="submitBtn" /><br/>
<br/>
<FloatingActionButton
secondary={true}
className="registerBtn">
<ContentAdd />
</FloatingActionButton>
</form>
</div>
)
}
}
export default LoginForm;
actions/loginAction.js
'use strict';
// import { polyfill } from 'es6-promise';
// require('es6-promise').polyfill();
// require('isomorphic-fetch');
import request from 'superagent';
// import fetch from 'isomorphic-fetch';
import loginCompleted from './loginCompletedAction.js';
import loginSubmitted from './loginSubmittedAction.js';
const loginAction = (dispatch) => {
console.log('validateUserLogin executed', this);
console.log('validateUserLogin dispatch', dispatch);
return (dispatch) => {
// console.log('first return', dispatch);
// this.props.dispatch(loginSubmitted());
// this.props.dispatchAction.loginSubmitted();
dispatch(loginSubmitted());
request.get('http://localhost:3000/loginSubmit')
.end((err, res) => {
console.log('res', res);
console.log('err', err);
// this.props.dispatchAction.loginCompleted();
});
}
}
export default loginAction;
Folder structure
I am using redux-thunk middleware that exposes dispatch function to async action creators. Still it doesn't work.
Please help!
Your first argument of the action creator should not be the dispatch but the argument you pass to the action creator.
const loginAction = () => (dispatch) => {
console.log('validateUserLogin executed', this);
console.log('validateUserLogin dispatch', dispatch);
// console.log('first return', dispatch);
// this.props.dispatch(loginSubmitted());
// this.props.dispatchAction.loginSubmitted();
dispatch(loginSubmitted());
request.get('http://localhost:3000/loginSubmit')
.end((err, res) => {
console.log('res', res);
console.log('err', err);
// this.props.dispatchAction.loginCompleted();
});
}
Edit
As a follow up to your comment, iat first i thought you are not implementing the redux-thunk properly as a middle-ware but according to your code it looks ok.
Then I've noticed that you are not really dispatching loginAction but just invoking it.
You should dispatch it dispatch(loginAction()). Your component should be connected to redux then you will have access to dispatch.
Hi All I am new to redux. I am creating a sample app as below:
entry point: index.js
import 'babel-polyfill'
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import inboundApp from './reducers'
import App from './components/App'
let store = createStore(inboundApp)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
)
/components/App.js
import React from 'react'
import HeaderContainer from '../containers/HeaderContainer'
import LoginForm from '../containers/LoginForm'
const App = () => (
<div>
<HeaderContainer />
<LoginForm />
</div>
)
export default App
/containers/LoginForm.js
import React from 'react'
import { connect } from 'react-redux'
import { login } from '../actions'
let LoginForm = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(login(input.value))
input.value = ''
}}>
<input ref={node => {
input = node
}} />
<button type="submit">
Login
</button>
</form>
</div>
)
}
LoginForm = connect()(LoginForm)
export default LoginForm
/actions/index.js
export const login = (supplierId) => {
return {
type: 'LOGIN',
supplierId
}
}
/containers/HeaderContainer.js
import { connect } from 'react-redux'
import Header from '../components/Header'
const mapStateToProps = (state) => {
return {
supplierId: state.supplierId
}
}
const HeaderContainer = connect(
mapStateToProps,
null
)(Header)
export default HeaderContainer
/components/Header.js
import React, { PropTypes } from 'react'
const Header = ({ supplierId}) => {
return (
<div>
<span>Your Supplier ID: </span> {supplierId}
</div>
)
}
export default Header
/reducers/loginForm.js
const loginForm = (state = '', action) => {
switch (action.type) {
case 'LOGIN':
return Object.assign({}, state, {
supplierId: action.supplierId
})
default:
return state;
}
}
export default loginForm
/reducers/index.js
import { combineReducers } from 'redux'
import loginForm from './loginForm'
const inboundApp = combineReducers({
loginForm
})
export default inboundApp
The problem is my presentation component Header does not get update by the action LOGIN which is firing by click on the button in the LoginForm.js.
would you please help me to find what am I missing? what's wrong with this code?
thanks
I think you try to get the supplierId, from the wrong namespace and your default state of loginForm is not good. Try like that:
const loginForm = (state = {supplierId: ''}, action) => {
switch (action.type) {
case 'LOGIN':
return Object.assign({}, state, {
supplierId: action.supplierId
})
default:
return state;
}
}
And the connect
const mapStateToProps = (state) => {
return {
supplierId: state.loginForm.supplierId
}
}