I am using a boilerplate called trufflebox's react-auth where
getWeb is called on loading the page (Link to code)
which creates the web3 object (Link to Code)
and stores web3 in the Redux store (Link to code)
Problem: When I retrieve the web3 object from the Redux store, it is undefined, most likely because web3 has not been created yet in Step 2 described above.
What should be the correct way to retrieve web3 from the Redux store only after it has been set?
layouts/test/Test.js
import React, { Component } from 'react';
import store from '../../store';
class Test extends Component {
componentWillMount() {
this.setState({
web3: store.getState().results.payload.web3Instance
})
this.instantiateContract()
}
instantiateContract() {
console.log(this.state.web3) // UNDEFINED!!
}
render() {
return (
<h1>Test</h1>
)
}
}
export default Test
Everything works if I retrieve web3 again without going to the Redux store:
import React, { Component } from 'react';
import getWeb3 from '../../util/web3/getWeb3';
class Test extends Component {
componentWillMount() {
getWeb3
.then(results => {
this.setState({
web3: results.payload.web3Instance
})
this.instantiateContract()
})
}
instantiateContract() {
console.log(this.state.web3)
}
render() {
return (
<h1>Test</h1>
)
}
}
export default Test
Resolve the promise just after creating the store
src/store.js
import getWeb3 from './util/web3/getWeb3';
import { createStore } from 'redux';
//... prepare middlewares and other stuffs , then : create store
const store = createStore(/*....configure it as you want..*/);
// Just after creating store, here the engineering:
getWeb3.then(results => {
// Assuming you have suitable reducer
// Assuming the reducer put the payload in state and accessible via "getState().results.payload.web3Instance"
store.dispatch({ type: 'SAVE_WEB3', payload: results.payload.web3Instance });
});
export default store;
In you ./index.js (where you are rendering the whole app) consider to use Provider component as wrapper to pass store behind the seen and have a singleton store.
src/index.js
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<Test />
</Provider>
)
Now, in your component, connect HOC will do everything , see comments below :
src/.../Test.js
import { connect } from 'react-redux';
class Test extends Component {
// No need any lifecyle method , "connect" will do everything :)
render() {
console.log(this.props.web3)
return (
<h1>Test</h1>
)
}
}
// Retrieve from Redux STATE and refresh PROPS of component
function mapStateToProps(state) {
return {
web3: state.results.payload.web3Instance // since you are using "getState().results.payload.web3Instance"
}
}
export default connect(mapStateToProps)(Test); // the awesome "connect" will refresh props
Maybe try calling instantiateContract during the componentWillReceiveProps phase. Check out the following...
componentWillMount() {
this.setState({
web3: store.getState().results.payload.web3Instance
});
}
instantiateContract() {
console.log(this.state.web3); // hopefully not undefined
}
componentWillReceiveProps(nextProps) {
if(nextProps.whatever) {
this.instantiateContract();
}
}
render() {
return (
<h1>Test</h1>
)
}
where nextProps.whatever is what you are mapping from redux (not totally sure what this is given your details). Ideally this is getting fed back into your component and when the value either populates or changes, you then call your function
Also, I see a lot of state management here opposed to what I would expect to see done via props. if componentWillReceiveProps(nextProps) is not a good hook given your application architecture, componentDidUpdate(prevProps, prevState) could be a viable alternative.
Related
I want to get props from Redux in the child component. But {this.props} is empty object. I am using react-redux connect to get the props. It is working in parent component and we can pass to child component to get the props but I need to get from child component
Login
```import React, { Component } from 'react'
import { Test } from './Test'
export default class Login extends Component {
render() {
return (
<div>
<Test hi=""/>
</div>
)
}
}```
Test
import React, { Component } from 'react'
import { connect } from 'react-redux'
export class Test extends Component {
render() {
console.log(this.props)
return (
<div>
vddfff
</div>
)
}
}
const mapStateToProps = (state) => ({
})
const mapDispatchToProps = (dispatch) => {
return {
selectedData: (val) => {
console.log("object")
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Test)
The problem here is that you are trying to use the Test component you wrote instead of the Higher order component which is being returned by the connect() from react-redux
You don't need to export the Test class.
In login.js file, use
import Test from './Test'
instead of
import {Test} from './Test'
See it in action here.
https://codesandbox.io/s/sample-test-app-q5srf
You used the wrong component. In login you need to import:
import Test from './Test'
You imported Test wich is not connected to redux (that pushes the redux props).
I was learning React and Redux and while doing that I decided to make webpage with a button which on clicking would change the state. Below the button I wanted to display the current state in a different component. Though the button on clicking changes the state, but it is not getting reflected in the component. Here is my code:
App.js
import React from 'react'
import Name from './Name'
import {changeName} from './Action';
export default function App () {
return (
<div>
<button onClick={changeName}>Click me</button>
<Name />
</div>
)
}
Name.js
import React from 'react'
import {store} from './Store'
function Name(props) {
return (
<div>
My name is: {store.getState()}
</div>
)
}
export default Name
Store.js
import { createStore } from 'redux';
import {reducer} from './Reducer';
export const store = createStore(reducer, 'Tarun');
Action.js
import {store} from './Store';
export const changeName = () => {
if (store.getState() === "Tarun"){
store.dispatch({ type: 'name', payload: 'Subhash' });
}
else{
store.dispatch({ type: 'name', payload: 'Tarun' });
}
}
Reducer.js
export const reducer = function(state, action) {
if (action.type === 'name') {
return action.payload;
}
return state;
};
When I click the button, The text inside the Name component does not change. What is the issue?
You need to set up your reducer and initial store properly following the Redux documentation.
You're missing a Provider, which will provide your store to your application.
const store = createStore(reducer, applyMiddleware(thunk));
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
Now, your store is available to your components.
Your reducer needs an initial state too and you're always supposed to return an updated copy of your state. That said, don't change the state directly, but make a copy, change it, then return that copy.
const initialState = {
name: ""
};
const reducer = function(state = initialState, action) {
if (action.type === "name") {
return { ...state, name: action.payload };
} else {
return state;
}
};
export default reducer;
You might have noticed that I added a middleware to your store, and that's because it's usually the way to go when accessing your current reducer's state in your actions. That said, I installed redux-thunk for that, so in your action, you can have something like this:
export const changeName = () => {
return (dispatch, getState) => {
if (getState().name === "Tarun") {
dispatch({ type: "name", payload: "Subhash" });
} else {
dispatch({ type: "name", payload: "Tarun" });
}
};
};
Now, with your store being provided to your app, your reducer being done and your actions being ready to go, you can connect different components to your reducer.
You use the high order component in react-redux called connect for that. For example, in your Name component, we can connect the name to be displayed to your reducer by mapping your state to the component's props:
function Name(props) {
return <div>My name is: {props.name}</div>;
}
const mapStateToProps = state => {
return {
name: state.name
};
};
export default connect(mapStateToProps)(Name);
The nice thing here is that you can also leave the first parameter in the connect high order component empty and just pass the second, which would be the dispatch functions. Well, that's what you would do in your App component, you would connect it to the changeName action.
function App(props) {
return (
<div>
<button onClick={props.changeName}>Click me</button>
<Name />
</div>
);
}
const mapDispatchToProps = dispatch => {
return {
changeName: () => dispatch(changeName())
};
};
export default connect(
null,
mapDispatchToProps
)(App);
Now, when App dispatches a changeName action, your reducer state will be updated and the other components that are connected to the reducer's state will re-render.
Summary: Try to think of your store as an empty jar of candies. Your jar starts empty, but different actions could change what's inside the jar. On top of that, different people in the house that know where the jar is can go get some candy. Translating to your problem, your app begins with an empty name and you have an action that sets up a name. The components that know where to find that name by being connected to your reducer will know when that name changes and will get the updated name.
The final code can be found here:
The only way your name component will rerender is its props or state change, or if a parent component rerenders. Making a change in redux will not automatically do this. In order to see changes to the state, you'd need to subscribe to those changes. You could do this yourself, but a far better solution is to use react-redux, which is designed for connecting react components to redux stores.
For example, you'd add a provider to your app:
import { Provider } from 'react-redux';
import { store } from './Store'
export default function App () {
return (
<Provider store={store}>
<div>
<button onClick={changeName}>Click me</button>
<Name />
</div>
</Provider>
)
}
And then you'd use connect with your Name component:
import { connect } from 'react-redux';
function Name(props) {
return (
<div>
My name is: {props.name}
</div>
)
}
const mapStateToProps = (state) => {
return { name: state };
}
export default connect(mapStateToProps)(Name)
I have a react main component that dispatches redux action on componentDidMount, the action will fetch API data.
The problem is: when I start my application my componentDidMount and main component are executed twice. So, it makes 2 API calls for each time application loads. API has a limit for the total number of calls I make, I don't want to reach my limit.
I have already tried fixing the issue by removing constructor, using componentWillMount problem is not solved.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../redux/actions/fetchActions';
import TableHeader from './tableHeader';
class Main extends Component {
componentDidMount() {
console.log("mounted");
// this.props.dispatch(actions.fetchall("market_cap"));
}
render() {
console.log("rendered");
// console.log(this.props.cdata);
// console.log(this.props.cdata.data.data_available);
return <div className="">
<TableHeader {...this.props} />
</div>
}
}
export default Main;
///actions
import axios from 'axios';
export function fetchall(sort) {
return function (dispatch) {
axios.get(`https://cors-anywhere.herokuapp.com/https:-----------`)
.then(function (response) {
dispatch({
type: 'FETCH_DATA',
payload: response.data
})
})
.catch(function (error) {
console.log(error);
})
}
}
//reducer
let initialState = {
coins: [],
data_available: false,
};
export default function (state = initialState, action) {
switch (action.type) {
case 'FETCH_DATA':
return {
...state,
coins: action.payload,
data_available: true
}
default: return state;
}
}
//rootreducer
import { combineReducers } from 'redux';
import DataReducer from './dataReducer';
export default combineReducers({
data: DataReducer
});
////index
import {createStore, applyMiddleware} from 'redux';
import MapStateToProps from './components/mapStateToProps';
import rootReducer from './redux/reducers/rootReducer';
import {Provider} from 'react-redux';
import thunk from 'redux-thunk';
//const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer, applyMiddleware(...middleware));
ReactDOM.render(<Provider store={store}><MapStateToProps/></Provider>, document.getElementById("root"));
console image is posted for reference "rendered" is logged inside main component
"runned1" is logged inside main-subcomponent
"mounted" logged inside componentDidMount
"
I believe you can work around this by providing some additional logic in your componentDidmount. You should also make use of your component state.
Write something like this:
constructor(props){
super(props)
this.state = {
mounted: false
}
}
componentDidMount(){
if(!this.state.mounted){
this.props.dispatchmyAction()
this.setState({
mounted: true
})
}
}
This essentially says, if your component has already mounted once, then you will not make your action creator request.
If you watch your console.log carefully you can notice that your HMR Hot Module Reloading -plugin, re-mounts your component and this is the main reason behind this occurrence.
What this plugin does, is that it watches for your bundles code changes and on every time you save re-renders your component. There has been a lot of discussion as well that this plugin does not work all cases as expected.
Here is some material you might consider to go trough if you wish to use HMR.
Writing about HMR -
https://codeburst.io/react-hot-loader-considered-harmful-321fe3b6ca74
User guide for HMR -
https://medium.com/#rajaraodv/webpacks-hmr-react-hot-loader-the-missing-manual-232336dc0d96
The problem is solved when I removed webpack from the project. But can anyone answer how can I solve this while still using the webpack.
Can I dispatch an action in the redux to change the inner state of a react component?
I have a state managed by the react state and I want to do some async stuff in the middleware of redux so that I can manage all the side effect in only one place. However, I want to change the inner state of react after I finished my async call and I don't want to manage this state by redux(You will need to pass too many things into the actions). Is there a way to fire up an action to change the react state by redux? Thanks.
You can do it using componentWillReceiveProps lifecycle hook.
So, you should connect to the updates using connect from react-redux and then update your local state.
For example:
SomeContainer.jsx
import React from 'react';
import { connect } from 'react-redux';
import { yourCustomAsyncAction } from '../actions';
import SomeComponent from './components';
const mapStateToProps = state => {
return {
someValue: state.someState.someValue
};
};
export default connect(mapStateToProps, { yourCustomAsyncAction })(SomeComponent));
SomeComponent.jsx
import React, { Component } from 'react';
class SomeComponent extends Component {
constructor(props) {
super(props);
this.state = {
someLocalValue: '',
}
}
componentWillReceiveProps(nextProps) {
// someValue - value which we passed from redux in container
const { someValue } = nextProps;
if (someValue !== this.state.someLocalValue) {
this.setState({ someLocalValue: someValue });
}
}
render() {
return <div> Here will be updated value via Redux: {this.state.someLocalValue} </div>
}
}
export default SomeComponent;
NOTE: componentWillReceiveProps is going to be deprecated starting with React version 16.3 (which should be released soon) and will be removed with 17 version. The new static method is introduced named getDerivedStateFromProps. See more here.
Hope it will helps.
I'm learning redux and react. I am following some tutorials, in order to make a app.
I have this action:
export function getDueDates(){
return {
type: 'getDueDate',
todo
}
}
this is the store:
import { createStore } from 'redux';
import duedates from './reducers/duedates'
export default createStore(duedates)
This is the reducer:
import Immutable from 'immutable'
export default (state = Immutable.List(['Code More!']), action) => {
switch(action.type) {
case 'getDueDate':
return state.unshift(action.todo)
default:
return state
}
}
and in the entry point js I have this:
import React from 'react';
import ReactDOM from 'react-dom';
import store from './app/store'
import { Provider } from 'react-redux'
import App from './app/Components/AppComponent';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);
Now, (according to some examples), I should call getDueDate from the dispatch but I dont get how to get the dispatch on the component, to trigger the action
Use connect from react-redux package. It has two functions as params, mapStateToProps and mapDispatchToProps, which you are interested in now. As per answer from Nick Ball, which is partially right, you will be exporting like this:
export default connect(mapStateToProps, mapDispatchToProps)(App)
and your mapDispatchToProps will look something like this:
function mapDispatchToProps (dispatch, ownProps) {
return {
getDueDate: dispatch(getDueDate(ownProps.id))
}
}
as long as your component connected to the store has property id passed from above, you'll be able to call this.props.getDueDate() from inside of it.
EDIT: There is probably no need of using an id in this case, however my point was to point out that props go as second parameter :)
The missing piece here is the connect function from react-redux. This function will "connect" your component to the store, giving it the dispatch method. There are variations on how exactly to do this, so I suggest reading the documentation, but a simple way would be something like this:
// app/Components/AppComponent.js
import { connect } from 'react-redux';
export class App extends React.Component {
/* ...you regular class stuff */
render() {
// todos are available as props here from the `mapStateToProps`
const { todos, dispatch } = this.props;
return <div> /* ... */ </div>;
}
}
function mapStateToProps(state) {
return {
todos: state.todos
};
}
// The default export is now the "connected" component
// You'll be provided the dispatch method as a prop
export default connect(mapStateToProps)(App);