When using many higher order functions I have found that the exports in a React project can get very messy. For example using react-redux, react-router, redux-form and react-i18next can look like:
const mapStateToProps = (state) => {
return {
// stuff
}
};
const form = {
// stuff
};
export default withRouter(withTranslation()(reduxForm(form)(connect(mapStateToProps)(Component)))));
This can get pretty unwieldy and keeping track of and changing the order can get messy. I created a simple one-liner to wrap the exports:
wrapper.js
export default ({ component, wrappers }) => wrappers.reduce((acc, wrapper) => wrapper(acc), component);
To be used like so:
import wrapper from '../path/to/wrapper.js';
/* rest of code */
const mapStateToProps = (state) => {
return {
// stuff
}
};
const form = {
// stuff
};
export default wrapper({
component: Component,
wrappers: [
// The order of the wrappers can be changed by just changing
// the order in which they appear here
connect(mapStateToProps),
reduxForm(form),
withTranslation,
withRouter,
],
});
So far this has worked, my question is: are there any potential issues with this approach that might not be immediately apparent? And are there any other ways or best practices for managing React exports?
Related
I facing a weird issue where, the state slice is working fine (backed by unit test and manual testing). But the react selector doesn't seem to have subscribed to it correctly.
The parent component:
import { useSelector } from 'react-redux'
import { Die } from './components/Die'
import './Dice.css'
import { Die as DieModel } from '../../core/dice/entities/Die'
import { RootState } from '../main'
export function Dice() {
const dice: DieModel[] = useSelector<RootState, DieModel[]>(
(state) => state.dice.dice,
)
const diceElements = dice.map((die) => <Die key={die.id} die={die} />)
return <div className="dice-container">{diceElements}</div>
}
The child component:
import './Die.css'
import { useDispatch } from 'react-redux'
import { AppDispatch } from '../../main'
import { holdDie } from '../../../core/dice/diceSlice'
interface DieProps {
die: {
id: string
props: {
value: number
isHeld: boolean
}
}
}
export function Die({
die: {
id,
props: { isHeld, value },
},
}: DieProps) {
const dispatch = useDispatch<AppDispatch>()
console.log(id, isHeld, value)
const isHeldStyle = {
backgroundColor: isHeld ? `var(--held-die)` : `var(--bright-white)`,
}
return (
<div
className="die-face"
style={isHeldStyle}
onClick={() => dispatch(holdDie(id))}
>
<h2 className="die-num">{value}</h2>
</div>
)
}
(Props are a bit exotic but don't get disturbed by it)
Full project codebase can be found here: https://github.com/amehmeto/HexaTenzies
import { createSlice } from '#reduxjs/toolkit'
import { Die } from './entities/Die'
import { rollDice } from './usecases/rollDice/rollDice'
import { holdDieReducer } from './usecases/holdDie/holdDie'
export const initialState = {
dice: [] as Die[],
loading: false,
error: null,
}
export const diceSlice = createSlice({
name: 'dice',
initialState,
reducers: {
holdDie: holdDieReducer,
},
extraReducers: (builder) => {
builder.addCase(rollDice.fulfilled, (state, action) => {
state.dice = action.payload
})
},
})
export const { holdDie } = diceSlice.actions
When looking at the Redux Toolkit devtool, the state is working as expected on click event. But the prop isHeld stays falsy.
What am I doing wrong? Why isn't the state being propagated from parent to child?
Per discussion in Reactiflux: it appears that the primary problem is use of a mutable class being stored in Redux. React and Redux both expect plain JS objects and arrays, immutably updated, in order to trigger re-renders:
https://redux.js.org/style-guide/#do-not-mutate-state
https://redux.js.org/style-guide/#do-not-put-non-serializable-values-in-state-or-actions
Related: the whole "ports and adapters" concept really doesn't translate well to a client-side app, and especially React+Redux. that's also going to make this a lot more complex and boilerplate-y than it needs to be. The "ports and adapters" concept does come from a OOP architecture background. While you probably could do something along those lines without writing any of the code OOP-style, all of the articles you've probably read about "ports and adapters" are most likely assuming use of classes and such. So, the main advice here would be:
Switch to storing data as plain JS objects and arrays
I'd avoid trying to write a client-side app as "ports and adapters". It's not idiomatic React or Redux, and it's going to make the code more complex.
Additionally, while it's not directly related to the issue: you're writing wayyyy more TS types code than you need to. I'd recommend taking a look at our TS usage instructions at https://redux.js.org/tutorials/typescript-quick-start to see our recommended TS usage patterns.
I'm attempting to create a React/Redux component that shows/hides an element when clicked.
I'm using this to trigger the function from another component:
import React from 'react'
//Some other code...
import { useSelector } from 'react-redux'
import onShowHelpClicked from '../help/AddHelpSelector'
<button onClick={onShowHelpClicked}>Help</button>
This this is AddHelpSelector:
import { useState } from 'react'
import { useDispatch } from 'react-redux'
import { helpVisible } from './HelpSlice'
export const AddHelp = () => {
const [isVisible, showHelp] = useState('')
const dispatch = useDispatch()
const onShowHelpClicked = () => {
dispatch(
helpVisible({
isVisible,
})
)
if (isVisible) {
showHelp(false)
} else {
showHelp(true)
}
}
return (
<section>
<h2 style={{ visibility: { isVisible } }}>Help section</h2>
</section>
)
}
export default AddHelp
Finally, this is HelpSlice
import { createSlice } from '#reduxjs/toolkit'
const initialState = [{ isVisible: false }]
const helpSlice = createSlice({
name: 'help',
initialState,
reducers: {
helpVisible(state, action) {
state.push(action.payload)
},
},
})
export const { helpVisible } = helpSlice.actions
export default helpSlice.reducer
I'm fairly certain I'm doing multiple things wrong, as this is my first attempt to do anything with Redux and I'm still struggling to wrap my mind around it after a week of learning.
But specifically, when clicking the help button I get this error.
"Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app"
The linked documentation provides a way to test a component to see if React is importing properly, and it's not. But I'm not really sure what I'm doing wrong.
I think it may be that I'm importing React multiple times, but if I don't then I can't use "useState."
What's the correct way to do this? I'm open to corrections on both my code as well as naming conventions. I'm using boilerplate code as a template as I try to understand this better after getting through the documentation as well as Mosh's 6 hour course which I just finished.
You're importing the < AddHelpSelector /> component here import onShowHelpClicked from '../help/AddHelpSelector', and then you try to use it as a callback handler for the button's onClick, which doesn't really make sense. I assume you actually wanted to only import the onShowHelpClicked function declared inside the < AddHelpSelector /> component (which is not really a valid way of doing it). Since you want to control the visibility using redux state, you could just grab the flag from the redux store inside the < AddHelpSelector /> component using useSelector hook. To set it, you're gonna do that in the component where your button is. For that, you just need to dispatch an action(like you already did), with the updated flag. No need for the local useState. Also, using the flag you could just conditionally render the element.
const App = () => {
const dispatch = useDispatch();
const { isVisible } = useSelector((state) => ({ isVisible: state.isVisible }));
const handleClick = () => {
dispatch(
helpVisible({
!isVisible,
})
)
}
return (<button onClick={handleClick}>Help</button>);
}
export const AddHelp = () => {
const { isVisible } = useSelector((state) => ({ isVisible: state.isVisible }));
return (
<section>
{isVisible && <h2>Help section</h2>}
</section>
)
}
export default AddHelp
What I Just want to fetch data from api and show it at frontend. I am using Redux to call the api using it's ACTIONS and REDUCERS. In Reducers i take the intialstate as empty array.When API is successfully called, I am updating store state.Below is the practical which can help to understand concept easily.
store.js
import { createStore } from 'redux';
import reducer from './reducers/reducer';
let store = createStore(reducer)
export default store
actions.js
import {
FETCH_IMAGES_SUCCESS
} from './actionTypes'
export function fetchImages() {
return dispatch => {
return fetch("https://api.com/data")
.then(res => res.json())
.then(json => {
dispatch(fetchImagesSuccess(json.posts));
return json.posts;
})
};
}
export const fetchImagesSuccess = images => ({
type: FETCH_IMAGES_SUCCESS,
payload: { images }
});
reducer.js
import {
FETCH_IMAGES_SUCCESS
} from '../actions/actionTypes'
const initialState = {
images:[]
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_IMAGES_SUCCESS:
return {...state,images:action.payload.images}
default:
return state
}
}
export default reducer;
Now, Please tell me what should i need to do to call that Redux action and
get Data from the API.I am using React to display data.
Thanks.
In React redux usage page you can use functions like mapStateToProps and connect to do that
You need a middleware like Redux-Saga or Redux-Thunk to talk with the actions and the global store maintained using Redux.
You may follow this Tutorial: https://redux.js.org/basics/exampletodolist
If you are going with Redux-Thunk, you need to modify your store assign like this:
const store = createStore(rootReducer, applyMiddleware(thunk));
Now, have a container to all the Parent component you have.
import { connect } from 'react-redux';
import App from '../components/App';
export function mapStateToProps(appState) {
return {
/* this is where you get your store data through the reducer returned
state */
};
}
export function mapDispatchToProps(dispatch) {
return {
// make all your action dispatches here
// for ex: getData(payload) => dispatch({type: GETDATA, payload: payload})
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
As Mustafa said you need to use mapStateToProps. Let me explain myself.
What you just done is just the configuration for the main store (there's only one in redux). Now you need to use it in your components, but how ? When you create a Component the content of the store will be passed as props with the help of Containers.
Containers are the way to link your store with your react component.
Said that, you need to install redux and react-redux. In your code above you have successfully configured the store with the reducers with redux library. Now you need react-redux to create the Container (which wraps your react component).
Here is an example of how to put this all together:
https://codepen.io/anon/pen/RqKyQZ?editors=1010
You need to use mapStateToProps similar to the code below. Let say your reducer is called test and it is part of a state.
const mapStateToProps = (state, props) =>
({
router: props.router,
test: state.test
});
Then test will be used as a property in a React class. Obviously you need to include respective imports for React.
I am building an app using redux and react-native.
I am curious about a pattern which I use. I have encountered no downsides however I haven't seen it in any tutorials which makes me wonder why nobody does it.
Instead of passing action creators as props in the connect function like
connect(mapStateToProps,{ func1, func2 })(Component);
I imported the app store inside of the module where I declare the functions in the first place:
import { AppStore } from '../App';
const actionCreator = () => {
doSomethng();
appStore.dispatch({ type: 'Action' });
};
This to me makes it easier to do async actions because I need no middleware:
import { AppStore } from '../App';
const actionCreator = async () => {
await doSomethng();
appStore.dispatch({ type: 'Action' });
};
I did this because of the js-lint error 'no-shadow'. It made me realise that in order to use it I had to import the action creators in the component file, and then pass it as a prop to the connect function in order for the action creator to have access to dispatch.
import { actionCreator1, actionCreator2 } from './actionCreators';
const myComponent = (props) => {
const { actionCreator1, actionCreator2 } = props; //shadowed names
return (
<Button onPress={actionCreator1} />
);
};
export default connect({}, { actionCreator1, actionCreator2 })(myComponent)
In my version I just import it once but do not pass it to connect. This eliminates the need to shadow names.
import { actionCreator1, actionCreator2 } from './actionCreators';
const myComponent = (props) => {
return (
<Button onPress={actionCreator1} />
);
};
export default connect({})(myComponent)
I like that you try to find your own solutions to your specific problems. It's the sign of an engineer, just in this case this isn't the solution.
I think the idea of how Redux teaches you to do things is not intended to be canonized. You have the ability to put a dispatcher on your props because it allows things to be transparent, meaning that things are bound outside of your class and injected in. You have hidden your store dependency by directly referencing it in some other files. It's no longer as obvious how your application works with regards to the workflow. Another react developer would be confused, I suppose that's the major downside.
If you're ok with those aspects what you're doing is fine. Fine as in, it gets the job done, but not "fine" in that it embraces concepts like Single Responsibility Principle
I want to connect / bind actions in the same file in which I am defining my component, and ideally would like this component to be functional. The problem is doing this means I must alias my action so as to avoid the eslint rule no-shadow. You can see this in the code block below. However, I also use an IDE and aliasing these actions renders them invisible to my IDE when trying to find all usages of said actions.
Is there a way I can connect these dispatched actions to my functional component while making said actions visible to my IDE for debugging?
import React from 'react';
import {connect} from 'react-redux';
import {actionOne, actionTwo} from '../../../../actions';
const ComponentOne = ({actionOneDispatch, actionTwoDispatch}) => {
const handleClick = () => {
actionOneDispatch();
actionTwoDispatch()
};
return (
<button onClick={handleClick}>Click Me</button>
);
};
const mapDispatchToProps = (dispatch) => ({
actionOneDispatch: () => {
dispatch(actionOne());
},
actionTwoDispatch: () => {
dispatch(actionTwo());
},
});
export default connect(null, mapDispatchToProps)(ComponentOne);