How to put reducer inside reducer - redux

I don't know, what is the correct way how to combine my reducers.
In root reducer I have page reducers like this:
export default combineReducers({
guidePage,
homePage,
})
So, the problem is, that I want to put another reducer into homePage reducer, how can I do this?
Like this:
homePage : {
propHomePage: foo,
anotherReducer: {
anotherReducerProp: foo2
}
}
When I use combineReducers({homePage, anotherReducer}) I am getting state like this:
{
homePage: {propHomePage: foo}
anotherReducer: {anotherReducerProp: foo2}
}
And it is wrong

Basically, think of combining reducers as just a key-value object store.
This should work:
export default combineReducers({
guidePage,
homePage: combineReducers({
propHomePage,
anotherReducer
})
});

I think something like this:
const combinedHome = combineReducers({
home1,
home2,
})
export default combineReducers({
combinedHome,
otherReducer,
})

Related

How to add transition effect when content changes in react using CSSTransition

const Component = () => {
const [newName, setnewName] = useState('');
const updateName = (max: number) => {
//logic to update newName
};
return (
<>
<div>{newName}</div>
</>
);
};
export default Component;
When ever the newName variable's value changes I want to add some effect in the ui for it. Is there any way that this can be done?
Yes. You can use the useEffect hook to achieve this, and add the newName state as a dependency.
For example, take a look at the following. I will demonstrate through console.log("hey, newName changed") every time the variable state changes.
const Component = () => {
const [newName, setnewName] = useState('');
useEffect(() => {console.log("hey, newName changed!"}, [newName])
const updateName = (max: number) => {
};
return (
<>
<div>{newName}</div>
</>
);
};
export default Component;
Import it with useState.
Now, you may ask "yes, but youre only consoling something out, not actually doing anything with the CSS transition". Rest assured, you can take a similar approach.
The useEffect hook is simply a function that watches for state change. From the callback function, just add your custom css transition class, or fire a function that changes the CSS.
As I am not sure what kind of transition effect you want as you did not specify it in your question, so forgive me for not being able to provide a specific example. I hope this helps you.
use useEffect and dependancy in to change state and using that state you can update class, and then write aniamtion css for that class , hope this will help...
import React from "react";
import "./App.css";
function App() {
const [newName, setnewName] = React.useState('Lorem');
const [transition, setTransition] = React.useState(false)
React.useEffect(()=>{
setnewName('ipsum')
setTransition(true)
},[newName])
return (
<>
<h1 classNane={`${transition?'animate':''}`} >{newName}</h1>
</>
);
}
export default App;

Using global props in my component with storybook doesn't work

I'm having an issue with storybook. It seems that when I try to pass props from the component to my stories.js it doesn't find my global variable. Everything seems to work correctly, except for the props not showing, sending an error that my prop is undefined.
This is my Component.stories.js code
import Component from './Component';
export default {
title: "Component",
component: Component,
}
// prop1 and prop2 are gathered globally from a .json-file in my project
// prop1 and prop2 are lists
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { Component },
template: '<Component :prop1="prop1" :prop2="prop2" />',
});
// Here I want to chose which props I want to show to be able to see it in storybook
export const allProps = Template.bind({});
allProps.args = {
prop1: ['prop1'],
prop2: ['prop2']
}
Is this even possible or how can I reach a global variable in stories? If there is a possible way to do this I'd love to hear it. I have googled for two days and didn't find anything that worked.
I did try to import the global variable inside my peview.js file but that didn't seem to help either.
I've tried changing globalTypes, argTypes, preview.js. And googled for 2 days. I want storybook to show all fields in my Component. Feel free to ask any questions.

Using RTK Query to accomplish a classic Load-More page, I don't know what is the right way to do it?

API:
import {
createApi,
fetchBaseQuery,
} from '#reduxjs/toolkit/query/react'
import { RootState } from 'store'
export interface FeedType {
id: string
title: string
imgUrl: string
}
export const feedsApi = createApi({
reducerPath: 'feeds',
tagTypes: ['Feeds'],
baseQuery: fetchBaseQuery({
baseUrl: 'http://localhost:5000',
}),
endpoints: (build) => ({
getFeedsMore: build.query<FeedType[], void>({
async queryFn(arg, queryApi, extraOptions, baseQuery) {
const state = queryApi.getState() as RootState
const selector = feedsApi.endpoints.getFeedsMore.select() as (
state: any
) => any
const result = selector(state) as { data: FeedType[] } | undefined
const oldData = (result?.data ?? []) as FeedType[]
const { data } = await baseQuery({
url: 'feeds?_page=' + Math.round(oldData.length / 10 + 1),
})
return { data: [...oldData, ...(data as FeedType[])] }
},
}),
}),
})
export const {
useGetFeedsMoreQuery,
} = feedsApi
Component:
import FeedItem from 'components/FeedItem'
import React from 'react'
import Masonry from 'react-masonry-css'
import { useGetFeedsMoreQuery } from 'services/feeds'
interface FeedsMorePageProps {}
const FeedsMorePage: React.FunctionComponent<FeedsMorePageProps> = () => {
const { isLoading, data: feeds, refetch } = useGetFeedsMoreQuery()
return (
<>
{isLoading ? (
'loading'
) : (
<>
<Masonry
breakpointCols={{
default: 3,
1100: 2,
700: 1,
}}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
{feeds &&
feeds.map((feed) => <FeedItem key={feed.id} feed={feed} />)}
</Masonry>
<button className="btn btn-info" onClick={() => refetch()}>
Load More...
</button>
</>
)}
</>
)
}
export default FeedsMorePage
I know it is totally a mess, this is the only clumsy way I could make it run.
What is the best practice to this scenario?
It is common to use dispatch and getState in a Thunk-Action, but the most confusing part to me is in queryFn I have getState and endpoint.select methods, but I do not know how to type them in typescript.
feedsApi.endpoints.getFeedsMore.select()(state as RootState)
// this gives me a super long type incompatible complain
I can not use useSelector hook here neither, util I made out this ugly way...
Generally, no, that's not what you should do. Building one cache entry that large will mean that eventually you will run out of memory. It can never be collected, so it will just keep growing. But if the user scrolled down to item 9500, you really don't need to keep item 1000 in memory any more.
Especially when you are displaying all those elements in your DOM.
And if you are not displaying all those elements in the DOM, there is also no need to have all of them in the cache.
So, assume you use some kind of virtualization library like react-virtual.
That means you know you have theoretically 10000 items to display, but you only render what is in view and a bit to the front and a bit to the back.
Then keep your endpoint also to a window.
Have your endpoint fetch "parts", so if the user is looking at item 9500, you maybe have items 9500-9550 on the screen and you want to keep some more fetched to quickly display them - one page to the front and one to the back.
So now you use your query hook three times in your component: the current "page" (9500-9550), the last "page" (9450-9500) and the next "page" (9550-9600).
That way, stuff not in view can at some point be cache-collected if it was not in view long enough.
Another way of doing that would be to just render "page" components from a parent component - and each of those "page" components would request their "window of data", while a "get more" button would add another element to the "pages" array in the parent component.
But either way: you would not stitch all of that together in the cache, but keep it as separate cache entries - and then decide to access which of those to access in your component and how to stitch them together.
Generally, I can recommend to read up on this GitHub discussion where multiple people share their approaches to the topic.

Meteor and withTracker: why is a component rendered twice?

I have created a bare-bones Meteor app, using React. It uses the three files shown below (and no others) in a folder called client. In the Console, the App prints out:
withTracker
rendering
withTracker
rendering
props {} {}
state null null
In other words, the App component is rendered twice. The last two lines of output indicate that neither this.props nor this.state changed between renders.
index.html
<body>
<div id="react-target"></div>
</body>
main.jsx
import React from 'react'
import { render } from 'react-dom'
import App from './App.jsx'
Meteor.startup(() => {
render(<App/>, document.getElementById('react-target'));
})
App.jsx
import React from 'react'
import { withTracker } from 'meteor/react-meteor-data'
class App extends React.Component {
render() {
console.log("rendering")
return "Rendered"
}
componentDidUpdate(prevProps, prevState) {
console.log("props", prevProps, this.props)
console.log("state", prevState, this.state)
}
}
export default withTracker(() => {
console.log("withTracker")
})(App)
If I change App.jsx to the following (removing the withTracker wrapper), then the App prints only rendering to the Console, and it only does this once.
import React from 'react'
import { withTracker } from 'meteor/react-meteor-data'
export default class App extends React.Component {
render() {
console.log("rendering")
return "Rendered"
}
componentDidUpdate(prevProps, prevState) {
console.log(prevProps, this.props)
console.log(prevState, this.state)
}
}
What is withTracker doing that triggers this second render? Since I cannot prevent it from occurring, can I be sure that any component that uses withTracker will always render twice?
Context: In my real project, I use withTracker to read data from a MongoDB collection, but I want my component to reveal that data only after a props change triggers the component to rerender. I thought that it would be enough to set a flag after the first render, but it seems that I need to do something more complex.
This a "feature", and it's not restricted to Meteor. It's a feature of asynchronous javascript. Data coming from the database arrives after a delay, no matter how quick your server is.
Your page will render immediately, and then again when the data arrives. Your code needs to allow for that.
One way to achieve this is to use an intermediate component (which can display "Loading" until the data arrives). Let's say that you have a component called List, which is going to display your data from a mongo collection called MyThings
const Loading = (props) => {
if (props.loading) return <div>Loading...</div>
return <List {...props}></List>
}
export default withTracker((props) => {
const subsHandle = Meteor.subscribe('all.myThings')
return {
items: MyThings.find({}).fetch(),
loading: !subsHandle.ready(),
}
})(Loading)
It also means that your List component will only ever be rendered with data, so it can use the props for the initial state, and you can set the PropTypes to be isRequired
I hope that helps
Unsure if you're running into the same error I discovered, or if this is just standard React behavior that you're coming into here as suggested by other answers, but:
When running an older (0.2.x) version of react-meteor-data on the 2.0 Meteor, I was seeing two sets of distinct renders, one of which was missing crucial props and causing issues with server publications due to the missing data. Consider the following:
// ./main.js
const withSomethingCount = (C) => (props) => <C { ...props } count={ ... } />
const withPagination = (C) => (props) => <C { ...props } pagination={ ... } />
const withSomething = withTracker((props) => {
console.log('withSomething:', props);
});
// Assume we're rending a "Hello, World" component here.
export const SomeComponent = withSomethingCount(withPagination(withSomething(...)));
// Console
withSomething: { count: 0 }
withSomething: { count: 0, pagination: { ... } }
withSomething: { count: 0 }
withSomething: { count: 0, pagination: { ... } }
For whatever reason, I was seeing not only N render calls but I was seeing N render calls that were missing properties in a duplicate manner. For those reading this and wonder, there was one and only one use of the component, one and only one use of the withTracker HoC, the parent HoCs had no logic that would cause conditional passing of props.
Unfortunately, I have not discovered a root-cause of the bug. However, creating a fresh Meteor application and moving the code over was the only solution which removed the bug. An in-place update of the Meteor application (2.0 to 2.1) and dependencies DID NOT solve the issue... however a fresh installation and running a git mv client imports server did solve my problems.
I've regrettably had to chalk this up to some form of drift due to subsequent Meteor updates over the two years of development.

How to re-render components everytime state is set

I want my components to re-render everytime I call 'state.set(...)', even if the values doesn't change.
So hey guys, i have this reducer, which is called everytime screen is resized:
import Immutable from 'immutable';
const initialState = Immutable.fromJS({
width: ''
});
export default (state=initialState, action) => {
switch(action.type){
case "SCREEN_RESIZE":
if(action.payload >= 768){
return state.set('width', 'big');
}
else{
return state.set('width', 'small');
}
default:
break;
}
return state;
}
I'm using ImmutableJS along with redux, so my store is a map (entire store) of maps (each reducer).
The problem is that my components only re-renders when we change 'width' from 'big' to 'small', or from 'small' to 'big', that is, when value changes!
I want it to re-render even when I set width from 'big' to 'big' or from 'small' to 'small'.
Am I making any mistake?
This is my rootReducer
import { combineReducers } from 'redux-immutable';
import reducer1 from './reducer1_reducer';
import reducer2 from './reducer2_reducer';
import reducer3 from './reducer3_reducer';
import screenSize from './screenSize_reducer';
import reducer5 from './reducer5_reducer';
import rounting from './routerReducer';
const rootReducer = combineReducers({
reducer1,
reducer2,
reducer3,
screenSize,
reducer5,
routing
});
export default rootReducer;
If you want to re-render on each screen-resizing, you're probably going to want to make the props of the component have the actual screen dimensions, like so:
<MyRerenderableComp width={this.props.screenWidth} height={this.props.screenHeight} />
You're question is somewhat similar to this post: Reactjs - Rerender on browser resize
Hope that helps?

Resources