Add propTypes to function - reactjs

I would like add propTypes to my function in React for passing data.
I use createContainer (for Meteor Data) and i would like passing my Data for test if user is logged and if is admin for render my component.
My AdminLayout (using in my React Router) :
const AdminLayout = ({component: Component, ...rest}) => {
console.log(AdminContainer)
if (AdminLayout === true) {
return (
<Route {...rest} render={matchProps => (
<div className="app-container">
<HeaderAdmin />
<main className="l-main">
<Component {...matchProps} />
</main>
<FooterAdmin />
</div>
)} />
)
} else {
return (
<Redirect push to="/connexion"/>
)
}
};
AdminLayout.propTypes = {
isLogged: React.PropTypes.bool,
isAdmin: React.PropTypes.bool
}
AdminContainer = createContainer(() => {
const isLogged = Meteor.userId();
const isAdmin = Meteor.call('is-admin', Meteor.userId(), function(err, data) {
if (err) {
return console.log(err);
} else {
return data;
}
});
return {
isLogged,
isAdmin
};
}, AdminLayout);
export default AdminLayout;
My console.log() return juste function ReactMeteorData() :/ I don't know how i can passing my data in my function.
Anyone can help me ?
Thank you community !

Related

Firebase.auth().onstateChanged() not working after clearing browser cache

I cleared my browser cache and now my app cant login
export function IsUserRedirect({ user, loggedInPath, children, ...rest}){
return (
<Route
{...rest}
render={() => {
if(!user){
return children;
}
if(user){
return (
<Redirect
to={{
pathname: loggedInPath
}}
/>
)
}
return null;
}}
/>
)
}
export function ProtectedRoute({ user, children, ...rest}){
return(
<Route
{...rest}
render={({location}) => {
if(user){
return children;
}
if(!user){
return (
<Redirect
to={{
pathname: 'signin',
state: { from : location}
}}
/>
)
}
return null;
}}
/>
)
}
I think it stored my login info on the browser as a localstorage but after clearing it still recognizes it as the user is logged in and takes me to the next page.
But on the next page i have kept a loading state for getting user data, as it doesnt has any user it just keeps loading and goes nowhere. can someone help
export default function useAuthListener(){
const [user, setUser] = useState(JSON.parse(localStorage.getItem('authUser')));
const {firebase} = useContext(FirebaseContext);
useEffect(() => {
const listener = firebase.auth().onAuthStateChanged((authUser) => {
if(authUser){
localStorage.setItem('authUser', JSON.stringify(authUser));
setUser(authUser);
}else {
localStorage.removeItem('authUser');
setUser(null);
}
});
return ()=> listener();
}, []);
return { user};
}
just a quick suggestion:
localStorage.clear();
Source:
(Another aproach might be a reboot, to see if it acts differently...)
Greetings,
Alexander

Firebase/React/Redux Component has weird updating behavior, state should be ok

I am having a chat web app which is connected to firebase.
When I refresh the page the lastMessage is loaded (as the gif shows), however, for some reason, if the component is otherwise mounted the lastMessage sometimes flickers and disappears afterwards like it is overridden. When I hover over it, and hence update the component, the lastMessage is there.
This is a weird behavior and I spent now days trying different things.
I would be very grateful if someone could take a look as I am really stuck here.
The db setup is that on firestore the chat collection has a sub-collection messages.
App.js
// render property doesn't re-mount the MainContainer on navigation
const MainRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => (
<MainContainer>
<Component {...props} />
</MainContainer>
)}
/>
);
render() {
return (
...
<MainRoute
path="/chats/one_to_one"
exact
component={OneToOneChatContainer}
/>
// on refresh the firebase user info is retrieved again
class MainContainer extends Component {
componentDidMount() {
const { user, getUserInfo, firebaseAuthRefresh } = this.props;
const { isAuthenticated } = user;
if (isAuthenticated) {
getUserInfo(user.id);
firebaseAuthRefresh();
} else {
history.push("/sign_in");
}
}
render() {
return (
<div>
<Navigation {...this.props} />
<Main {...this.props} />
</div>
);
}
}
Action
// if I set a timeout around fetchResidentsForChat this delay will make the lastMessage appear...so I must have screwed up the state / updating somewhere.
const firebaseAuthRefresh = () => dispatch => {
firebaseApp.auth().onAuthStateChanged(user => {
if (user) {
localStorage.setItem("firebaseUid", user.uid);
dispatch(setFirebaseAuthUser({uid: user.uid, email: user.email}))
dispatch(fetchAllFirebaseData(user.projectId));
}
});
};
export const fetchAllFirebaseData = projectId => dispatch => {
const userId = localStorage.getItem("firebaseId");
if (userId) {
dispatch(fetchOneToOneChat(userId));
}
if (projectId) {
// setTimeout(() => {
dispatch(fetchResidentsForChat(projectId));
// }, 100);
...
export const fetchOneToOneChat = userId => dispatch => {
dispatch(requestOneToOneChat());
database
.collection("chat")
.where("userId", "==", userId)
.orderBy("updated_at", "desc")
.onSnapshot(querySnapshot => {
let oneToOne = [];
querySnapshot.forEach(doc => {
let messages = [];
doc.ref
.collection("messages")
.orderBy("created_at")
.onSnapshot(snapshot => {
snapshot.forEach(message => {
messages.push({ id: message.id, ...message.data() });
});
});
oneToOne.push(Object.assign({}, doc.data(), { messages: messages }));
});
dispatch(fetchOneToOneSuccess(oneToOne));
});
};
Reducer
const initialState = {
residents: [],
oneToOne: []
};
function firebaseChat(state = initialState, action) {
switch (action.type) {
case FETCH_RESIDENT_SUCCESS:
return {
...state,
residents: action.payload,
isLoading: false
};
case FETCH_ONE_TO_ONE_CHAT_SUCCESS:
return {
...state,
oneToOne: action.payload,
isLoading: false
};
...
Main.js
// ...
render() {
return (...
<div>{React.cloneElement(children, this.props)}</div>
)
}
OneToOne Chat Container
// without firebaseAuthRefresh I don't get any chat displayed. Actually I thought having it inside MainContainer would be sufficient and subscribe here only to the chat data with fetchOneToOneChat.
// Maybe someone has a better idea or point me in another direction.
class OneToOneChatContainer extends Component {
componentDidMount() {
const { firebaseAuthRefresh, firebaseData, fetchOneToOneChat } = this.props;
const { user } = firebaseData;
firebaseAuthRefresh();
fetchOneToOneChat(user.id || localStorage.getItem("firebaseId"));
}
render() {
return (
<OneToOneChat {...this.props} />
);
}
}
export default class OneToOneChat extends Component {
render() {
<MessageNavigation
firebaseChat={firebaseChat}
firebaseData={firebaseData}
residents={firebaseChat.residents}
onClick={this.selectUser}
selectedUserId={selectedUser && selectedUser.residentId}
/>
}
}
export default class MessageNavigation extends Component {
render() {
const {
onClick,
selectedUserId,
firebaseChat,
firebaseData
} = this.props;
<RenderResidentsChatNavigation
searchChat={this.searchChat}
residents={residents}
onClick={onClick}
firebaseData={firebaseData}
firebaseChat={firebaseChat}
selectedUserId={selectedUserId}
/>
}
}
const RenderResidentsChatNavigation = ({
residents,
searchChat,
selectedUserId,
onClick,
firebaseData,
firebaseChat
}) => (
<div>
{firebaseChat.oneToOne.map(chat => {
const user = residents.find(
resident => chat.residentId === resident.residentId
);
const selected = selectedUserId == chat.residentId;
if (!!user) {
return (
<MessageNavigationItem
id={chat.residentId}
key={chat.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
{residents.map(user => {
const selected = selectedUserId == user.residentId;
const chat = firebaseChat.oneToOne.find(
chat => chat.residentId === user.residentId
);
if (_isEmpty(chat)) {
return (
<MessageNavigationItem
id={user.residentId}
key={user.residentId}
chat={chat}
onClick={onClick}
selected={selected}
user={user}
firebaseData={firebaseData}
/>
);
}
})}
</div>
}
}
And lastly the item where the lastMessage is actually displayed
export default class MessageNavigationItem extends Component {
render() {
const { hovered } = this.state;
const { user, selected, chat, isGroupChat, group, id } = this.props;
const { messages } = chat;
const item = isGroupChat ? group : user;
const lastMessage = _last(messages);
return (
<div>
{`${user.firstName} (${user.unit})`}
{lastMessage && lastMessage.content}
</div>
)
}
In the end it was an async setup issue.
In the action 'messages' are a sub-collection of the collection 'chats'.
To retrieve them it is an async operation.
When I returned a Promise for the messages of each chat and awaited for it before I run the success dispatch function, the messages are shown as expected.

this.props.history.push not re-rendering react component

In my component I use this.props.history.push(pathname:.. search:..) to rerender the component and fetch new data form a third party service. When I first call the page it renders. But when I call history push inside the component the URL updates correctly BUT the component doesn't rerender. I read a lot but couldn't get it working. Any ideas?
I'm using react router v4
//index.js
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/login" component={Login}/>
<Route path="/" component={Main}/>
</Switch>
</BrowserRouter>
</Provider>
//Main.js
//PropsRoute is used to push props to logs component so I can use them when fetching new data
const PropsRoute = ({ component: Component, ...rest }) => {
return (
<Route {...rest} render={props => <Component {...props} />}/>
);
};
class Main extends Component {
render() {
return (
<div className="app">
<NavigationBar/>
<div className="app-body">
<SideBar/>
<Switch>
<PropsRoute path="/logs" component={Log}/> //this component is not rerendering
<Route path="/reports" component={Reports}/>
<Route path="/gen" component={Dashboard}/>
<Redirect from="/" to="/gen"/>
</Switch>
</div>
</div>
)
}
}
export default Main;
//inside 'Log' component I call
import React, {Component} from 'react';
import {getSystemLogs} from "../api";
import {Link} from 'react-router-dom';
import _ from "lodash";
import queryString from 'query-string';
let _isMounted;
class Log extends Component {
constructor(props) {
super(props);
//check if query params are defined. If not re render component with query params
let queryParams = queryString.parse(props.location.search);
if (!(queryParams.page && queryParams.type && queryParams.pageSize && queryParams.application)) {
this.props.history.push({
pathname: '/logs',
search: `?page=1&pageSize=25&type=3&application=fdce4427fc9b49e0bbde1f9dc090cfb9`
});
}
this.state = {
logs: {},
pageCount: 0,
application: [
{
name: 'internal',
id: '...'
}
],
types: [
{
name: 'Info',
id: 3
}
],
paginationPage: queryParams.page - 1,
request: {
page: queryParams.page === undefined ? 1 : queryParams.page,
type: queryParams.type === undefined ? 3 : queryParams.type,
pageSize: queryParams.pageSize === undefined ? 25 : queryParams.pageSize,
application: queryParams.application === undefined ? 'fdce4427fc9b49e0bbde1f9dc090cfb9' : queryParams.application
}
};
this.onInputChange = this.onInputChange.bind(this);
}
componentDidMount() {
_isMounted = true;
this.getLogs(this.state.request);
}
componentWillUnmount() {
_isMounted = false;
}
getLogs(request) {
getSystemLogs(request)
.then((response) => {
if (_isMounted) {
this.setState({
logs: response.data.Data,
pageCount: (response.data.TotalCount / this.state.request.pageSize)
});
}
});
}
applyFilter = () => {
//reset page to 1 when filter changes
console.log('apply filter');
this.setState({
request: {
...this.state.request,
page: 1
}
}, () => {
this.props.history.push({
pathname: '/logs',
search: `?page=${this.state.request.page}&pageSize=${this.state.request.pageSize}&type=${this.state.request.type}&application=${this.state.request.application}`
});
});
};
onInputChange = () => (event) => {
const {request} = this.state; //create copy of current object
request[event.target.name] = event.target.value; //update object
this.setState({request}); //set object to new object
};
render() {
let logs = _.map(this.state.logs, log => {
return (
<div className="bg-white rounded shadow mb-2" key={log.id}>
...
</div>
);
});
return (
<main className="main">
...
</main>
);
}
}
export default Log;
Reactjs don't re-run the constructor method when just props or state change, he call the constructor when you first call your component.
You should use componentDidUpdate and do your fetch if your nextProps.location.pathname is different than your this.props.location.pathname (react-router location)
I had this same issue with a functional component and I solved it using the hook useEffect with the props.location as a dependency.
import React, { useEffect } from 'react';
const myComponent = () => {
useEffect(() => {
// fetch your data when the props.location changes
}, [props.location]);
}
This will call useEffect every time that props.location changes so you can fetch your data. It acts like a componentDidMountand componentDidUpdate.
what about create a container component/provider with getderivedstatefromprops lifecycle method, its more react-look:
class ContainerComp extends Component {
state = { needRerender: false };
static getderivedstatefromprops(nextProps, nextState) {
let queryParams = queryString.parse(nextProps.location.search);
if (!(queryParams.page && queryParams.type && queryParams.pageSize && queryParams.application)) {
return { needRefresh: true };
} else {
return { needRefresh: false };
}
}
render() {
return (
<div>
{this.state.needRefresh ? <Redirect params={} /> : <Log />}
</div>
);
}
}

App re-renders when redux store is updated

Everytime I dispatch an action and update my store, my entire app re-renders. I assume I'm doing anything wrong w/ my connect/mapDispatchToProps function? Is it right to pass { ...actions } as a 2nd argument to my connect function in App.js?
Here's my code:
class App extends Component {
componentDidMount() {
this.props.fetchPages(api.API_PAGES);
this.props.fetchPosts(api.API_POSTS);
window.addEventListener('resize', () => {
this.props.resizeScreen(window.innerWidth);
});
}
render() {
return (
<div>
{this.props.app.showIntro && <Intro {...this.props} endIntro={this.props.endIntro} />}
{!this.props.pages.isFetching && this.props.pages.data &&
<div>
<Navbar {...this.props} />
<Layout {...this.props}>
<Switch location={this.props.location}>
<Route
path={routes.HOME}
exact
component={() => (
<Home {...this.props} />
)}
/>
<Route
path={routes.ABOUT}
component={() => (
<About {...this.props} />
)}
/>
<Route
path={routes.NEWS}
exact
component={() => (
<News {...this.props} />
)}
/>
<Route
component={NotFound}
/>
</Switch>
</Layout>
</div>
}
</div>
);
}
}
function mapStateToProps(state) {
return {
app: state.app,
pages: state.pages,
posts: state.posts
};
}
export default withRouter(connect(
mapStateToProps,
{ ...actions }
)(App));
actions/index.js
export function resizeScreen(screenWidth) {
return {
type: types.RESIZE_SCREEN,
screenWidth
};
}
export function endIntro() {
return {
type: types.END_INTRO,
showIntro: false
};
}
export function toggleNav(bool) {
return {
type: types.TOGGLE_NAV,
navOpen: bool
};
}
export function toggleVideoPlayer(bool) {
return {
type: types.TOGGLE_VIDEO_PLAYER,
videoIsPlaying: bool
};
}
export function toggleScroll(bool) {
return {
type: types.TOGGLE_SROLL,
disableScroll: bool
};
}
// pages
function requestPages() {
return {
type: types.REQUEST_PAGES
};
}
function receivePages(data) {
return {
type: types.RECEIVE_PAGES,
data
};
}
// posts
function requestPosts() {
return {
type: types.REQUEST_POSTS
};
}
function receivePosts(data) {
return {
type: types.RECEIVE_POSTS,
data
};
}
// creators
export function fetchPages(path) {
return (dispatch, getState) => {
const { pages } = getState();
if (pages.isFetching) return;
dispatch(requestPages());
fetch(`${process.env.API_URL}${path}`)
.then(response => response.json())
.then(json => dispatch(receivePages(json)));
};
}
export function fetchPosts(path) {
return (dispatch, getState) => {
const { posts } = getState();
if (posts.isFetching) return;
dispatch(requestPosts());
fetch(`${process.env.API_URL}${path}`)
.then(response => response.json())
.then(json => dispatch(receivePosts(json)));
};
}
reducers/app.js:
const initialState = {
screenWidth: typeof window === 'object' ? window.innerWidth : null,
showIntro: true,
navOpen: false,
videoIsPlaying: false,
disableScroll: false
};
export default function app(state = initialState, action) {
switch (action.type) {
case RESIZE_SCREEN: {
return {
...state,
screenWidth: action.screenWidth
};
}
case TOGGLE_NAV: {
return {
...state,
navOpen: !state.navOpen
};
}
case END_INTRO: {
return {
...state,
showIntro: false
};
}
case TOGGLE_VIDEO_PLAYER: {
return {
...state,
videoIsPlaying: !state.videoIsPlaying
};
}
case TOGGLE_SCROLL: {
return {
...state,
disableScroll: !state.disableScroll
};
}
default: {
return state;
}
}
}
reducers/posts.js is similar to reducers/pages.js:
const initialState = {
isFetching: false
};
export default function posts(state = initialState, action) {
switch (action.type) {
case REQUEST_POSTS: {
return {
...state,
isFetching: true
};
}
case RECEIVE_POSTS: {
return {
...state,
isFetching: false,
data: action.data
};
}
default: {
return state;
}
}
}
If you have an issue with too much of your app re-rendering with each redux update, it helps to use more connected components and limit the amount of state being passed to each one. I see that you're spreading props down into each page, this is convenient, but a common cause of inefficient re-renders.
<Home {...this.props} />
<About {...this.props} />
<News {...this.props} />
This could result in too much data being passed to each of these components, and each redux action causing the entire page to re-render.
Another potential issue that I see is that you're using an inline anonymous function as the component callback for your routes
<Route
path={routes.ABOUT}
component={() => (
<About {...this.props} />
)}
/>
I'm not exactly sure how React Router is working here, but a potential issue is that each time the router re-renders, those anonymous functions are created brand new again. React will see them as a new component and force a re-render. You can resolve this by making each of these a connected component that pulls in their own props, and then update the router like so
<Route
path={routes.ABOUT}
component={ConnectedAbout}
/>
This is how redux should work: props are changing so the connected componentin re-redered.
You can:
implements your shouldComponentUpdate to limit rerender (note: this will also prevent subcomponents)
use PureComponent instead of Component base class so you'll switch to shallow compare
Limit numbers of connected props, maybe you can connect subcomponents instead.

Code splitting route components wrapped in a HOC with React Loadable

I am running into problems using React Loadable with route based code splitting using Webpack 3.11.
When I try to render my app on the server my async modules immediately resolve without waiting for the promise. Thus the SSR output becomes <div id="root"></div>.
App.js:
const App = () => (
<Switch>
{routes.map((route, index) => (
<Route key={index} path={route.path} render={routeProps => {
const RouteComponent = route.component
return <RouteComponent {...routeProps} />
}} />
))}
</Switch>
)
I've defined my async route components with React Loadable like this:
Routes.js
function Loading ({ error }) {
if (error) {
return 'Oh nooess!'
} else {
return <h3>Loading...</h3>
}
}
const Article = Loadable({
loader: () => import(/* webpackChunkName: "Article" */ '../components/contentTypes/Article'),
loading: Loading
})
const Page = Loadable({
loader: () => import(/* webpackChunkName: "Page" */ '../components/contentTypes/Page'),
loading: Loading,
render (loaded, props) {
let Component = WithSettings(loaded.default)
return <Component {...props}/>
}
})
export default [
{
path: `/:projectSlug/:env${getEnvironments()}/article/:articleSlug`,
component: Article,
exact: true
},
{
path: `/:projectSlug/:env${getEnvironments()}/:menuSlug?/:pageSlug?`,
component: Page
}
]
WithSettings.js
export default (WrappedComponent: any) => {
class WithSettings extends React.Component<WithSettingsProps, WithSettingsState> {
static displayName = `WithSettings(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`
state = {
renderWidth: 1200
}
componentDidMount () {
this.loadSettings({ match: { params: { projectSlug: '', env: '' } } })
window.addEventListener('resize', this.onResize)
this.onResize()
}
componentWillUnmount () {
if (isClient) {
window.removeEventListener('resize', this.onResize)
}
}
componentDidUpdate (oldProps) {
this.loadSettings(oldProps)
}
onResize = () => {
this.setState({ renderWidth: this.getLayoutWidth() })
}
getLayoutWidth () {
return (document.body && document.body.offsetWidth) || 0
}
loadSettings (oldProps) {
const { settings, request, getNewSettings } = this.props
const { projectSlug: oldProjectSlug, env: oldEnv } = oldProps.match.params
const { projectSlug: newProjectSlug, env: newEnv } = this.props.match.params
if (
(
oldProjectSlug !== newProjectSlug ||
oldEnv !== newEnv
) ||
(
settings === undefined ||
(request.networkStatus === 'ready')
)
) {
getNewSettings()
}
}
render () {
const { settings, request, history, location, match } = this.props
const { renderWidth } = this.state
if (!settings || !request || request.networkStatus === 'loading') {
return <div />
}
if (request.networkStatus === 'failed') {
return <ErrorBlock {...getErrorMessages(match.params, 'settings')[4044]} fullscreen match={match} />
}
return (
<WrappedComponent
settings={settings}
settingsRequest={request}
history={history}
location={location}
match={match}
renderWidth={renderWidth}
/>
)
}
}
hoistNonReactStatic(WithSettings, WrappedComponent)
return connect(mapStateToProps, mapDispatchToProps)(WithSettings)
}
I've managed to narrow it down to the WithSettings HOC that I am using to wrap my route components in. If I don't use the WithSettings HOC (as with the Article route) then my SSR output waits for the async import to complete, and the server generated html includes markup related to the route (good!). If I do use the HOC (as with the Page route) then the module immediately resolves and my SSR output turns into <div id="root"></div because it no longer waits for the dynamic import to complete before rendering. Problem is: I need the WithSettings HOC in all my routes as it fetches required data from the server that I need to render the app.
Can anyone tell me how I can use a HOC and use React Loadable's async component for route components so that it works on the server?
Managed to figure it out.
It was due to my HOC. In the render method it would return <div /> when settings or request where undefined or request.networkStatus is loading. It turns out this tripped up server side rendering. Removing this block was enough to make it work.

Resources