I'm trying to update my home componentstate by getting data from the redux store every time the store is updated. I'm not sure what's wrong with the code below. I can't listen to store changes in my `home component.
my dispatch function is handled in this class.
export class GanttFilter extends Component {
...
handleSubmit = () => {
this.gFilter.filterGanttData(this.formValues)
.then((result) => {
if (result.data)
this.props.dispatch(ganttActions.loadGanttData(result.data));
});
}
...
GanttFilter.propTypes = {
dispatch: PropTypes.func.IsRequired
};
function mapStateToProps(state) {
return {
ganttData: state.gantt.ganttData
};
}
export default connect(mapStateToProps)(GanttFilter);
What I would like to do every time dispatch is called and the data changes, is update the state in my home component. Here is the component.
export class Home extends Component {
constructor() {
super();
this.state = {
data: [],
links: []
};
}
render() {
return (
<div className="fill">
<Gantt data={this.state.data} links={this.state.links} />
</div>
);
}
}
Home.propTypes = {
data: PropTypes.IsRequired
};
function mapStateToProps(state) {
return {
data: state.gantt.ganttData
};
}
export default connect(mapStateToProps)(Home);
the function mapStateToProps is never hit when I set a break point. How can I listen to changes to the store from the home component and update state?
Edit: Here is the wrapper component
function renderApp() {
// This code starts up the React app when it runs in a browser. It sets up the routing
// configuration and injects the app into a DOM element.
const baseUrl = document.getElementsByTagName("base")[0].getAttribute("href");
ReactDOM.render(
<ReduxProvider store={store}>
<AppContainer>
<BrowserRouter children={routes} basename={baseUrl} />
</AppContainer>
</ReduxProvider>,
document.getElementById("react-app")
);
}
reducers
const actionTypes = require('../actions/actionTypes');
const gantt = {
ganttData: [],
selectedTask: null
};
export default function ganttReducer(state = gantt, action) {
switch (action.type) {
case actionTypes.loadGanttData:
return { ...state, ganttData: [...action.ganttData] };
default:
return state;
}
}
root reducer
import { combineReducers } from 'redux';
import gantt from './ganttReducer';
const rootReducer = combineReducers({
gantt
});
export default rootReducer;
actions
const actionTypes = require('./actionTypes');
export function loadGanttData(ganttData) {
return { type: actionTypes.loadGanttData, ganttData };
}
export function getSelectedTask(ganttTask) {
return { type: actionTypes.setSelectedTask, ganttTask };
}
Error:
Make sure you import your Home component using import Home from '...' as opposed to import { Home } from '...', otherwise you'd be grabbing the unconnected component. In general, I would also avoid exporting the unconnected component.
Change this:
render() {
return (
<div className="fill">
<Gantt data={this.state.data} links={this.state.links} />
</div>
);
}
To
render() {
return (
<div className="fill">
<Gantt data={this.props.data} links={this.state.links} />
</div>
);
}
Your data is comming from your props (redux), not from your state.
Related
I'm trying to do something like this;
I have a file called /components/master_layout.js and it has the following content:
import useUser from "../data/use-user";
function MasterLayout({ children }) {
const { data, error, mutate } = useUser();
if ( error ) return <div>error</div>
if ( !data && !error ) return <div>loading..</div>
return (
<div>
{children}
</div>
)
}
export default MasterLayout
In short, this layout file returns according to the response of the useuser function.
Here is an example of a page where I use this layout:
file path and name: /pages/dashboard/index.js
import MasterLayout from "../../components/master_layout";
function Dashboard() {
return (
<MasterLayout>
dashboard..
</MasterLayout>
)
}
export default Dashboard
Can I use useUser data from Layout in '/pages/dashboard/index.js' and my other pages?
The reason I want this is, I'm trying to do something like:
import MasterLayout from "../../components/master_layout";
function Dashboard({data}) {
return (
<MasterLayout>
Welcome back, {data.username}
</MasterLayout>
)
}
export default Dashboard
Do I have any other choice but to pull the useUser for each page one by one and transfer it to the master layout as
You can use HOC pattern in this case. Something like
// with-data.js
import React from "react";
import useUser from "../data/use-user";
const withData = (WrappedComponent) => {
class WithData extends React.Component {
constructor(props) {
super(props);
this.state = {
data: "",
};
}
componentDidMount() {
const { data, error, mutate } = useUser();
this.setState({data:data});
}
render() {
const { data, ...otherProps } = this.props;
return (
<WrappedComponent data={this.state.data}/>
)
//* See how we can enhance the functionality of the wrapped component
}
}
return WithData;
};
export default withData;
Now you can use the withData,
import MasterLayout from "../../components/master_layout";
import withData from "../withData.js"
function Dashboard({data}) {
return (
<MasterLayout>
Welcome back, {data.username}
</MasterLayout>
)
}
export default withData(Dashboard);
In fact wrapping around any component with withData, can access the data variable.
I have a redux State HOC to manage the connection
I Have a problem when I add a new post to the store
import React, { useEffect } from "react";
import { connect } from "react-redux";
export default function withState(WrappedComponent) {
function mapStateToProps(reduxState) {
let state = {};
for(let t of Object.entries(reduxState)) {
state = {...state, ...t[1]}
}
return {
...state,
};
}
return connect(
mapStateToProps,
null
)(function (props) {
useEffect(() => {}, [props.posts, props.comments]) /*tried this but didn't work*/
return (
<React.Fragment>
<WrappedComponent {...props} />
</React.Fragment>
);
});
}
I am trying to make the program render the response from my back-end without me reloading the page manually
I tried using the useEffect
and I saw through the dev tools that the state change correctly
my reducer
import { GET_ALL_POSTS, CREATE_NEW_POST } from "../actions"
const initialState = {
posts: []
}
export default function postReducer(state = initialState, action) {
let newState = {...state}
switch(action.type){
case GET_ALL_POSTS:
return {
...newState,
posts: [...action.posts],
}
case CREATE_NEW_POST:
const posts = [...newState.posts, action.post]
return {
...newState,
posts
}
default:
return {
...newState,
}
}
}
I also read that react changes doesn't respond to shallow copies so I changed the whole array in the post reduces when I add a new post
Your withState HOC is very strange. I'm not sure why you don't just use connect directly (or use hooks). But try this:
export function withState(WrappedComponent) {
return connect(
(state) => ({
posts: state.postsReducer.posts,
comments: state.commentsReducer.comments
}),
null
)(WrappedComponent);
}
I'm making a markdown editor using Marked library like this <div id="preview" dangerouslySetInnerHTML={ {__html: marked('Rendered by **marked**.''></div> but get TypeError: Object(...) is not a function.
Found two relevant posts on SO; first and second I'm using the same syntax as the answers but I get a TypeError. In both posts they used ReactDOM.render() method in the end. Full code:
import React, { Component } from 'react';
import './App.css';
import { Provider, connect } from 'react-redux';
import { createStore } from'redux';
import { marked } from "marked";
// Redux
const ADD = "ADD";
const addText = (text) => {
return {
type: ADD,
text: text
}
};
const textReducer = (state = {text: ''}, action) => {
switch(action.type) {
case ADD:
return Object.assign({},state, { text: action.text })
default:
return state
}
};
const store = createStore(textReducer);
// React
class App extends Component {
constructor(props){
super(props)
/*this.state = {
input : ''
}*/
this.handleChange = this.handleChange.bind(this);
};
handleChange(e){
/*this.setState({
input: e.target.value
})*/
this.props.addText(e.target.value)
};
render(){
return(
<div className="App-header">
<textarea id="editor" value={this.props.text} onChange={this.handleChange}></textarea>
<div id="preview" dangerouslySetInnerHTML={ {__html: marked('Rendered by **marked**.') } }></div>
</div>
)
}
};
// React-Redux
const mapStateToProps = (state) => {
return {
text: state.text
}
};
const mapDispatchToProps = (dispatch) => {
return {
addText: (text) => {
dispatch(addText(text))
}
}
};
const Container = connect(mapStateToProps, mapDispatchToProps)(App);
// eslint-disable-next-line
export default class AppWrapper extends Component {
render() {
return(
<Provider store={store}>
<Container />
</Provider>
);
}
};
The markdown text suppose to be rendered as html in preview element but instead I get TypeError: Object(...) is not a function.
UPDATE: apparently redux was not setup properly and was set to an array instead of object. I fixed that but I still get the same error.
I found the solution, problem was I imported marked as named import {import {marked} from 'marked' instead of import as default like this import marked from 'marked'
I need to change the "global" state of Redux (I believe it's called storage). This is my code:
reducer
export const user = (state = {}, action) => {
console.log(4);
console.log(action.type)
console.log(action.payload)
switch (action.type) {
case C.SET_USER:
console.log(action.payload);
return action.payload;
case C.CLEAR_USER:
return action.payload;
default:
return state;
}
};
Action:
export const setUser = (user = {}) => {
console.log(user);
return {
type: C.SET_USER,
payload: user,
}
};
Calling the action:
const user = {test:true};
setUser(this.state.user);
But if I run this code, it fails and doesn't call the reducer. It calls the action, but not the reducer. What am I missing?
My current app.js code:
export default class App extends Component {
constructor(p) {
super(p);
this.state = {user: null};
}
setUser = () => {
const {uid} = firebase.auth().currentUser;
firebase.database().ref('Users').child(uid).on('value', r => {
const user = r.val();
this.setState({user: user});
console.log(this.state.user);
setUser(this.state.user);
});
};
componentWillMount() {
if (firebase.auth().currentUser) {
this.setUser();
}
firebase.auth().onAuthStateChanged(async () => {
console.log('authChanged');
if (!firebase.auth().currentUser) {
return null;
}
this.setUser();
});
}
render() {
return (
<div className="App">
<Nav/>
</div>
);
}
}
setUser have to be dispatched and not simply called:
store.dispatch(setUser(user));
But that's not really the react way, you'd better use mapDispatchToProps in your connect function to dispatch actions directly from component props. Something along the lines of:
import { setUser } from 'store/user';
// ...
class UserComponent extends React.Component {
// ...
someMethod() {
this.props.setUser(user);
}
}
export default connect(
null,
({setUser: setUser})
)(UserComponent);
This allows your React component to be linked to your Redux store in an optimized and bug-free way. That's also the way most developer use, so you're likely to find a lot of docs on this.
Example: Your connected Component where you want to use your setUser action with redux
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setUser} from '../../actions';
class YourComponent extends Component {
render(){
// now your redux action is passed to component as prop
// and you can use it like
this.props.setUser(some-user);
return()
}
}
export default connect(null, {setUser})(YourComponent);
first of all you have to dispatch action to change the state , second you have to connect your component to the store
to connect your component to the store
...
import { connect } from 'react-redux';
class MyComponent extends React.Component {
...
}
export default connect((store) => ({...}))
when you connect your component to the store you will have access to dispatch function in the props
to dispatch action do this :
this.props.dispatch(setUser());
I believe it's called storage
BTW it called store
Here is my code:
ChartActions.js
import * as types from './ChartTypes.js';
export function chartData(check){
return { type: types.CHART_DATA,check };
}
ChartTypes.js
export const CHART_DATA = 'CHART_DATA';
ChartReducers.js
import {
CHART_DATA,
}from './ChartTypes.js';
const initialState = {
chartData : [],
}
export default function ChartReducers(state = initialState, action) {
switch (action.type) {
case CHART_DATA :
return Object.assign({}, state, {
chartData : action.check
});
default:
return state;
}
}
I am so sure that I setup redux quite accurate and it works perfectly. My problem is:
In a component A I dispatch a function:
handleClick(){
this.props.ChartActions.chartData("test string")
}
so in theory, a component B in my project will receive the string "test string" right after the handeClick function triggered, like this
componentWillReceiveProps(){
console.log(this.props.chartData) // test string
}
But I have no idea why SOMETIMES (it only happens sometimes) I have to trigger handleClick function TWO times in component A so that the component B could be able to get the updated state (in this example, it is "test string"). I supposed it's a bug.
I need the component B will receive the updated state (i.e "test string") RIGHT AFTER the handleClick is triggered only ONE TIME.
I have a container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as ChartActions from '../../components/center-menu/services/ChartActions.js';
import CenterMenu from '../../components/center-menu/center-menu-index.js'
import RightMenu from '../../components/right-content/right-content-index.js'
class Home extends Component {
render() {
return (
<div>
<CenterMenu
ChartActions = {this.props.ChartActions}
/>
<RightMenu
ChartProps={this.props.ChartProps}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
ChartProps: state.ChartReducers
};
}
function mapDispatchToProps(dispatch) {
return {
ChartActions: bindActionCreators(ChartActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Here is the component A where I fire an actions:
import React, { Component } from 'react';
class CenterMenu extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {};
}
}
handleClick(){
this.props.ChartActions.chartData('test string')
}
render() {
return (
<div className="center_menu" onClick={this.handleClick.bind(this)}>
Some stuff
</div>
)
}
}
export default CenterMenu;
And in another component B:
import React, { Component } from 'react';
class RightMenu extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {};
}
}
componentWillReceiveProps(){
console.log(this.props.ChartProps.chartData, "right here")
}
render() {
return (
<div className="center_menu">
Some stuff
</div>
)
}
}
export default RightMenu;
Weird thing:
In Component A, if I trigger the handleClick function by clicking in a div tag, it fires an action that change the initial state to "test string"
But...
In the component B the statement
console.log(this.props.ChartProps.chartData, "right here")
show empty string first like this:
right here
But when I trigger the handleClick function the SECOND TIME in component A , then in component B, in the statement
console.log(this.props.ChartProps.chartData, "right here")
it show the following:
test string "right here"
which is the result I want to achieve.
But I don't understand why I HAVE TO trigger the handleClick function twice. I need it by one click.
The problem is your Home component doesn't rerender the children. Try keeping ChartProps in a state in Home like so:
class Home extends Component {
state = {
ChartProps: null //you can use some default value, this might cause undefined is not an object error in you children
}
componentDidMount() {
const { ChartProps } = this.props
this.setState(() => ({ ChartProps }))
}
componentWillReceiveProps() {
const { ChartProps } = this.props
this.setState(() => ({ ChartProps }))
}
render() {
const { ChartProps } = this.state
return (
<div>
<CenterMenu
ChartActions={this.props.ChartActions}
/>
<RightMenu
ChartProps={ChartProps}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
ChartProps: state.ChartReducers
};
}
function mapDispatchToProps(dispatch) {
return {
ChartActions: bindActionCreators(ChartActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);