Hi I've seen this same error and multiple possible solutions but none has been able to solve my issue (Probably because I'm lacking in depth understanding of the whole React structure).
I know that context.insertCss.apply(context, styles); isn't receiving the context and that's why the error is thrown, I've added the ContextProvider but I'm afraid this could be conflicting with my routing setup. Also used Daniel's answer to this question [Why does isomorphic-style-loader throw a TypeError: Cannot read property 'apply' of undefined when being used in unison with CSS-Modules
server index.js
app.get('/*', (req, res) => {
const matchingRoutes = matchRoutes(Routes, req.url);
let promises = [];
matchingRoutes.forEach(route => {
if (route.loadData) {
promises.push(route.loadData());
}
});
// promise.then(data => {
Promise.all(promises).then(dataArr => {
// Let's add the data to the context
// const context = { data };
// const context = { dataArr };
const css = new Set()
const context = { insertCss: (...styles) => styles.forEach(style => css.add(style._getCss()))}
const app = React.renderToString(
<StaticRouter location={req.url}>
<ContextProvider context={context}>
<App/>
</ContextProvider>
</StaticRouter>
)
const indexFile = path.resolve('./build/index.html');
fs.readFile(indexFile, 'utf8', (err, indexData) => {
if (err) {
console.error('Something went wrong:', err);
return res.status(500).send('Oops, better luck next time!');
}
if (context.status === 404) {
res.status(404);
}
if (context.url) {
return res.redirect(301, context.url);
}
return res.send(
indexData
.replace('<style id="myStyle"></style>',`<style type="text/css" id="myStyle">${[...css].join('')}</style>`)
.replace('<div id="root"></div>', `<div id="root">${app}</div>`)
.replace(
'</body>',
`<script>window.__ROUTE_DATA__ = ${serialize(dataArr)}</script></body>`
)
);
});
});
});
Added on the server the ContextProvider in the renderToString(..) method, also I'm replacing the html body so the received CSS is attached to the HTML response.
ContextProvider.js
import React from 'react';
import PropTypes from 'prop-types'
import App from './App'
class ContextProvider extends React.Component {
static childContextTypes = {
insertCss: PropTypes.func,
}
getChildContext() {
return {
...this.props.context
}
}
render() {
return <App {
...this.props
}
/>
}
}
export default ContextProvider
Used the context provider from Daniel's answer (Reference above)
Client index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import ContextProvider from './ContextProvider';
const context = {
insertCss: (...styles) => {
const removeCss = styles.map(x => x._insertCss());
return () => {
removeCss.forEach(f => f());
};
},
}
ReactDOM.hydrate(
<BrowserRouter>
<ContextProvider context={context} />
</BrowserRouter>,
document.getElementById('root')
);
Passing the context through the ContextProvider as supposed.
App.js used inside the ContextProvider
import React from 'react';
import { renderRoutes } from 'react-router-config';
import { Switch, NavLink } from 'react-router-dom';
import Routes from './routes';
export default props => {
return (
<div>
<ul>
<li>
<NavLink to="/">Home</NavLink>
</li>
<li>
<NavLink to="/todos">Todos</NavLink>
</li>
<li>
<NavLink to="/posts">Posts</NavLink>
</li>
</ul>
<Switch>
{renderRoutes(Routes)}
</Switch>
</div>
);
};
Home.js where I'm trying to test the custom style
import React from 'react';
import withStyles from '../../node_modules/isomorphic-style-loader/withStyles'
import styles from '../scss/Home.scss';
function Home(props, context) {
return (
<h1>Hello, world!</h1>
)
}
export default withStyles(styles)(Home);
routes.js describes the routes used.
import Home from './components/Home';
import Posts from './components/Posts';
import Todos from './components/Todos';
import NotFound from './components/NotFound';
import loadData from './helpers/loadData';
const Routes = [
{
path: '/',
exact: true,
component: Home
},
{
path: '/posts',
component: Posts,
loadData: () => loadData('posts')
},
{
path: '/todos',
component: Todos,
loadData: () => loadData('todos')
},
{
component: NotFound
}
];
export default Routes;
Almost sure there is an easy fix for this issue but it doesn't seem so trivial to me. Thank you in advance.
Please try to use the built in StyleContext of isomorphic-style-loader instead of custom context provider.
server.js:
import StyleContext from 'isomorphic-style-loader/StyleContext';
const insertCss = (...styles) => {
const removeCss = styles.map(style => style._insertCss());
return () => removeCss.forEach(dispose => dispose());
};
ReactDOM.render(
<StyleContext.Provider value={{ insertCss }}>
<Router>{renderRoutes(Routes)}</Router>
</StyleContext.Provider>,
document.getElementById('root')
);
client.js:
app.get('/*', function(req, res) {
const context = {};
const css = new Set(); // CSS for all rendered React components
const insertCss = (...styles) => styles.forEach(style => css.add(style._getCss()));
const component = ReactDOMServer.renderToString(
<StyleContext.Provider value={{ insertCss }}>
<StaticRouter location={req.url} context={context}>
{renderRoutes(Routes)}
</StaticRouter>
</StyleContext.Provider>
);
if (context.url) {
res.writeHead(301, { Location: context.url });
res.end();
} else {
res.send(Html('React SSR', component));
}
});
You can see example project here: https://github.com/digz6666/webpack-loader-test/tree/ssr-2
Related
I'm doing a course of React Js, in the terminal not have errors but i have a error in the console of the navigator.
This is the error:
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of ToggleLikeMutation.
at ToggleLikeMutation (webpack://curso-platzi-react-avanzado/./src/container/ToggleLikeMutation.js?:16:23)
at article
at styled.article (webpack://curso-platzi-react-avanzado/./node_modules/styled-components/dist/styled-components.browser.esm.js?:29:19307)
at PhotoCard (webpack://curso-platzi-react-avanzado/./src/components/PhotoCard/index.js?:31:17)
at ul
at ListOfPhotoCards (webpack://curso-platzi-react-avanzado/./src/components/ListOfPhotoCards/index.js?:22:25)
at div
at App
at ApolloProvider (webpack://curso-platzi-react-avanzado/./node_modules/#apollo/client/react/context/ApolloProvider.js?:12:21)
Here's my documents:
FavButton.js
import React from 'react'
import { MdFavoriteBorder, MdFavorite } from 'react-icons/md'
import { Button } from './styles'
export const FavButton = ({ liked, likes, onClick }) => {
const Icon = liked ? MdFavorite : MdFavoriteBorder
return (
<Button onClick={onClick}>
<Icon size='32px' /> {likes} likes!
</Button>
)
}
ToggleLikeMutation.js
import React from 'react'
import { Mutation, gql } from '#apollo/client'
const LIKE_PHOTO = gql`
mutation likeAnonymusPhoto($input: LikePhoto!) {
likeAnonymousPhoto(input: $input) {
id,
liked,
likes
}
}
`
export const ToggleLikeMutation = ({ children }) => {
return (
<Mutation mutation={LIKE_PHOTO}>
{children}
</Mutation>
)
}
PhotoCard.js
import React, { Fragment } from 'react'
import { Article, ImgWrapper, Img } from './styles'
import { useLocalStorage } from '../../hooks/useLocalStorage'
import { useNearScreen } from '../../hooks/useNearScreen'
import { FavButton } from '../FavButton/index.js'
import { ToggleLikeMutation } from '../../container/ToggleLikeMutation'
const DEFAULT_IMAGE = 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60'
export const PhotoCard = ({
id, likes = 0,
src = DEFAULT_IMAGE
}) => {
const [show, element] = useNearScreen()
const key = `like-${id}`
const [liked, setLiked] = useLocalStorage(key, false)
return (
<Article ref={element}>
{
show &&
<>
<a href={`/?detail=${id}`}>
<ImgWrapper>
<Img src={src} />
</ImgWrapper>
</a>
<ToggleLikeMutation>
{
(toggleLike) => {
const handleFavClick = () => {
!liked && toggleLike({
variables: {
input: { id }
}
})
setLiked(!liked)
}
return (
<FavButton
liked={liked} likes={likes} onClick={handleFavClick}
/>
)
}
}
</ToggleLikeMutation>
</>
}
</Article>
)
}
App.js
import React, { Fragment } from 'react'
import { ListOfCategories } from './components/ListOfCategories'
import { GlobalStyle } from './styles/GlobalStyles'
import { ListOfPhotoCards } from './components/ListOfPhotoCards'
import { Logo } from './components/Logo'
import { PhotoCardWithQuery } from './container/PhotoCardWithQuery'
export const App = () => {
const urlParams = new window.URLSearchParams(window.location.search)
const detailId = urlParams.get('detail')
return (
<div>
<GlobalStyle />
<Logo />
{
detailId
? <PhotoCardWithQuery id={detailId} />
: <>
<ListOfCategories />
<ListOfPhotoCards categoryId={1} />
</>
}
</div>
)
}
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { ApolloClient, HttpLink, InMemoryCache } from 'apollo-boost'
import { ApolloProvider } from '#apollo/client'
import { App } from './App'
const cache = new InMemoryCache()
const link = new HttpLink({
uri: 'https://petgram-jv-2011.vercel.app/graphql'
})
const client = new ApolloClient({
cache,
link
})
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('app'))
I am unclear as to why I am getting my URL to redirect but it's not rendering the corresponding component. I am referring to the Result component. I am not getting any errors, it is successfully unmounting OnfidoSDK, but it's not rendering my Result component.
import React, {useEffect, useState} from 'react';
import axios from 'axios';
import * as OnfidoSDK from 'onfido-sdk-ui/dist/onfido.min.js';
import 'onfido-sdk-ui/dist/style.css';
import {useHistory} from 'react-router-dom';
const onfidoContainerId = 'onfido-sdk-wrapper';
const transmitAPI = 'third/party/api/url';
const useOnfidoFetch = (URL) => {
const [token, setToken] = useState();
const [id, setId] = useState();
const history = useHistory();
useEffect(() => {
axios
.get("http://localhost:5000/post_stuff")
.then((response) => response.data.data.data.json_data)
.then((json_data) => {
console.log("this is the json data", json_data);
const id = json_data.applicant_id;
const token = json_data.onfido_sdk_token;
setId(id);
setToken(token);
});
}, [URL]);
useEffect(() => {
if (!token) return;
console.log("this is working!");
onfidoOut = OnfidoSDK.init({
token,
containerId: "root",
steps: [
{
type: "welcome",
options: {
title: "Open your new bank account",
},
},
"document",
],
onComplete: function (data) {
console.log("everything is complete");
console.log("this is the applicant id", id);
let obj;
axios
.post("http://localhost:5000/post_id", {
applicant_id: id,
})
.then((response) => {
obj = response.data.data.data.json_data.result;
onfidoOut.tearDown();
handleRedirect();
});
function handleRedirect() {
history.push('/result');
}
},
});
}, [id, token]);
};
export default function() {
const URL = `${transmitAPI}/anonymous_invoke?aid=onfido_webapp`;
const result = useOnfidoFetch(URL, {});
return (
<div id={onfidoContainerId} />
)
}
I am not getting any errors in console. This is the App.js file with all of my components:
import Landing from './components/Landing';
import Onfido from './components/Onfido';
import Result from './components/Result';
export default () => {
return (
<div>
<StylesProvider>
<BrowserRouter>
<Switch>
<Route exact path="/onfido" component={Onfido} />
<Route exact path="/result" component={Result} />
<Route path="/" component={Landing} />
</Switch>
</BrowserRouter>
</StylesProvider>
</div>
);
};
Now this is a microfrontend application. And in the container app inside of src/components./MarketingApp.js, I have is written in the following manner:
import { mount } from "marketing/MarketingApp";
import React, { useRef, useEffect } from "react";
import { useHistory } from "react-router-dom";
export default () => {
const ref = useRef(null);
const history = useHistory();
useEffect(() => {
const { onParentNavigate } = mount(ref.current, {
initialPath: history.location.pathname,
onNavigate: ({ pathname: nextPathname }) => {
const { pathname } = history.location;
if (pathname !== nextPathname) {
history.push(nextPathname);
}
},
});
history.listen(onParentNavigate);
}, []);
return <div ref={ref} />;
};
Since it's already using useHistory in the container, could that somehow be colliding with using useHistory on the sub app?
When I do a console log of history on the sub app, I get back an object with the property of location, pathname: "/result"
So on troubleshooting, I noticed that if I refresh the page, then the component renders. Not sure why that is though. Or could the issue be because I am rendering it in this manner:
import React from "react";
const onfidoHTML = `<h1>Thank You!</h1><H2>Your documents have been submitted for review.</h2>`;
export default function Result() {
return <div dangerouslySetInnerHTML={{ __html: onfidoHTML }} />;
}
I am properly using history, in my container app, there is a src/bootstrap.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import {createMemoryHistory, createBrowserHistory} from 'history';
import App from './App';
const mount = (el, {onNavigate, defaultHistory, initialPath}) => {
const history = defaultHistory || createMemoryHistory({
initialEntries: [initialPath],
});
if (onNavigate) {
history.listen(onNavigate);
}
ReactDOM.render(<App history={history} />, el);
return {
onParentNavigate({ pathname: nextPathname }) {
const { pathname } = history.location;
if (pathname !== nextPathname) {
history.push(nextPathname);
}
},
};
};
if (process.env.NODE_ENV === 'development') {
const devRoot = document.querySelector('#_marketing-dev-root');
if (devRoot) {
mount(devRoot, {defaultHistory: createBrowserHistory()});
}
}
export { mount };
I am developing a website in which I want to be able to access the state information anywhere in the app. I have tried several ways of implementing state but I always get following error message:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of SOS.
Here is my SOS->index.js file:
import React, { useContext } from 'react';
import axios from 'axios';
import CONST from '../utils/Constants';
import { Grid, Box, Container } from '#material-ui/core';
import { styled } from '#material-ui/styles';
import { Header } from '../Layout';
import ListItem from './ListItem';
import SOSButton from './SOSButton';
import FormPersonType from './FormPersonType';
import FormEmergencyType from './FormEmergencyType';
import StateContext from '../App';
import Context from '../Context';
export default function SOS() {
const { componentType, setComponentType } = useContext(Context);
const timerOn = false;
//'type_of_person',
const ambulance = false;
const fire_service = false;
const police = false;
const car_service = false;
//static contextType = StateContext;
const showSettings = event => {
event.preventDefault();
};
const handleComponentType = e => {
console.log(e);
//this.setState({ componentType: 'type_of_emergency' });
setComponentType('type_of_emergency');
};
const handleEmergencyType = new_emergency_state => {
console.log(new_emergency_state);
// this.setState(new_emergency_state);
};
const onSubmit = e => {
console.log('in OnSubmit');
axios
.post(CONST.URL + 'emergency/create', {
id: 1,
data: this.state //TODO
})
.then(res => {
console.log(res);
console.log(res.data);
})
.catch(err => {
console.log(err);
});
};
let component;
if (componentType == 'type_of_person') {
component = (
<FormPersonType handleComponentType={this.handleComponentType} />
);
} else if (componentType == 'type_of_emergency') {
component = (
<FormEmergencyType
handleComponentType={this.handleComponentType}
handleEmergencyType={this.handleEmergencyType}
emergencyTypes={this.state}
timerStart={this.timerStart}
onSubmit={this.onSubmit}
/>
);
}
return (
<React.Fragment>
<Header title="Send out SOS" />
<StateContext.Provider value="type_of_person" />
<Container component="main" maxWidth="sm">
{component}
</Container>
{/*component = (
<HorizontalNonLinearStepWithError
handleComponentType={this.handleComponentType}
/>*/}
</React.Fragment>
);
}
I would really appreciate your help!
Just for reference, the Context file is defined as follows:
import React, { useState } from 'react';
export const Context = React.createContext();
const ContextProvider = props => {
const [componentType, setComponentType] = useState('');
setComponentType = 'type_of_person';
//const [storedNumber, setStoredNumber] = useState('');
//const [functionType, setFunctionType] = useState('');
return (
<Context.Provider
value={{
componentType,
setComponentType
}}
>
{props.children}
</Context.Provider>
);
};
export default ContextProvider;
EDIT: I have changed my code according to your suggestions (updated above). But now I get following error:
TypeError: Cannot read property 'componentType' of undefined
Context is not the default export from your ../Context file so you have to import it as:
import { Context } from '../Context';
Otherwise, it's trying to import your Context.Provider component.
For your file structure/naming, the proper usage is:
// Main app file (for example)
// Wraps your application in the context provider so you can access it anywhere in MyApp
import ContextProvider from '../Context'
export default () => {
return (
<ContextProvider>
<MyApp />
</ContextProvider>
)
}
// File where you want to use the context
import React, { useContext } from 'react'
import { Context } from '../Context'
export default () => {
const myCtx = useContext(Context)
return (
<div>
Got this value - { myCtx.someValue } - from context
</div>
)
}
And for godsakes...rename your Context file, provider, and everything in there to something more explicit. I got confused even writing this.
The function below is meant to check if user is authorized before rendering a route with react-router. I got the basic code from somewhere I forgot.
This was working fine when I had a simple synchronous check to local storage, but when I introduced an Axios call to the server things got messy.
I read a lot of the SO's questions on like issues, ie, promises not returning value and I seem to have modified my code to conform to the regular pitfalls.
I particularly used the last answer to this question to fix my setup, but the issue remains.
On the code below, the console.logs output the parameters correctly, meaning that the failure with related to the return statement.
The specific error is:
Error: AuthRoute(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
import React from 'react';
import PropTypes from 'prop-types';
import { Redirect, Route } from 'react-router-dom';
import axios from 'axios';
const PRIVATE_ROOT = '/account';
const PUBLIC_ROOT = '/';
const RenderRoute = ({response, isPrivate, Route, Redirect, component, ...props}) => {
console.log('is_logged', response.data.is_logged); // boolean
console.log('isPrivate', isPrivate); // boolean
console.log('response', response); // axios object
console.log('route', Route); // function route
console.log('component', component); // home component
console.log('props', {...props}); // route props
if (response.data.is_logged) {
// set last activity on local storage
let now = new Date();
now = parseInt(now.getTime() / 1000);
localStorage.setItem('last_active', now );
return isPrivate
? <Route { ...props } component={ component } />
: <Route { ...props } component={ component } />;
} else {
return isPrivate
? <Redirect to={ PUBLIC_ROOT } />
: <Route { ...props } component={ component } />;
}
}
const AuthRoute = ({component, ...props}) => {
const { isPrivate } = component;
let last_active_client = localStorage.getItem('last_active') ? localStorage.getItem('last_active') : 0;
let data = {
last_active_client: last_active_client
}
let getApiSession = new Promise((resolve) => {
resolve(axios.post('/api-session', data));
});
getApiSession.then(response => RenderRoute({response, isPrivate,Route, Redirect, component, ...props})
).catch((error) => {
console.log(error);
});
}
export default AuthRoute;
This is what worked for me after comments above. It was necessary to create a separate component class. It's not tremendously elegant, but works.
The code needs to be placed into componentWillReceiveProps() for it to update at each new props. I know componentWillReceiveProps() is being deprecated. I will handle that separately.
/auth/auth.js
import React from 'react';
import RenderRoute from './components/render_route';
const AuthRoute = ({component, ...props}) => {
let propsAll = {...props}
return (
<RenderRoute info={component} propsAll={propsAll} />
)
}
export default AuthRoute;
/auth/components/render_route.js
import React from 'react';
import PropTypes from 'prop-types';
import { Redirect, Route } from 'react-router-dom';
import axios from 'axios';
const PRIVATE_ROOT = '/account';
const PUBLIC_ROOT = '/';
class RenderRoute extends React.Component {
constructor (props) {
super(props);
this.state = {
route: ''
}
}
componentWillReceiveProps(nextProps, prevState){
const { isPrivate } = this.props.info;
let last_active_client = localStorage.getItem('last_active') ? localStorage.getItem('last_active') : 0;
let data = {
last_active_client: last_active_client
}
axios.post('/api-session', data).then(response => {
let isLogged = response.data.is_logged;
if(response.data.is_logged) {
// set last activity on local storage
let now = new Date();
now = parseInt(now.getTime() / 1000);
localStorage.setItem('last_active', now );
this.setState({ route: isPrivate
? <Route { ...this.props.propsAll } component={ this.props.info } />
: <Route { ...this.props.propsAll } component={ this.props.info } />
})
} else {
this.setState({ route: isPrivate
? <Redirect to={ PUBLIC_ROOT } />
: <Route { ...this.props.propsAll } component={ this.props.info } />
})
}
}).catch((error) => {
console.log(error);
});
}
render() {
return (
<div>
{this.state.route}
</div>
)
}
}
export default RenderRoute;
Tried to look through similar questions, but didn't find similar issues.
I am trying to implement sorts by name and amount in my app, this event is triggered in this component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { sortByExpenseName, sortByExpenseAmount } from '../actions/expensesFilters';
class ExpensesListFilter extends Component {
onSortByExpenseName = () => {
this.props.sortByExpenseName();
};
onSortByExpenseAmount = () => {
this.props.sortByExpenseAmount();
}
render() {
return (
<div>
<span>Expense Name</span>
<button onClick={this.onSortByExpenseName}>Sort me by name</button>
<button onClick={this.onSortByExpenseAmount}>Sort me by amount</button>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
sortByExpenseName: () => dispatch(sortByExpenseName()),
sortByExpenseAmount: () => dispatch(sortByExpenseAmount()),
});
export default connect(null, mapDispatchToProps)(ExpensesListFilter);
for that I am using following selector:
export default (expenses, { sortBy }) => {
return expenses.sort((a, b) => {
if (sortBy === 'name') {
return a.name < b.name ? 1 : -1;
} else if (sortBy === 'amount') {
return parseInt(a.amount, 10) < parseInt(b.amount, 10) ? 1 : -1;
}
});
};
I run this selector in mapStateToProps function for my ExpensesList component here:
import React from 'react';
import { connect } from 'react-redux';
import ExpensesItem from './ExpensesItem';
// my selector
import sortExpenses from '../selectors/sortExpenses';
const ExpensesList = props => (
<div className="content-container">
{props.expenses && props.expenses.map((expense) => {
return <ExpensesItem key={expense.id} {...expense} />;
}) }
</div>
);
// Here I run my selector to sort expenses
const mapStateToProps = (state) => {
return {
expenses: sortExpenses(state.expensesData.expenses, state.expensesFilters),
};
};
export default connect(mapStateToProps)(ExpensesList);
This selector updates my filter reducer, which causes my app state to update:
import { SORT_BY_EXPENSE_NAME, SORT_BY_EXPENSE_AMOUNT } from '../actions/types';
const INITIAL_EXPENSE_FILTER_STATE = {
sortBy: 'name',
};
export default (state = INITIAL_EXPENSE_FILTER_STATE, action) => {
switch (action.type) {
case SORT_BY_EXPENSE_NAME:
return {
...state,
sortBy: 'name',
};
case SORT_BY_EXPENSE_AMOUNT:
return {
...state,
sortBy: 'amount',
};
default:
return state;
}
};
Sort event causes my state to update, the expenses array in my expenses reducer below is updated and sorted by selector, BUT the ExpensesList component doesn't re-render after my expenses array in state is updated.
What I want my ExpensesList component to do, is to re-render with sorted expenses array and sort ExpensesItem components in list.
What could be the reason why it fails? Pretty sure I am missing out something essential, but can't figure out what. My expenses reducer:
import { FETCH_EXPENSES } from '../actions/types';
const INITIAL_STATE = {};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_EXPENSES:
return {
...state,
expenses: action.expenses.data,
};
default:
return state;
}
};
All these components are childs to this parent component:
import React from 'react';
import ExpensesListFilter from './ExpensesListFilter';
import ExpensesList from './ExpensesList';
const MainPage = () => (
<div className="box-layout">
<div className="box-layout__box">
<ExpensesListFilter />
<ExpensesList />
</div>
</div>
);
export default MainPage;
App.js file (where I run startExpenseFetch)
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import 'normalize.css/normalize.css';
import AppRouter, { history } from './routers/AppRouter';
import configureStore from './store/configureStore';
import LoadingPage from './components/LoadingPage';
import { startExpenseFetch } from './actions/expensesData';
import './styles/styles.scss';
const store = configureStore();
const jsx = (
<Provider store={store}>
<AppRouter />
</Provider>
);
let hasRendered = false;
const renderApp = () => {
if (!hasRendered) {
ReactDOM.render(jsx, document.getElementById('app'));
hasRendered = true;
}
};
store.dispatch(startExpenseFetch()).then(() => {
renderApp();
});
ReactDOM.render(<LoadingPage />, document.getElementById('app'));
Rest of files:
ExpenseItem Component:
import React from 'react';
const ExpenseItem = ({ amount, name }) => (
<div>
<span>{name}</span>
<span>{amount}</span>
</div>
);
export default ExpenseItem;
Action creators:
expensesData.js
import axios from 'axios';
import { FETCH_EXPENSE } from './types';
// no errors here
const ROOT_URL = '';
export const fetchExpenseData = expenses => ({
type: FETCH_EXPENSE,
expenses,
});
export const startExpenseFetch = () => {
return (dispatch) => {
return axios({
method: 'get',
url: `${ROOT_URL}`,
})
.then((response) => {
dispatch(fetchExpenseData(response));
console.log(response);
})
.catch((error) => {
console.log(error);
});
};
};
expensesFilters.js
import { SORT_BY_EXPENSE_NAME, SORT_BY_EXPENSE_AMOUNT } from './types';
export const sortByExpenseName = () => ({
type: SORT_BY_EXPENSE_NAME,
});
export const sortByExpenseAmount = () => ({
type: SORT_BY_EXPENSE_AMOUNT,
});
configureStores.js file
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import expensesDataReducer from '../reducers/expensesData';
import expensesFilterReducer from '../reducers/expensesFilters';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default () => {
const store = createStore(
combineReducers({
expensesData: expensesDataReducer,
expensesFilters: expensesFilterReducer,
}),
composeEnhancers(applyMiddleware(thunk))
);
return store;
};
AppRouter.js file
import React from 'react';
import { Router, Route, Switch, Link, NavLink } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import MainPage from '../components/MainPage';
import NotFoundPage from '../components/NotFoundPage';
export const history = createHistory();
const AppRouter = () => (
<Router history={history}>
<div>
<Switch>
<Route path="/" component={MainPage} exact={true} />
<Route component={NotFoundPage} />
</Switch>
</div>
</Router>
);
export default AppRouter;
Don't you have a typo on your call to your selector? :)
// Here I run my selector to sort expenses
const mapStateToProps = (state) => {
return {
expenses: sortExpenses(state.expensesData.expenses, state.expnsesFilters),
};
};
state.expnsesFilters look like it should be state.expensesFilters
Which is one of the reasons you should make your sortExpenses selector grab itself the parts of the state it needs and do it's job on its own. You could test it isolation and avoid mistakes like this.
I found a reason why it happens, in my selector I was mutating my app's state. I wasn't returning a new array from it, and was changing the old one instead, that didn't trigger my vue layer to re-render. Fixed it and it works now.