I start working with React + Redux and newbie to the framework.*
What is am trying to do?
I have to click a button and on the click it will show an svg path in other container/svg tag.
My code or my code
import { connect } from "react-redux";
import { BodyTemplate, Button } from "../component/svgPreview/Preview.jsx";
import { previewBodyTemplate } from "../modcon/Actions.js";
const mapStateToProps = ({ previewTemplateState }) => {
return ({
previewTemplateState: previewTemplateState
});
};
const mapDispatchToProps = (dispatch) => {
return ({
onImageClick: (value) => {
dispatch(previewBodyTemplate(value));
}
});
};
/* the following code will work because we cannot export more then one by
default **/
/*
const PreviewContainer = connect(mapStateToProps, mapDispatchToProps)(Button);
const PreviewBody = connect(mapStateToProps, mapDispatchToProps)(BodyTemplate);
export default PreviewContainer;
export default PreviewBody;
*/
/* so i try this */
export const PreviewContainer = connect(mapStateToProps, mapDispatchToProps)(Button);
export const PreviewBody = connect(mapStateToProps)(BodyTemplate);
According to my knowlage i am passing the state to two components so when the state of one update it will update the other.
But the file is not working because we and not export it directly.
How i have to tackle to pass the state to more then one components
below is the error when i am exporting directly
*
You need to create a parent component, this component will contain the button and the preview component.
const MyContainer = ({ image, onImageClick }) => (
<div>
<div className="sidebar">
<Button label="Preview image" onClick={onImageClick} />
</div>
<div className="content">
<Preview source={image} />
</div>
</div>
);
Once you have your container ready, you need to map the props of this component to the state/actions from redux.
const mapStateToProps = ({ image, previewTemplateState }) => {
return ({
image: image,
previewTemplateState: previewTemplateState
});
};
const mapDispatchToProps = (dispatch) => {
return ({
onImageClick: (value) => {
dispatch(previewBodyTemplate(value));
}
});
};
export default connect(mapStateToProps, mapDispatchToProps)(MyContainer);
Now the main container will receive the data and the actions, from there you can send whatever you need to the children components.
The button and the preview image component are stateless component, they just receive props from the parent container.
Related
I'm trying to access 2 different stores in a single component, but worry that perhaps the architecture of my app may need to change as easy-peasy may not have this functionality.
I have a GlobalStore
import { createStore } from 'easy-peasy';
const globalModel = {
menuOpen: false,
toggleMenu: action((state, payload) => {
state.menuOpen = payload;
}),
};
const GlobalStore = createStore(globalModel);
export default GlobalStore;
Just for this example, I'll use a single state and action used in the store to define whether the navigation menu is open or not.
The GlobalStore appears at the top level of my app in my App.js file.
import React from 'react';
import { StoreProvider } from 'easy-peasy';
import GlobalStore from './store/GlobalStore';
const App = () => {
return (
<StoreProvider store={GlobalStore}>
</StoreProvider>
);
};
export default App;
Now, further down the tree, I have another store SearchStore that dictates which view is active in the component.
import { createStore } from 'easy-peasy';
import { action } from 'easy-peasy';
const searchModel = {
view: 'filter',
setView: action((state, payload) => {
state.view = payload;
}),
};
const SearchStore = createStore(searchModel);
export default SearchStore;
The issue I have now is that in a component that I need to be able to access both stores to update the view with the setView action in the SearchStore and get the value of menuOpen from the GlobalStore but cannot access both concurrently.
The example I have in a component is that I have a styled component that when clicked calls the action setView but its position is also defined by whether the menuOpen is true or not. but obviously, if I try and get the state of menuOpen it will be undefined as it does not exist in SearchStore
const Close = styled.span`
$(({ menuOpen }) => menuOpen ? `
// styles go here
` : `` }
`;
const setView = useStoreActions((action) => action.setView);
const menuOpen = useStoreState((state) => state.menuOpen);
<Close menuOpen={menuOpen} onClick={() => setView('list')}>
Is this possible? Any help would be much appreciated.
Alternative 1: extending the global store
To access both store (via the useStoreState/Actions from the StoreProvider), you could nest both "sub" stores into the GlobalStore:
// SearchModel.js
import { action } from 'easy-peasy';
const searchModel = {
view: 'filter',
setView: action((state, payload) => {
state.view = payload;
}),
};
export default searchModel;
// MenuModel.js
import { action } from 'easy-peasy';
const menuModel = {
isOpen: false,
toggle: action((state, payload) => {
state.isOpen = !state.isOpen;
}),
};
export default menuModel;
// GlobalStore.js
import { createStore } from 'easy-peasy';
import menu from './MenuhModel';
import search from './SearchModel';
const globalModel = {
menu,
search,
};
const GlobalStore = createStore(globalModel);
export default GlobalStore;
This way, you can access both stores at your convenience, using the hooks:
const searchState = useStoreState((state) => state.search);
const menuState = useStoreState((state) => state.menu);
const searchActions = useStoreActions((action) => action.search);
const menuActions = useStoreActions((action) => action.menu);
Alternative 2: useLocalStore()
If you do not want to extend the global store, you could create a local store, by using the useLocalStore():
function Menu() {
const [state, actions] = useLocalStore(() => ({
isOpen: false,
toggle: action((state, payload) => {
state.isOpen = !state.isOpen;
}),
}));
return (
<div>
{state.isOpen && <MenuItems />}
<button onClick={() => actions.toggle()}>Open menu</button>
</div>
);
}
However, the drawback of this approach, is that the state is not global and only available at the component-level.
You could however get around this, by creating your own provider - but then again, alternative 1 would probably be the path of least resistance.
In my code below, I have a delete button that should be deleting the data if clicked. However, when I click on it, I am seeing through console.log that it is returning undefined instead of the id number. Can't seem to figure out why. Any help will be greatly appreciated. Thank you.
//Actions File
export const GET_ITEMS = 'GET ITEMS';
export const FETCH_ITEMS_SUCCESS = 'FETCH ITEMS SUCCESS';
export const FETCH_ITEMS_ERROR = 'FETCH ITEMS ERROR';
export const DELETE_ITEM = 'DELETE_ITEM';
export const getItems = () => ({
type: GET_ITEMS
});
export const deleteItem = (itemId) => ({
type : DELETE_ITEM,
payload: itemId
});
//App.js
class App extends Component {
componentDidMount() {
this.props.getItems()
}
static propTypes = {
getItems: PropTypes.func.isRequired,
deleteItem: PropTypes.func.isRequired
}
handleDelete = (id) =>{
this.props.deleteItem(id)
console.log(this.props.deleteItem(id));
}
render() {
const { itemsList} = this.props.items
return (
<div className="container app-wrapper">
<header>
{itemsList.map(item => (<h1 key={item.id}>{item.title} <button onClick={this.handleDelete.bind(this, item.id)}>delete</button></h1>))}
</header>
</div>
);
}
}
const mapStateToProps = state => ({
items: state.items
});
export default connect(mapStateToProps, {getItems, deleteItem})(App);
The dispatched action should return undefined, because it does not return anything. You are misunderstanding how data flows in the Redux/reducer pattern.
Here's the basic flow of a Redux update:
Action is dispatched.
All reducers receive the action object.
All reducers return their new or previous state depending on that action's contents.
connect sees that the Redux state has changed, and triggers a re-render of the children components.
You may now use the updated data from your Redux store through props (mapped in mapStateToProps).
You cannot call an action and receive the updated state as the return value. It breaks the fundamental pattern of how data flows/updates in Redux.
You are referencing your delete action incorrectly in connect. deleteItem expects an id param passed into it.
Try this,
const mapStateToProps = state => ({
items: state.items
});
const mapDispatchToProps = (dispatch) =>
{
return {
deleteItem: (id) => dispatch(actions.deleteItem(id)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
so I've got an really simple react-app. It renders some cars on the first render, when you click details, it takes you to another router and shows only that vehicle based on its ID.
It's all okay when you follow the right order, open up the page, redux gets filled with data, car cards render up, then you click 'details' button, react-router steps in and routes us to some particular car's id, based on the ID we see the car.
BUT... at that point, when you try to click re-render the page, I get nothing from my redux store, what am I need to do? Do I need to inplement in my every component that needs redux store to fetch items if there's not?
This is my slice from redux
import { createSlice, createEntityAdapter, createAsyncThunk } from '#reduxjs/toolkit'
import axios from 'axios'
const carAdapter = createEntityAdapter();
// async action
export const fetchCars = createAsyncThunk('cars', async () =>{
const car = await axios.get('http://localhost:5000/api/cars');
return car.data.data
});
const carSlice = createSlice({
name: 'cars',
initialState: carAdapter.getInitialState({
status: 'idle',
error: null
}),
reducers:{
},
extraReducers :{
[fetchCars.pending]: (state, action) => {
state.status = 'loading'
},
[fetchCars.fulfilled]: (state, action) => {
state.status = 'fulfilled'
carAdapter.setAll(state, action.payload)
},
[fetchCars.rejected]: (state, action) => {
state.status = 'failed'
state.error = action.error.message
}
}
})
export const {
selectAll: selectAllCars,
selectById : selectCarById,
} = carAdapter.getSelectors(state => state.cars)
export default carSlice.reducer
This is my first page, where I render all the vehicles from my api
import React, { useEffect } from 'react'
import { Link } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { fetchCars, selectAllCars } from '../features/car/carSlice'
import './car.css'
export default function Car() {
const dispatch = useDispatch();
const carStatus = useSelector(state => state.cars.status)
const cars = useSelector(selectAllCars)
useEffect(() => {
if(carStatus === 'idle') {
dispatch(fetchCars());
}
}, [dispatch, carStatus])
return (
<>
{
cars.map(car => {
return (
<div key={car.id} className="card">
<img src={car.vehiclePhoto} alt="vehicle" className="vehicle-img" />
<div className="card-container">
<h4>{car.vehicleName}</h4>
<p>{car.price}</p>
<Link to={car.id}>Details</Link>
</div>
</div>
)
})
}
</>
)
}
This is where the issue begins when you try to re-load the page
export default function Car({ match: { params: { id } } }) {
const state = useSelector(state => state)
const car = selectCarById(state, id);
return (
<div className="card">
{ car ?
<>
<img src={car.vehiclePhoto} alt="vehicle" class="vehicle-img" />
<div className="card-container">
<h4>{car.vehicleName}</h4>
<p>{car.price}</p>
</div>
</> : 'loading...'
}
</div>
)
}
Every page of the app needs to be able to load its own data. On a page which displays details for a single car, you want it to look in the state, select the data if it's already loaded, and dispatch a request for the data if it hasn't been loaded (like when you go to that page directly).
You'll want to use a different API endpoint on the single car page than the one that you use on the home page because you want to load a single car's details from the id. It's probably something like 'http://localhost:5000/api/cars/123' for id #123.
Looking at the last image, I think line 9 should be
return selectCarById(state, id)
in my react-redux app one of the components is rendering "box" components according to a number passed down as props through the store. the number is controlled by a slider controlled component, that for sure changes the store as i can see with my redux dev tools.
at first render the component renders the boxes as expected inside their wrapper div, but as soon as i move the slider and change the number all i get is a single box component.
i've tried to change the component into a stateful one and use different hooks but so far without success.
here is the component:
import React from 'react';
import { connect } from 'react-redux';
import Box from './Box';
const Grid = ({ boxNumber }) => {
return(
<div className='flex flex-wrap'>
{new Array(boxNumber).fill(null).map((box, i) => <Box key={i} />)}
</div>
)
}
const mapStateToProps = (state) => ({
boxNumber: state.boxNumberReducer.boxNumber
})
export default connect(mapStateToProps)(Grid);
i'm adding here the reducer and action just in case even though i don't believe that's where the issue is, but maybe i'm missing something.
reducer:
import { SET_BOX_NUMBER } from '../actions/constants';
const initialState = {
boxNumber: 100
}
export default (state = initialState , {type, payload}) => {
switch (type) {
case SET_BOX_NUMBER:
return {...state, boxNumber: payload};
default:
return state;
}
}
action:
export const setBoxNumber = (payload) => ({
type: SET_BOX_NUMBER, payload
})
here is the box component, i'm using tailwindcss so it's basically a div with height and width of 2rem, a border and a white background color:
import React from 'react';
const Box = () => {
return(
<div className='w-8 h-8 border border-black bg-white'>
</div>
)
}
export default Box;
EDIT:
this is the slider component where the action is being dispatched:
import React from 'react';
import { connect } from 'react-redux';
import { setBoxNumber } from '../actions';
const Slider = ({ boxNumber, handleChange }) => {
return(
<div className='slider p-1 m-1'>
<div className='flex justify-center'>
{boxNumber}
</div>
<div className='flex justify-center'>
<input
onChange={handleChange}
value={boxNumber}
type="range"
step='10'
min="10"
max="500"
/>
</div>
</div>
)
}
const mapStateToProps = (state) => ({
boxNumber: state.boxNumberReducer.boxNumber
});
const mapDispatchToProps = {
handleChange: (event) => setBoxNumber(event.target.value)
}
export default connect(mapStateToProps, mapDispatchToProps)(Slider);
You need to convert event.target.value to Number in your Slider component, because you are passing the value as string to new Array(boxNumber)
const mapDispatchToProps = {
handleChange: (event) => setBoxNumber(Number(event.target.value))
}
I'm dispatching an action from some-other component , and store is getting updated with svgArr property, but though the following Stateless component connect'ed to the store , it ain't getting updated when store changes for svgArr.
Is it how it suppose to behave as it's a stateless component ? Or am I doing something wrong ?
const Layer = (props) => {
console.log(props.svgArr);
return (<div style = {
{
width: props.canvasWidth,
height: props.canvasWidth
}
}
className = {
styles.imgLayer
} > hi < /div>);
};
connect((state) => {
return {
svgArr: state.svgArr
};
}, Layer);
export default Layer;
You seem to be exporting Layer instead of the connected version of the Layer component.
If you look at the redux documentation: https://github.com/reactjs/react-redux/blob/master/docs/api.md#inject-dispatch-and-todos
It should be something like
function mapStateToProps(state) {
return {svgArr: state.svgArr}
}
export default connect(mapSTateToProps)(Layer)
Here's a rewrite of your code
import {connect} from 'react-redux';
// this should probably not be a free variable
const styles = {imgLayer: '???'};
const _Layer = ({canvasWidth}) => (
<div className={styles.imgLayer}
style={{
width: canvasWidth,
height: canvasWidth
}}
children="hi" />
);
const Layer = connect(
state => ({
svgArr: state.svgArr
})
)(_Layer);
export default Layer;
If you want to connect the stateless function you should wrap it into
the another const:
const Layer = (props) => {
return (
<div >
</div>
);
};
export const ConnectedLayer = connect(mapStateToProps)(Layer);
Here use redux with functional component in react native
import { useSelector } from 'react-redux';
const variable = useSelector(state => state.user.variable)
In addition, you can also pass multiple state object with functional components.
import {connect} from 'react-redux';
const PartialReview = ({auth, productreview}) => (
<div className="row">
<h2>{auth.uInfo._ubase}<h2>
<p>{productreview.review_description}
</div>
);
const mapStateToProps = (state) => {
return {auth: state.auth,productreview: state.productreview}
};
export default connect(mapStateToProps)(PartialReview)